summaryrefslogtreecommitdiffstats
path: root/src/libdnssec/key/privkey.c
blob: abe968a3e2f16a820def245c6f4dbcca44059bbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <gnutls/abstract.h>
#include <gnutls/gnutls.h>

#include "libdnssec/binary.h"
#include "libdnssec/error.h"
#include "libdnssec/key/algorithm.h"
#include "libdnssec/key/convert.h"
#include "libdnssec/key/dnskey.h"
#include "libdnssec/key/internal.h"
#include "libdnssec/key/privkey.h"
#include "libdnssec/shared/shared.h"
#include "libdnssec/shared/binary_wire.h"

/* -- internal functions --------------------------------------------------- */

/*!
 * Check if the algorithm number is valid for given DNSKEY.
 */
static bool valid_algorithm(dnssec_key_t *key, gnutls_privkey_t privkey)
{
	uint8_t current = dnssec_key_get_algorithm(key);
	int gnu_algorithm = gnutls_privkey_get_pk_algorithm(privkey, NULL);

	return (gnu_algorithm == algorithm_to_gnutls(current));
}

/*!
 * Create GnuTLS public key from private key.
 */
static int public_from_private(gnutls_privkey_t privkey, gnutls_pubkey_t *pubkey)
{
	assert(privkey);
	assert(pubkey);

	gnutls_pubkey_t new_key = NULL;
	int result = gnutls_pubkey_init(&new_key);
	if (result != GNUTLS_E_SUCCESS) {
		return DNSSEC_ENOMEM;
	}

	result = gnutls_pubkey_import_privkey(new_key, privkey, 0, 0);
	if (result != GNUTLS_E_SUCCESS) {
		gnutls_pubkey_deinit(new_key);
		return DNSSEC_KEY_IMPORT_ERROR;
	}

	*pubkey = new_key;

	return DNSSEC_EOK;
}

/*!
 * Create public key (GnuTLS and DNSKEY RDATA) from a private key.
 */
static int create_public_key(gnutls_privkey_t privkey,
			     gnutls_pubkey_t *pubkey_ptr,
			     dnssec_binary_t *rdata)
{
	assert(privkey);
	assert(pubkey_ptr);
	assert(rdata);

	// crypto public key

	gnutls_pubkey_t pubkey = NULL;
	int result = public_from_private(privkey, &pubkey);
	if (result != DNSSEC_EOK) {
		return result;
	}

	// dnssec public key

	_cleanup_binary_ dnssec_binary_t rdata_pubkey = { 0 };
	result = convert_pubkey_to_dnskey(pubkey, &rdata_pubkey);
	if (result != DNSSEC_EOK) {
		gnutls_pubkey_deinit(pubkey);
		return result;
	}

	size_t rdata_size = DNSKEY_RDATA_OFFSET_PUBKEY + rdata_pubkey.size;
	result = dnssec_binary_resize(rdata, rdata_size);
	if (result != DNSSEC_EOK) {
		gnutls_pubkey_deinit(pubkey);
		return result;
	}

	// updated RDATA

	wire_ctx_t wire = binary_init(rdata);
	wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
	binary_write(&wire, &rdata_pubkey);
	assert(wire_ctx_offset(&wire) == rdata->size);

	*pubkey_ptr = pubkey;

	return DNSSEC_EOK;
}

/* -- internal API --------------------------------------------------------- */

/*!
 * Load a private key into a DNSSEC key, create a public part if necessary.
 */
int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey)
{
	assert(key);
	assert(privkey);
	assert(key->private_key == NULL);

	if (!valid_algorithm(key, privkey)) {
		return DNSSEC_INVALID_KEY_ALGORITHM;
	}

	if (!key->public_key) {
		int r = create_public_key(privkey, &key->public_key, &key->rdata);
		if (r != DNSSEC_EOK) {
			return r;
		}
	}

	key->private_key = privkey;

	return DNSSEC_EOK;
}