resolve: add support for RFC 8080 (#7600)

RFC 8080 describes how to use EdDSA keys and signatures in DNSSEC. It
uses the curves Ed25519 and Ed448. Libgcrypt 1.8.1 does not support
Ed448, so only the Ed25519 is supported at the moment. Once Libgcrypt
supports Ed448, support for it can be trivially added to resolve.
This commit is contained in:
ott 2017-12-12 16:30:12 +01:00 committed by Lennart Poettering
parent 7715629e9a
commit cb9eeb062c
5 changed files with 378 additions and 38 deletions

View File

@ -53,6 +53,7 @@ Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Unde
Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS
Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors
~ https://tools.ietf.org/html/rfc7719 → DNS Terminology
Y https://tools.ietf.org/html/rfc8080 → Edwards-Curve Digital Security Algorithm (EdDSA) for DNSSEC
Also relevant:

View File

@ -18,12 +18,16 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdio_ext.h>
#if HAVE_GCRYPT
#include <gcrypt.h>
#endif
#include "alloc-util.h"
#include "dns-domain.h"
#include "fd-util.h"
#include "fileio.h"
#include "gcrypt-util.h"
#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
@ -436,6 +440,99 @@ static int dnssec_ecdsa_verify(
q, key_size*2+1);
}
#if GCRYPT_VERSION_NUMBER >= 0x010600
static int dnssec_eddsa_verify_raw(
const char *curve,
const void *signature_r, size_t signature_r_size,
const void *signature_s, size_t signature_s_size,
const void *data, size_t data_size,
const void *key, size_t key_size) {
gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
gcry_error_t ge;
int k;
ge = gcry_sexp_build(&signature_sexp,
NULL,
"(sig-val (eddsa (r %b) (s %b)))",
(int) signature_r_size,
signature_r,
(int) signature_s_size,
signature_s);
if (ge != 0) {
k = -EIO;
goto finish;
}
ge = gcry_sexp_build(&data_sexp,
NULL,
"(data (flags eddsa) (hash-algo sha512) (value %b))",
(int) data_size,
data);
if (ge != 0) {
k = -EIO;
goto finish;
}
ge = gcry_sexp_build(&public_key_sexp,
NULL,
"(public-key (ecc (curve %s) (flags eddsa) (q %b)))",
curve,
(int) key_size,
key);
if (ge != 0) {
k = -EIO;
goto finish;
}
ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
k = 0;
else if (ge != 0) {
log_debug("EdDSA signature check failed: %s", gpg_strerror(ge));
k = -EIO;
} else
k = 1;
finish:
if (public_key_sexp)
gcry_sexp_release(public_key_sexp);
if (signature_sexp)
gcry_sexp_release(signature_sexp);
if (data_sexp)
gcry_sexp_release(data_sexp);
return k;
}
static int dnssec_eddsa_verify(
int algorithm,
const void *data, size_t data_size,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey) {
const char *curve;
size_t key_size;
if (algorithm == DNSSEC_ALGORITHM_ED25519) {
curve = "Ed25519";
key_size = 32;
} else
return -EOPNOTSUPP;
if (dnskey->dnskey.key_size != key_size)
return -EINVAL;
if (rrsig->rrsig.signature_size != key_size * 2)
return -EINVAL;
return dnssec_eddsa_verify_raw(
curve,
rrsig->rrsig.signature, key_size,
(uint8_t*) rrsig->rrsig.signature + key_size, key_size,
data, data_size,
dnskey->dnskey.key, key_size);
}
#endif
static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
gcry_md_write(md, &v, sizeof(v));
}
@ -445,9 +542,18 @@ static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
gcry_md_write(md, &v, sizeof(v));
}
static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
static void fwrite_uint8(FILE *fp, uint8_t v) {
fwrite(&v, sizeof(v), 1, fp);
}
static void fwrite_uint16(FILE *fp, uint16_t v) {
v = htobe16(v);
fwrite(&v, sizeof(v), 1, fp);
}
static void fwrite_uint32(FILE *fp, uint32_t v) {
v = htobe32(v);
gcry_md_write(md, &v, sizeof(v));
fwrite(&v, sizeof(v), 1, fp);
}
static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) {
@ -613,6 +719,9 @@ int dnssec_verify_rrset(
gcry_md_hd_t md = NULL;
int r, md_algorithm;
size_t k, n = 0;
size_t sig_size = 0;
_cleanup_free_ char *sig_data = NULL;
_cleanup_fclose_ FILE *f = NULL;
size_t hash_size;
void *hash;
bool wildcard;
@ -628,14 +737,6 @@ int dnssec_verify_rrset(
* using the signature "rrsig" and the key "dnskey". It's
* assumed that RRSIG and DNSKEY match. */
md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
if (md_algorithm == -EOPNOTSUPP) {
*result = DNSSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
if (md_algorithm < 0)
return md_algorithm;
r = dnssec_rrsig_prepare(rrsig);
if (r == -EINVAL) {
*result = DNSSEC_INVALID;
@ -725,28 +826,23 @@ int dnssec_verify_rrset(
/* Bring the RRs into canonical order */
qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
initialize_libgcrypt(false);
f = open_memstream(&sig_data, &sig_size);
if (!f)
return -ENOMEM;
__fsetlocking(f, FSETLOCKING_BYCALLER);
hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);
gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
md_add_uint16(md, rrsig->rrsig.type_covered);
md_add_uint8(md, rrsig->rrsig.algorithm);
md_add_uint8(md, rrsig->rrsig.labels);
md_add_uint32(md, rrsig->rrsig.original_ttl);
md_add_uint32(md, rrsig->rrsig.expiration);
md_add_uint32(md, rrsig->rrsig.inception);
md_add_uint16(md, rrsig->rrsig.key_tag);
fwrite_uint16(f, rrsig->rrsig.type_covered);
fwrite_uint8(f, rrsig->rrsig.algorithm);
fwrite_uint8(f, rrsig->rrsig.labels);
fwrite_uint32(f, rrsig->rrsig.original_ttl);
fwrite_uint32(f, rrsig->rrsig.expiration);
fwrite_uint32(f, rrsig->rrsig.inception);
fwrite_uint16(f, rrsig->rrsig.key_tag);
r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
fwrite(wire_format_name, 1, r, f);
/* Convert the source of synthesis into wire format */
r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true);
@ -760,24 +856,66 @@ int dnssec_verify_rrset(
/* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */
if (wildcard)
gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
gcry_md_write(md, wire_format_name, r);
fwrite((uint8_t[]) { 1, '*'}, sizeof(uint8_t), 2, f);
fwrite(wire_format_name, 1, r, f);
md_add_uint16(md, rr->key->type);
md_add_uint16(md, rr->key->class);
md_add_uint32(md, rrsig->rrsig.original_ttl);
fwrite_uint16(f, rr->key->type);
fwrite_uint16(f, rr->key->class);
fwrite_uint32(f, rrsig->rrsig.original_ttl);
l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
fwrite_uint16(f, (uint16_t) l);
fwrite(DNS_RESOURCE_RECORD_RDATA(rr), 1, l, f);
}
hash = gcry_md_read(md, 0);
if (!hash) {
r = -EIO;
r = fflush_and_check(f);
if (r < 0)
return r;
initialize_libgcrypt(false);
switch (rrsig->rrsig.algorithm) {
#if GCRYPT_VERSION_NUMBER >= 0x010600
case DNSSEC_ALGORITHM_ED25519:
break;
#else
case DNSSEC_ALGORITHM_ED25519:
#endif
case DNSSEC_ALGORITHM_ED448:
*result = DNSSEC_UNSUPPORTED_ALGORITHM;
r = 0;
goto finish;
default:
/* OK, the RRs are now in canonical order. Let's calculate the digest */
md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
if (md_algorithm == -EOPNOTSUPP) {
*result = DNSSEC_UNSUPPORTED_ALGORITHM;
r = 0;
goto finish;
}
if (md_algorithm < 0) {
r = md_algorithm;
goto finish;
}
gcry_md_open(&md, md_algorithm, 0);
if (!md) {
r = -EIO;
goto finish;
}
hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);
gcry_md_write(md, sig_data, sig_size);
hash = gcry_md_read(md, 0);
if (!hash) {
r = -EIO;
goto finish;
}
}
switch (rrsig->rrsig.algorithm) {
@ -802,6 +940,15 @@ int dnssec_verify_rrset(
rrsig,
dnskey);
break;
#if GCRYPT_VERSION_NUMBER >= 0x010600
case DNSSEC_ALGORITHM_ED25519:
r = dnssec_eddsa_verify(
rrsig->rrsig.algorithm,
sig_data, sig_size,
rrsig,
dnskey);
break;
#endif
}
if (r < 0)
@ -821,7 +968,9 @@ int dnssec_verify_rrset(
r = 0;
finish:
gcry_md_close(md);
if (md)
gcry_md_close(md);
return r;
}

View File

@ -1849,6 +1849,8 @@ static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] =
[DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST",
[DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256",
[DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384",
[DNSSEC_ALGORITHM_ED25519] = "ED25519",
[DNSSEC_ALGORITHM_ED448] = "ED448",
[DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT",
[DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS",
[DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID",

View File

@ -57,6 +57,8 @@ enum {
DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */
DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */
DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */
DNSSEC_ALGORITHM_ED25519 = 15, /* RFC 8080 */
DNSSEC_ALGORITHM_ED448 = 16, /* RFC 8080 */
DNSSEC_ALGORITHM_INDIRECT = 252,
DNSSEC_ALGORITHM_PRIVATEDNS,
DNSSEC_ALGORITHM_PRIVATEOID,

View File

@ -19,6 +19,7 @@
***/
#include <arpa/inet.h>
#include <gcrypt.h>
#include <netinet/in.h>
#include <sys/socket.h>
@ -124,6 +125,189 @@ static void test_dnssec_verify_dns_key(void) {
assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0);
}
static void test_dnssec_verify_rfc8080_ed25519_example1(void) {
static const uint8_t dnskey_blob[] = {
0x97, 0x4d, 0x96, 0xa2, 0x2d, 0x22, 0x4b, 0xc0, 0x1a, 0xdb, 0x91, 0x50, 0x91, 0x47, 0x7d,
0x44, 0xcc, 0xd9, 0x1c, 0x9a, 0x41, 0xa1, 0x14, 0x30, 0x01, 0x01, 0x17, 0xd5, 0x2c, 0x59,
0x24, 0xe
};
static const uint8_t ds_fprint[] = {
0xdd, 0xa6, 0xb9, 0x69, 0xbd, 0xfb, 0x79, 0xf7, 0x1e, 0xe7, 0xb7, 0xfb, 0xdf, 0xb7, 0xdc,
0xd7, 0xad, 0xbb, 0xd3, 0x5d, 0xdf, 0x79, 0xed, 0x3b, 0x6d, 0xd7, 0xf6, 0xe3, 0x56, 0xdd,
0xd7, 0x47, 0xf7, 0x6f, 0x5f, 0x7a, 0xe1, 0xa6, 0xf9, 0xe5, 0xce, 0xfc, 0x7b, 0xbf, 0x5a,
0xdf, 0x4e, 0x1b
};
static const uint8_t signature_blob[] = {
0xa0, 0xbf, 0x64, 0xac, 0x9b, 0xa7, 0xef, 0x17, 0xc1, 0x38, 0x85, 0x9c, 0x18, 0x78, 0xbb,
0x99, 0xa8, 0x39, 0xfe, 0x17, 0x59, 0xac, 0xa5, 0xb0, 0xd7, 0x98, 0xcf, 0x1a, 0xb1, 0xe9,
0x8d, 0x07, 0x91, 0x02, 0xf4, 0xdd, 0xb3, 0x36, 0x8f, 0x0f, 0xe4, 0x0b, 0xb3, 0x77, 0xf1,
0xf0, 0x0e, 0x0c, 0xdd, 0xed, 0xb7, 0x99, 0x16, 0x7d, 0x56, 0xb6, 0xe9, 0x32, 0x78, 0x30,
0x72, 0xba, 0x8d, 0x02
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *mx = NULL,
*rrsig = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
DnssecResult result;
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.");
assert_se(dnskey);
dnskey->dnskey.flags = 257;
dnskey->dnskey.protocol = 3;
dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ED25519;
dnskey->dnskey.key_size = sizeof(dnskey_blob);
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
ds = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "example.com.");
assert_se(ds);
ds->ds.key_tag = 3613;
ds->ds.algorithm = DNSSEC_ALGORITHM_ED25519;
ds->ds.digest_type = DNSSEC_DIGEST_SHA256;
ds->ds.digest_size = sizeof(ds_fprint);
ds->ds.digest = memdup(ds_fprint, ds->ds.digest_size);
assert_se(ds->ds.digest);
log_info("DS: %s", strna(dns_resource_record_to_string(ds)));
mx = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_MX, "example.com.");
assert_se(mx);
mx->mx.priority = 10;
mx->mx.exchange = strdup("mail.example.com.");
assert_se(mx->mx.exchange);
log_info("MX: %s", strna(dns_resource_record_to_string(mx)));
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.");
assert_se(rrsig);
rrsig->rrsig.type_covered = DNS_TYPE_MX;
rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ED25519;
rrsig->rrsig.labels = 2;
rrsig->rrsig.original_ttl = 3600;
rrsig->rrsig.expiration = 1440021600;
rrsig->rrsig.inception = 1438207200;
rrsig->rrsig.key_tag = 3613;
rrsig->rrsig.signer = strdup("example.com.");
assert_se(rrsig->rrsig.signer);
rrsig->rrsig.signature_size = sizeof(signature_blob);
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
assert_se(dnssec_key_match_rrsig(mx->key, rrsig) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
answer = dns_answer_new(1);
assert_se(answer);
assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
#if GCRYPT_VERSION_NUMBER >= 0x010600
assert_se(result == DNSSEC_VALIDATED);
#else
assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM);
#endif
}
static void test_dnssec_verify_rfc8080_ed25519_example2(void) {
static const uint8_t dnskey_blob[] = {
0xcc, 0xf9, 0xd9, 0xfd, 0x0c, 0x04, 0x7b, 0xb4, 0xbc, 0x0b, 0x94, 0x8f, 0xcf, 0x63, 0x9f,
0x4b, 0x94, 0x51, 0xe3, 0x40, 0x13, 0x93, 0x6f, 0xeb, 0x62, 0x71, 0x3d, 0xc4, 0x72, 0x4,
0x8a, 0x3b
};
static const uint8_t ds_fprint[] = {
0xe3, 0x4d, 0x7b, 0xf3, 0x56, 0xfd, 0xdf, 0x87, 0xb7, 0xf7, 0x67, 0x5e, 0xe3, 0xdd, 0x9e,
0x73, 0xbe, 0xda, 0x7b, 0x67, 0xb5, 0xe5, 0xde, 0xf4, 0x7f, 0xae, 0x7b, 0xe5, 0xad, 0x5c,
0xd1, 0xb7, 0x39, 0xf5, 0xce, 0x76, 0xef, 0x97, 0x34, 0xe1, 0xe6, 0xde, 0xf3, 0x47, 0x3a,
0xeb, 0x5e, 0x1c
};
static const uint8_t signature_blob[] = {
0xcd, 0x74, 0x34, 0x6e, 0x46, 0x20, 0x41, 0x31, 0x05, 0xc9, 0xf2, 0xf2, 0x8b, 0xd4, 0x28,
0x89, 0x8e, 0x83, 0xf1, 0x97, 0x58, 0xa3, 0x8c, 0x32, 0x52, 0x15, 0x62, 0xa1, 0x86, 0x57,
0x15, 0xd4, 0xf8, 0xd7, 0x44, 0x0f, 0x44, 0x84, 0xd0, 0x4a, 0xa2, 0x52, 0x9f, 0x34, 0x28,
0x4a, 0x6e, 0x69, 0xa0, 0x9e, 0xe0, 0x0f, 0xb0, 0x10, 0x47, 0x43, 0xbb, 0x2a, 0xe2, 0x39,
0x93, 0x6a, 0x5c, 0x06
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *mx = NULL,
*rrsig = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
DnssecResult result;
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.");
assert_se(dnskey);
dnskey->dnskey.flags = 257;
dnskey->dnskey.protocol = 3;
dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ED25519;
dnskey->dnskey.key_size = sizeof(dnskey_blob);
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
ds = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "example.com.");
assert_se(ds);
ds->ds.key_tag = 35217;
ds->ds.algorithm = DNSSEC_ALGORITHM_ED25519;
ds->ds.digest_type = DNSSEC_DIGEST_SHA256;
ds->ds.digest_size = sizeof(ds_fprint);
ds->ds.digest = memdup(ds_fprint, ds->ds.digest_size);
assert_se(ds->ds.digest);
log_info("DS: %s", strna(dns_resource_record_to_string(ds)));
mx = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_MX, "example.com.");
assert_se(mx);
mx->mx.priority = 10;
mx->mx.exchange = strdup("mail.example.com.");
assert_se(mx->mx.exchange);
log_info("MX: %s", strna(dns_resource_record_to_string(mx)));
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.");
assert_se(rrsig);
rrsig->rrsig.type_covered = DNS_TYPE_MX;
rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ED25519;
rrsig->rrsig.labels = 2;
rrsig->rrsig.original_ttl = 3600;
rrsig->rrsig.expiration = 1440021600;
rrsig->rrsig.inception = 1438207200;
rrsig->rrsig.key_tag = 35217;
rrsig->rrsig.signer = strdup("example.com.");
assert_se(rrsig->rrsig.signer);
rrsig->rrsig.signature_size = sizeof(signature_blob);
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
assert_se(dnssec_key_match_rrsig(mx->key, rrsig) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
answer = dns_answer_new(1);
assert_se(answer);
assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
#if GCRYPT_VERSION_NUMBER >= 0x010600
assert_se(result == DNSSEC_VALIDATED);
#else
assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM);
#endif
}
static void test_dnssec_verify_rrset(void) {
static const uint8_t signature_blob[] = {
@ -335,6 +519,8 @@ int main(int argc, char*argv[]) {
#if HAVE_GCRYPT
test_dnssec_verify_dns_key();
test_dnssec_verify_rfc8080_ed25519_example1();
test_dnssec_verify_rfc8080_ed25519_example2();
test_dnssec_verify_rrset();
test_dnssec_verify_rrset2();
test_dnssec_nsec3_hash();