Skip to content

Commit

Permalink
resolve: add support for RFC 8080 (#7600)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ott authored and poettering committed Dec 12, 2017
1 parent 7715629 commit cb9eeb0
Show file tree
Hide file tree
Showing 5 changed files with 378 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/resolve/RFCs
Expand Up @@ -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:

Expand Down
225 changes: 187 additions & 38 deletions src/resolve/resolved-dns-dnssec.c
Expand Up @@ -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"
Expand Down Expand Up @@ -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));
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);

gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
f = open_memstream(&sig_data, &sig_size);
if (!f)
return -ENOMEM;
__fsetlocking(f, FSETLOCKING_BYCALLER);

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);
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -821,7 +968,9 @@ int dnssec_verify_rrset(
r = 0;

finish:
gcry_md_close(md);
if (md)
gcry_md_close(md);

return r;
}

Expand Down
2 changes: 2 additions & 0 deletions src/resolve/resolved-dns-rr.c
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/resolve/resolved-dns-rr.h
Expand Up @@ -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,
Expand Down

0 comments on commit cb9eeb0

Please sign in to comment.