diff options
-rwxr-xr-x | .github/workflows/unit_tests.sh | 2 | ||||
-rw-r--r-- | .github/workflows/unit_tests.yml | 10 | ||||
-rw-r--r-- | .packit.yml | 3 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | meson.build | 57 | ||||
-rw-r--r-- | meson_options.txt | 2 | ||||
-rw-r--r-- | src/basic/build.c | 2 | ||||
-rw-r--r-- | src/basic/gcrypt-util.c | 2 | ||||
-rw-r--r-- | src/basic/gcrypt-util.h | 19 | ||||
-rw-r--r-- | src/import/pull-job.c | 65 | ||||
-rw-r--r-- | src/import/pull-job.h | 4 | ||||
-rw-r--r-- | src/partition/repart.c | 23 | ||||
-rw-r--r-- | src/resolve/meson.build | 24 | ||||
-rw-r--r-- | src/resolve/resolvectl.c | 1 | ||||
-rw-r--r-- | src/resolve/resolved-conf.c | 6 | ||||
-rw-r--r-- | src/resolve/resolved-dns-dnssec.c | 726 | ||||
-rw-r--r-- | src/resolve/resolved-dns-packet.c | 6 | ||||
-rw-r--r-- | src/resolve/resolved-link.c | 4 | ||||
-rw-r--r-- | src/resolve/test-dnssec.c | 207 | ||||
-rw-r--r-- | src/shared/openssl-util.c | 67 | ||||
-rw-r--r-- | src/shared/openssl-util.h | 43 | ||||
-rw-r--r-- | src/test/meson.build | 6 | ||||
-rw-r--r-- | src/test/test-cryptolib.c (renamed from src/test/test-gcrypt-util.c) | 17 |
23 files changed, 1003 insertions, 298 deletions
diff --git a/.github/workflows/unit_tests.sh b/.github/workflows/unit_tests.sh index 7e7e3016e8..ec7e92a49a 100755 --- a/.github/workflows/unit_tests.sh +++ b/.github/workflows/unit_tests.sh @@ -29,6 +29,8 @@ function info() { set -ex +MESON_ARGS=(-Dcryptolib=${CRYPTOLIB:-auto}) + for phase in "${PHASES[@]}"; do case $phase in SETUP) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 1458732d2b..537074c231 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -22,10 +22,18 @@ jobs: fail-fast: false matrix: run_phase: [GCC, GCC_ASAN_UBSAN, CLANG, CLANG_ASAN_UBSAN] + cryptolib: [auto] + include: + - run_phase: GCC + cryptolib: openssl + - run_phase: CLANG + cryptolib: gcrypt steps: - name: Repository checkout uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - name: Install build dependencies run: sudo -E .github/workflows/unit_tests.sh SETUP - - name: Build & test (${{ matrix.run_phase }}) + - name: Build & test (${{ matrix.run_phase }}-${{ matrix.cryptolib }}) run: sudo -E .github/workflows/unit_tests.sh RUN_${{ matrix.run_phase }} + env: + CRYPTOLIB: ${{ matrix.cryptolib }} diff --git a/.packit.yml b/.packit.yml index 962c77913e..98b71fc15e 100644 --- a/.packit.yml +++ b/.packit.yml @@ -31,6 +31,9 @@ actions: # [0] https://github.com/mesonbuild/meson/issues/7360 # [1] https://github.com/systemd/systemd/pull/18908#issuecomment-792250110 - 'sed -i "/^CONFIGURE_OPTS=(/a--werror" .packit_rpm/systemd.spec' + # FIXME: temporarily disable the deprecated-declarations check to suppress + # OpenSSL 3.0 warnings in Rawhide + - 'sed -i "1 i %global optflags %{optflags} -Wno-deprecated-declarations" .packit_rpm/systemd.spec' jobs: - job: copr_build @@ -432,11 +432,8 @@ Features: * socket units: allow creating a udev monitor socket with ListenDevices= or so, with matches, then activate app through that passing socket over -* unify on openssl (as soon as OpenSSL 3.0 is out, and the Debian license - confusion is gone) - - port resolved over from libgcrypt (DNSSEC code) +* unify on openssl: - port journald + fsprg over from libgcrypt - - port importd over from libgcrypt - when that's done: kill gnutls support in resolved * add growvol and makevol options for /etc/crypttab, similar to diff --git a/meson.build b/meson.build index ce3b01e342..0eb89a7bbd 100644 --- a/meson.build +++ b/meson.build @@ -1448,21 +1448,25 @@ else endif conf.set10('HAVE_DBUS', have) -default_dnssec = get_option('default-dnssec') -if skip_deps - default_dnssec = 'no' -endif -if default_dnssec != 'no' and conf.get('HAVE_GCRYPT') == 0 - message('default-dnssec cannot be set to yes or allow-downgrade when gcrypt is disabled. Setting default-dnssec to no.') - default_dnssec = 'no' -endif -conf.set('DEFAULT_DNSSEC_MODE', - 'DNSSEC_' + default_dnssec.underscorify().to_upper()) -conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec) +# We support one or the other. If gcrypt is available, we assume it's there to +# be used, and use it in preference. +opt = get_option('cryptolib') +if opt == 'openssl' and conf.get('HAVE_OPENSSL') == 0 + error('openssl requested as the default cryptolib, but not available') +endif +conf.set10('PREFER_OPENSSL', + opt == 'openssl' or (opt == 'auto' and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_GCRYPT') == 0)) +conf.set10('HAVE_OPENSSL_OR_GCRYPT', + conf.get('HAVE_OPENSSL') == 1 or conf.get('HAVE_GCRYPT') == 1) +lib_openssl_or_gcrypt = conf.get('PREFER_OPENSSL') == 1 ? libopenssl : libgcrypt dns_over_tls = get_option('dns-over-tls') if dns_over_tls != 'false' - if dns_over_tls == 'openssl' + if dns_over_tls == 'gnutls' and conf.get('PREFER_OPENSSL') == 1 + error('Sorry, -Ddns-over-tls=gnutls is not supported when openssl is used as the cryptolib') + endif + + if dns_over_tls == 'openssl' or conf.get('PREFER_OPENSSL') == 1 have_gnutls = false else have_gnutls = (conf.get('HAVE_GNUTLS') == 1 and libgnutls.version().version_compare('>= 3.6.0')) @@ -1523,12 +1527,24 @@ else endif conf.set10('ENABLE_REPART', have) +default_dnssec = get_option('default-dnssec') +if skip_deps + default_dnssec = 'no' +endif +if default_dnssec != 'no' and conf.get('HAVE_OPENSSL_OR_GCRYPT') == 0 + message('default-dnssec cannot be set to yes or allow-downgrade openssl and gcrypt are disabled. Setting default-dnssec to no.') + default_dnssec = 'no' +endif +conf.set('DEFAULT_DNSSEC_MODE', + 'DNSSEC_' + default_dnssec.underscorify().to_upper()) +conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec) + want_importd = get_option('importd') if want_importd != 'false' have = (conf.get('HAVE_LIBCURL') == 1 and + conf.get('HAVE_OPENSSL_OR_GCRYPT') == 1 and conf.get('HAVE_ZLIB') == 1 and - conf.get('HAVE_XZ') == 1 and - conf.get('HAVE_GCRYPT') == 1) + conf.get('HAVE_XZ') == 1) if want_importd == 'true' and not have error('importd support was requested, but dependencies are not available') endif @@ -2139,6 +2155,7 @@ if conf.get('ENABLE_RESOLVE') == 1 libbasic_gcrypt, libsystemd_resolve_core], dependencies : [threads, + lib_openssl_or_gcrypt, libgpg_error, libm, libidn], @@ -2717,10 +2734,10 @@ if conf.get('ENABLE_IMPORTD') == 1 link_with : [libshared], dependencies : [versiondep, libcurl, + lib_openssl_or_gcrypt, libz, libbzip2, - libxz, - libgcrypt], + libxz], install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) @@ -4021,6 +4038,14 @@ else found += 'static-libudev(@0@)'.format(static_libudev) endif +if conf.get('HAVE_OPENSSL_OR_GCRYPT') == 1 and conf.get('PREFER_OPENSSL') == 1 + found += 'cryptolib(openssl)' +elif conf.get('HAVE_OPENSSL_OR_GCRYPT') == 1 + found += 'cryptolib(gcrypt)' +else + missing += 'cryptolib' +endif + if conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1 found += 'DNS-over-TLS(gnutls)' elif conf.get('DNS_OVER_TLS_USE_OPENSSL') == 1 diff --git a/meson_options.txt b/meson_options.txt index 1e91bf1fd2..0d3491a56c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -380,6 +380,8 @@ option('gnutls', type : 'combo', choices : ['auto', 'true', 'false'], description : 'gnutls support') option('openssl', type : 'combo', choices : ['auto', 'true', 'false'], description : 'openssl support') +option('cryptolib', type : 'combo', choices : ['auto', 'openssl', 'gcrypt'], + description : 'whether to use openssl or gcrypt where both are supported') option('p11kit', type : 'combo', choices : ['auto', 'true', 'false'], description : 'p11kit support') option('libfido2', type : 'combo', choices : ['auto', 'true', 'false'], diff --git a/src/basic/build.c b/src/basic/build.c index 45074591a6..f8baaabb9f 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -48,7 +48,7 @@ const char* const systemd_features = " -SECCOMP" #endif - /* crypto libraries */ + /* cryptographic libraries */ #if HAVE_GCRYPT " +GCRYPT" diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index cdc308aca3..64c63cdab1 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -18,6 +18,7 @@ void initialize_libgcrypt(bool secmem) { gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } +# if !PREFER_OPENSSL int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; gcry_error_t err; @@ -47,4 +48,5 @@ int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { *out = enc; return 0; } +# endif #endif diff --git a/src/basic/gcrypt-util.h b/src/basic/gcrypt-util.h index 27dcc72028..4c40cefbed 100644 --- a/src/basic/gcrypt-util.h +++ b/src/basic/gcrypt-util.h @@ -12,23 +12,28 @@ #include "macro.h" void initialize_libgcrypt(bool secmem); -int string_hashsum(const char *s, size_t len, int md_algorithm, char **out); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(gcry_md_hd_t, gcry_md_close, NULL); #endif +#if !PREFER_OPENSSL +# if HAVE_GCRYPT +int string_hashsum(const char *s, size_t len, int md_algorithm, char **out); +# endif + static inline int string_hashsum_sha224(const char *s, size_t len, char **out) { -#if HAVE_GCRYPT +# if HAVE_GCRYPT return string_hashsum(s, len, GCRY_MD_SHA224, out); -#else +# else return -EOPNOTSUPP; -#endif +# endif } static inline int string_hashsum_sha256(const char *s, size_t len, char **out) { -#if HAVE_GCRYPT +# if HAVE_GCRYPT return string_hashsum(s, len, GCRY_MD_SHA256, out); -#else +# else return -EOPNOTSUPP; -#endif +# endif } +#endif diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 34b116a8f8..f5eb82131e 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -41,8 +41,12 @@ PullJob* pull_job_unref(PullJob *j) { import_compress_free(&j->compress); - if (j->checksum_context) - gcry_md_close(j->checksum_context); + if (j->checksum_ctx) +#if PREFER_OPENSSL + EVP_MD_CTX_free(j->checksum_ctx); +#else + gcry_md_close(j->checksum_ctx); +#endif free(j->url); free(j->etag); @@ -102,9 +106,13 @@ static int pull_job_restart(PullJob *j, const char *new_url) { import_compress_free(&j->compress); - if (j->checksum_context) { - gcry_md_close(j->checksum_context); - j->checksum_context = NULL; + if (j->checksum_ctx) { +#if PREFER_OPENSSL + EVP_MD_CTX_free(j->checksum_ctx); +#else + gcry_md_close(j->checksum_ctx); +#endif + j->checksum_ctx = NULL; } r = pull_job_begin(j); @@ -200,16 +208,30 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { goto finish; } - if (j->checksum_context) { - uint8_t *k; + if (j->checksum_ctx) { + unsigned checksum_len; +#if PREFER_OPENSSL + uint8_t k[EVP_MAX_MD_SIZE]; - k = gcry_md_read(j->checksum_context, GCRY_MD_SHA256); + r = EVP_DigestFinal_ex(j->checksum_ctx, k, &checksum_len); + if (r == 0) { + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum."); + goto finish; + } + assert(checksum_len <= sizeof k); +#else + const uint8_t *k; + + k = gcry_md_read(j->checksum_ctx, GCRY_MD_SHA256); if (!k) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum."); goto finish; } - j->checksum = hexmem(k, gcry_md_get_algo_dlen(GCRY_MD_SHA256)); + checksum_len = gcry_md_get_algo_dlen(GCRY_MD_SHA256); +#endif + + j->checksum = hexmem(k, checksum_len); if (!j->checksum) { r = log_oom(); goto finish; @@ -358,8 +380,16 @@ static int pull_job_write_compressed(PullJob *j, void *p, size_t sz) { return log_error_errno(SYNTHETIC_ERRNO(EFBIG), "Content length incorrect."); - if (j->checksum_context) - gcry_md_write(j->checksum_context, p, sz); + if (j->checksum_ctx) { +#if PREFER_OPENSSL + r = EVP_DigestUpdate(j->checksum_ctx, p, sz); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Could not hash chunk."); +#else + gcry_md_write(j->checksum_ctx, p, sz); +#endif + } r = import_uncompress(&j->compress, p, sz, pull_job_write_uncompressed, j); if (r < 0) @@ -392,11 +422,22 @@ static int pull_job_open_disk(PullJob *j) { } if (j->calc_checksum) { +#if PREFER_OPENSSL + j->checksum_ctx = EVP_MD_CTX_new(); + if (!j->checksum_ctx) + return log_oom(); + + r = EVP_DigestInit_ex(j->checksum_ctx, EVP_sha256(), NULL); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize hash context."); +#else initialize_libgcrypt(false); - if (gcry_md_open(&j->checksum_context, GCRY_MD_SHA256, 0) != 0) + if (gcry_md_open(&j->checksum_ctx, GCRY_MD_SHA256, 0) != 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize hash context."); +#endif } return 0; diff --git a/src/import/pull-job.h b/src/import/pull-job.h index bc5258a693..7a98b0f2f6 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include <gcrypt.h> #include <sys/stat.h> #include "curl-util.h" #include "import-compress.h" #include "macro.h" +#include "openssl-util.h" #include "pull-common.h" typedef struct PullJob PullJob; @@ -74,7 +74,7 @@ struct PullJob { usec_t last_status_usec; bool calc_checksum; - gcry_md_hd_t checksum_context; + hash_context_t checksum_ctx; char *checksum; bool sync; diff --git a/src/partition/repart.c b/src/partition/repart.c index f1af5bb0ee..895c0665d8 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -12,9 +12,6 @@ #include <sys/ioctl.h> #include <sys/stat.h> -#include <openssl/hmac.h> -#include <openssl/sha.h> - #include "sd-id128.h" #include "alloc-util.h" @@ -38,6 +35,7 @@ #include "glyph-util.h" #include "gpt.h" #include "hexdecoct.h" +#include "hmac.h" #include "id128-util.h" #include "json.h" #include "list.h" @@ -1519,7 +1517,7 @@ static int fdisk_set_disklabel_id_by_uuid(struct fdisk_context *c, sd_id128_t id static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) { union { - unsigned char md[SHA256_DIGEST_LENGTH]; + uint8_t md[SHA256_DIGEST_SIZE]; sd_id128_t id; } result; @@ -1531,11 +1529,7 @@ static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) { * machine ID). We use the machine ID as key (and not as cleartext!) of the HMAC operation since it's * the machine ID we don't want to leak. */ - if (!HMAC(EVP_sha256(), - &base, sizeof(base), - (const unsigned char*) token, strlen(token), - result.md, NULL)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "HMAC-SHA256 calculation failed."); + hmac_sha256(base.bytes, sizeof(base.bytes), token, strlen(token), result.md); /* Take the first half, mark it as v4 UUID */ assert_cc(sizeof(result.md) == sizeof(result.id) * 2); @@ -3067,7 +3061,7 @@ static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *re uint64_t counter; } _packed_ plaintext = {}; union { - unsigned char md[SHA256_DIGEST_LENGTH]; + uint8_t md[SHA256_DIGEST_SIZE]; sd_id128_t id; } result; @@ -3111,11 +3105,10 @@ static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *re plaintext.type_uuid = p->type_uuid; plaintext.counter = htole64(k); - if (!HMAC(EVP_sha256(), - &context->seed, sizeof(context->seed), - (const unsigned char*) &plaintext, k == 0 ? sizeof(sd_id128_t) : sizeof(plaintext), - result.md, NULL)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SHA256 calculation failed."); + hmac_sha256(context->seed.bytes, sizeof(context->seed.bytes), + &plaintext, + k == 0 ? sizeof(sd_id128_t) : sizeof(plaintext), + result.md); /* Take the first half, mark it as v4 UUID */ assert_cc(sizeof(result.md) == sizeof(result.id) * 2); diff --git a/src/resolve/meson.build b/src/resolve/meson.build index c7cb88ac04..d4534cb4a9 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -176,14 +176,16 @@ tests += [ [['src/resolve/test-resolve-tables.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm]], [['src/resolve/test-dns-packet.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm]], @@ -192,28 +194,33 @@ tests += [ 'src/resolve/resolved-etc-hosts.h'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm]], [['src/resolve/test-resolved-packet.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm]], [['src/resolve/test-dnssec.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, - libm]], + libm], + [], 'HAVE_OPENSSL_OR_GCRYPT'], [['src/resolve/test-dnssec-complex.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm], [], '', 'manual'], @@ -223,7 +230,8 @@ fuzzers += [ [['src/resolve/fuzz-dns-packet.c'], [libsystemd_resolve_core, libshared], - [libgcrypt, + [lib_openssl_or_gcrypt, + libgcrypt, libgpg_error, libm]], ] diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 20455bf7a9..5b3ceeff36 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -23,6 +23,7 @@ #include "main-func.h" #include "missing_network.h" #include "netlink-util.h" +#include "openssl-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 453f1175e1..a4e44f29be 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -498,14 +498,14 @@ int manager_parse_config_file(Manager *m) { return r; } -#if ! HAVE_GCRYPT +#if !HAVE_OPENSSL_OR_GCRYPT if (m->dnssec_mode != DNSSEC_NO) { - log_warning("DNSSEC option cannot be enabled or set to allow-downgrade when systemd-resolved is built without gcrypt support. Turning off DNSSEC support."); + log_warning("DNSSEC option cannot be enabled or set to allow-downgrade when systemd-resolved is built without a cryptographic library. Turning off DNSSEC support."); m->dnssec_mode = DNSSEC_NO; } #endif -#if ! ENABLE_DNS_OVER_TLS +#if !ENABLE_DNS_OVER_TLS if (m->dns_over_tls_mode != DNS_OVER_TLS_NO) { log_warning("DNS-over-TLS option cannot be enabled or set to opportunistic when systemd-resolved is built without DNS-over-TLS support. Turning off DNS-over-TLS support."); m->dns_over_tls_mode = DNS_OVER_TLS_NO; diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index fd7679f17d..b1fe9d1314 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -7,6 +7,7 @@ #include "gcrypt-util.h" #include "hexdecoct.h" #include "memory-util.h" +#include "openssl-util.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "sort-util.h" @@ -58,7 +59,7 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { return sum & UINT32_C(0xFFFF); } -#if HAVE_GCRYPT +#if HAVE_OPENSSL_OR_GCRYPT static int rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b) { const DnsResourceRecord *x = *a, *y = *b; @@ -82,12 +83,67 @@ static int rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b } static int dnssec_rsa_verify_raw( - const char *hash_algorithm, + hash_algorithm_t hash_algorithm, const void *signature, size_t signature_size, const void *data, size_t data_size, const void *exponent, size_t exponent_size, const void *modulus, size_t modulus_size) { +#if PREFER_OPENSSL + _cleanup_(RSA_freep) RSA *rpubkey = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *epubkey = NULL; + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL; + _cleanup_(BN_freep) BIGNUM *e = NULL, *m = NULL; + int r; + + assert(hash_algorithm); + + e = BN_bin2bn(exponent, exponent_size, NULL); + if (!e) + return -EIO; + + m = BN_bin2bn(modulus, modulus_size, NULL); + if (!m) + return -EIO; + + rpubkey = RSA_new(); + if (!rpubkey) + return -ENOMEM; + + if (RSA_set0_key(rpubkey, m, e, NULL) <= 0) + return -EIO; + e = m = NULL; + + assert((size_t) RSA_size(rpubkey) == signature_size); + + epubkey = EVP_PKEY_new(); + if (!epubkey) + return -ENOMEM; + + if (EVP_PKEY_assign_RSA(epubkey, RSAPublicKey_dup(rpubkey)) <= 0) + return -EIO; + + ctx = EVP_PKEY_CTX_new(epubkey, NULL); + if (!ctx) + return -ENOMEM; + + if (EVP_PKEY_verify_init(ctx) <= 0) + return -EIO; + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + return -EIO; + + if (EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) + return -EIO; + + r = EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Signature verification failed: 0x%lx", ERR_get_error()); + + return r; + +#else gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; gcry_mpi_t n = NULL, e = NULL, s = NULL; gcry_error_t ge; @@ -147,10 +203,10 @@ static int dnssec_rsa_verify_raw( ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) r = 0; - else if (ge != 0) { - log_debug("RSA signature check failed: %s", gpg_strerror(ge)); - r = -EIO; - } else + else if (ge != 0) + r = log_debug_errno(SYNTHETIC_ERRNO(EIO), + "RSA signature check failed: %s", gpg_strerror(ge)); + else r = 1; finish: @@ -169,10 +225,11 @@ finish: gcry_sexp_release(data_sexp); return r; +#endif } static int dnssec_rsa_verify( - const char *hash_algorithm, + hash_algorithm_t hash_algorithm, const void *hash, size_t hash_size, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { @@ -228,13 +285,78 @@ static int dnssec_rsa_verify( } static int dnssec_ecdsa_verify_raw( - const char *hash_algorithm, - const char *curve, + hash_algorithm_t hash_algorithm, + elliptic_curve_t 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) { +#if PREFER_OPENSSL + _cleanup_(EC_GROUP_freep) EC_GROUP *ec_group = NULL; + _cleanup_(EC_POINT_freep) EC_POINT *p = NULL; + _cleanup_(EC_KEY_freep) EC_KEY *eckey = NULL; + _cleanup_(BN_CTX_freep) BN_CTX *bctx = NULL; + _cleanup_(BN_freep) BIGNUM *r = NULL, *s = NULL; + _cleanup_(ECDSA_SIG_freep) ECDSA_SIG *sig = NULL; + int k; + + assert(hash_algorithm); + + ec_group = EC_GROUP_new_by_curve_name(curve); + if (!ec_group) + return -ENOMEM; + + p = EC_POINT_new(ec_group); + if (!p) + return -ENOMEM; + + bctx = BN_CTX_new(); + if (!bctx) + return -ENOMEM; + + if (EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) + return -EIO; + + eckey = EC_KEY_new(); + if (!eckey) + return -ENOMEM; + + if (EC_KEY_set_group(eckey, ec_group) <= 0) + return -EIO; + + if (EC_KEY_set_public_key(eckey, p) <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "EC_POINT_bn2point failed: 0x%lx", ERR_get_error()); + + assert(EC_KEY_check_key(eckey) == 1); + + r = BN_bin2bn(signature_r, signature_r_size, NULL); + if (!r) + return -EIO; + + s = BN_bin2bn(signature_s, signature_s_size, NULL); + if (!s) + return -EIO; + + /* TODO: We should eventually use use the EVP API once it supports ECDSA signature verification */ + + sig = ECDSA_SIG_new(); + if (!sig) + return -ENOMEM; + + if (ECDSA_SIG_set0(sig, r, s) <= 0) + return -EIO; + r = s = NULL; + + k = ECDSA_do_verify(data, data_size, sig, eckey); + if (k < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Signature verification failed: 0x%lx", ERR_get_error()); + + return k; + +#else gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; gcry_mpi_t q = NULL, r = NULL, s = NULL; gcry_error_t ge; @@ -315,16 +437,17 @@ finish: gcry_sexp_release(data_sexp); return k; +#endif } static int dnssec_ecdsa_verify( - const char *hash_algorithm, + hash_algorithm_t hash_algorithm, int algorithm, const void *hash, size_t hash_size, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { - const char *curve; + elliptic_curve_t curve; size_t key_size; uint8_t *q; @@ -334,11 +457,11 @@ static int dnssec_ecdsa_verify( assert(dnskey); if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { + curve = OPENSSL_OR_GCRYPT(NID_X9_62_prime256v1, "NIST P-256"); /* NIST P-256 */ key_size = 32; - curve = "NIST P-256"; } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { + curve = OPENSSL_OR_GCRYPT(NID_secp384r1, "NIST P-384"); /* NIST P-384 */ key_size = 48; - curve = "NIST P-384"; } else return -EOPNOTSUPP; @@ -361,25 +484,66 @@ 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) { + elliptic_curve_t curve, + const uint8_t *signature, size_t signature_size, + const uint8_t *data, size_t data_size, + const uint8_t *key, size_t key_size) { + +#if PREFER_OPENSSL + _cleanup_(EVP_PKEY_freep) EVP_PKEY *evkey = NULL; + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *pctx = NULL; + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; + int r; + + assert(curve == NID_ED25519); + assert(signature_size == key_size * 2); + + uint8_t *q = newa(uint8_t, signature_size + 1); + q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ + memcpy(q+1, signature, signature_size); + + evkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, key, key_size); + if (!evkey) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "EVP_PKEY_new_raw_public_key failed: 0x%lx", ERR_get_error()); + + pctx = EVP_PKEY_CTX_new(evkey, NULL); + if (!pctx) + return -ENOMEM; + + ctx = EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + /* This prevents EVP_DigestVerifyInit from managing pctx and complicating our free logic. */ + EVP_MD_CTX_set_pkey_ctx(ctx, pctx); + + /* One might be tempted to use EVP_PKEY_verify_init, but see Ed25519(7ssl). */ + if (EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) + return -EIO; + + r = EVP_DigestVerify(ctx, signature, signature_size, data, data_size); + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Signature verification failed: 0x%lx", ERR_get_error()); + + return r; + +#elif GCRYPT_VERSION_NUMBER >= 0x010600 gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; gcry_error_t ge; int k; + assert(signature_size == key_size * 2); + 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); + (int) key_size, + signature, + (int) key_size, + signature + key_size); if (ge != 0) { k = -EIO; goto finish; @@ -409,10 +573,10 @@ static int dnssec_eddsa_verify_raw( 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 + else if (ge != 0) + k = log_debug_errno(SYNTHETIC_ERRNO(EIO), + "EdDSA signature check failed: %s", gpg_strerror(ge)); + else k = 1; finish: if (public_key_sexp) @@ -423,6 +587,9 @@ finish: gcry_sexp_release(data_sexp); return k; +#else + return -EOPNOTSUPP; +#endif } static int dnssec_eddsa_verify( @@ -430,11 +597,11 @@ static int dnssec_eddsa_verify( const void *data, size_t data_size, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { - const char *curve; + elliptic_curve_t curve; size_t key_size; if (algorithm == DNSSEC_ALGORITHM_ED25519) { - curve = "Ed25519"; + curve = OPENSSL_OR_GCRYPT(NID_ED25519, "Ed25519"); key_size = 32; } else return -EOPNOTSUPP; @@ -447,20 +614,28 @@ static int dnssec_eddsa_verify( return dnssec_eddsa_verify_raw( curve, - rrsig->rrsig.signature, key_size, - (uint8_t*) rrsig->rrsig.signature + key_size, key_size, + rrsig->rrsig.signature, rrsig->rrsig.signature_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)); +static int md_add_uint8(hash_context_t ctx, uint8_t v) { +#if PREFER_OPENSSL + return EVP_DigestUpdate(ctx, &v, sizeof(v)); +#else + gcry_md_write(ctx, &v, sizeof(v)); + return 0; +#endif } -static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { +static int md_add_uint16(hash_context_t ctx, uint16_t v) { v = htobe16(v); - gcry_md_write(md, &v, sizeof(v)); +#if PREFER_OPENSSL + return EVP_DigestUpdate(ctx, &v, sizeof(v)); +#else + gcry_md_write(ctx, &v, sizeof(v)); + return 0; +#endif } static void fwrite_uint8(FILE *fp, uint8_t v) { @@ -565,36 +740,32 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { return realtime < inception || realtime > expiration; } -static int algorithm_to_gcrypt_md(uint8_t algorithm) { +static hash_md_t algorithm_to_implementation_id(uint8_t algorithm) { - /* Translates a DNSSEC signature algorithm into a gcrypt - * digest identifier. + /* Translates a DNSSEC signature algorithm into an openssl/gcrypt digest identifier. * - * Note that we implement all algorithms listed as "Must - * implement" and "Recommended to Implement" in RFC6944. We - * don't implement any algorithms that are listed as - * "Optional" or "Must Not Implement". Specifically, we do not - * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and - * GOST-ECC. */ + * Note that we implement all algorithms listed as "Must implement" and "Recommended to Implement" in + * RFC6944. We don't implement any algorithms that are listed as "Optional" or "Must Not Implement". + * Specifically, we do not implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and GOST-ECC. */ switch (algorithm) { case DNSSEC_ALGORITHM_RSASHA1: case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return GCRY_MD_SHA1; + return OPENSSL_OR_GCRYPT(EVP_sha1(), GCRY_MD_SHA1); case DNSSEC_ALGORITHM_RSASHA256: case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return GCRY_MD_SHA256; + return OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256); case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return GCRY_MD_SHA384; + return OPENSSL_OR_GCRYPT(EVP_sha384(), GCRY_MD_SHA384); case DNSSEC_ALGORITHM_RSASHA512: - return GCRY_MD_SHA512; + return OPENSSL_OR_GCRYPT(EVP_sha512(), GCRY_MD_SHA512); default: - return -EOPNOTSUPP; + return OPENSSL_OR_GCRYPT(NULL, -EOPNOTSUPP); } } @@ -625,6 +796,183 @@ static void dnssec_fix_rrset_ttl( rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; } +static int dnssec_rrset_serialize_sig( + DnsResourceRecord *rrsig, + const char *source, + DnsResourceRecord **list, + size_t list_len, + bool wildcard, + char **ret_sig_data, + size_t *ret_sig_size) { + + _cleanup_free_ char *sig_data = NULL; + size_t sig_size = 0; + _cleanup_fclose_ FILE *f = NULL; + uint8_t wire_format_name[DNS_WIRE_FORMAT_HOSTNAME_MAX]; + DnsResourceRecord *rr; + int r; + + assert(rrsig); + assert(source); + assert(list || list_len == 0); + assert(ret_sig_data); + assert(ret_sig_size); + + f = open_memstream_unlocked(&sig_data, &sig_size); + if (!f) + return -ENOMEM; + + 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) + return 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); + if (r < 0) + return r; + + for (size_t k = 0; k < list_len; k++) { + size_t l; + + rr = list[k]; + + /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ + if (wildcard) + fwrite((uint8_t[]) { 1, '*'}, sizeof(uint8_t), 2, f); + fwrite(wire_format_name, 1, r, f); + + 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); + + fwrite_uint16(f, (uint16_t) l); + fwrite(DNS_RESOURCE_RECORD_RDATA(rr), 1, l, f); + } + + r = fflush_and_check(f); + f = safe_fclose(f); /* sig_data may be reallocated when f is closed. */ + if (r < 0) + return r; + + *ret_sig_data = TAKE_PTR(sig_data); + *ret_sig_size = sig_size; + return 0; +} + +static int dnssec_rrset_verify_sig( + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + const char *sig_data, + size_t sig_size) { + + assert(rrsig); + assert(dnskey); + assert(sig_data); + assert(sig_size > 0); + + hash_md_t md_algorithm; + +#if PREFER_OPENSSL + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_size; +#else + _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; + void *hash; + size_t hash_size; + + initialize_libgcrypt(false); +#endif + + switch (rrsig->rrsig.algorithm) { + case DNSSEC_ALGORITHM_ED25519: +#if PREFER_OPENSSL || GCRYPT_VERSION_NUMBER >= 0x010600 + return dnssec_eddsa_verify( + rrsig->rrsig.algorithm, + sig_data, sig_size, + rrsig, + dnskey); +#endif + case DNSSEC_ALGORITHM_ED448: + return -EOPNOTSUPP; + default: + /* OK, the RRs are now in canonical order. Let's calculate the digest */ + md_algorithm = algorithm_to_implementation_id(rrsig->rrsig.algorithm); +#if PREFER_OPENSSL + if (!md_algorithm) + return -EOPNOTSUPP; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + + if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + return -EIO; + + if (EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) + return -EIO; + + if (EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) + return -EIO; + + assert(hash_size > 0); + +#else + if (md_algorithm < 0) + return md_algorithm; + + gcry_error_t err = gcry_md_open(&md, md_algorithm, 0); + if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) + return -EIO; + + 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) + return -EIO; +#endif + } + + switch (rrsig->rrsig.algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_RSASHA512: + return dnssec_rsa_verify( + OPENSSL_OR_GCRYPT(md_algorithm, gcry_md_algo_name(md_algorithm)), + hash, hash_size, + rrsig, + dnskey); + + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + return dnssec_ecdsa_verify( + OPENSSL_OR_GCRYPT(md_algorithm, gcry_md_algo_name(md_algorithm)), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + + default: + assert_not_reached(); + } +} + int dnssec_verify_rrset( DnsAnswer *a, const DnsResourceKey *key, @@ -633,18 +981,12 @@ int dnssec_verify_rrset( usec_t realtime, DnssecResult *result) { - uint8_t wire_format_name[DNS_WIRE_FORMAT_HOSTNAME_MAX]; DnsResourceRecord **list, *rr; const char *source, *name; - _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; - int r, md_algorithm; - size_t n = 0; - size_t sig_size = 0; + size_t n = 0, sig_size; _cleanup_free_ char *sig_data = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t hash_size; - void *hash; bool wildcard; + int r; assert(key); assert(rrsig); @@ -746,123 +1088,15 @@ int dnssec_verify_rrset( /* Bring the RRs into canonical order */ typesafe_qsort(list, n, rr_compare); - f = open_memstream_unlocked(&sig_data, &sig_size); - if (!f) - return -ENOMEM; - - 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) - return 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); + r = dnssec_rrset_serialize_sig(rrsig, source, list, n, wildcard, + &sig_data, &sig_size); if (r < 0) return r; - for (size_t k = 0; k < n; k++) { - size_t l; - - rr = list[k]; - - /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ - if (wildcard) - fwrite((uint8_t[]) { 1, '*'}, sizeof(uint8_t), 2, f); - fwrite(wire_format_name, 1, r, f); - - 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); - - fwrite_uint16(f, (uint16_t) l); - fwrite(DNS_RESOURCE_RECORD_RDATA(rr), 1, l, f); - } - - 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: + r = dnssec_rrset_verify_sig(rrsig, dnskey, sig_data, sig_size); + if (r == -EOPNOTSUPP) { *result = DNSSEC_UNSUPPORTED_ALGORITHM; return 0; - default: { - gcry_error_t err; - - /* 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; - return 0; - } - if (md_algorithm < 0) - return md_algorithm; - - err = gcry_md_open(&md, md_algorithm, 0); - if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) - return -EIO; - - 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) - return -EIO; - } - } - - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_RSASHA512: - r = dnssec_rsa_verify( - gcry_md_algo_name(md_algorithm), - hash, hash_size, - rrsig, - dnskey); - break; - - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - r = dnssec_ecdsa_verify( - gcry_md_algo_name(md_algorithm), - rrsig->rrsig.algorithm, - hash, hash_size, - 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) return r; @@ -1067,33 +1301,29 @@ int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { return 0; } -static int digest_to_gcrypt_md(uint8_t algorithm) { +static hash_md_t digest_to_hash_md(uint8_t algorithm) { - /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ + /* Translates a DNSSEC digest algorithm into an openssl/gcrypt digest identifier */ switch (algorithm) { case DNSSEC_DIGEST_SHA1: - return GCRY_MD_SHA1; + return OPENSSL_OR_GCRYPT(EVP_sha1(), GCRY_MD_SHA1); case DNSSEC_DIGEST_SHA256: - return GCRY_MD_SHA256; + return OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256); case DNSSEC_DIGEST_SHA384: - return GCRY_MD_SHA384; + return OPENSSL_OR_GCRYPT(EVP_sha384(), GCRY_MD_SHA384); default: - return -EOPNOTSUPP; + return OPENSSL_OR_GCRYPT(NULL, -EOPNOTSUPP); } } int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { uint8_t wire_format[DNS_WIRE_FORMAT_HOSTNAME_MAX]; - _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; - gcry_error_t err; - size_t hash_size; - int md_algorithm, r; - void *result; + int r; assert(dnskey); assert(ds); @@ -1116,23 +1346,65 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) return 0; - initialize_libgcrypt(false); + r = dns_name_to_wire_format(dns_resource_key_name(dnskey->key), wire_format, sizeof wire_format, true); + if (r < 0) + return r; - md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); - if (md_algorithm < 0) - return md_algorithm; + hash_md_t md_algorithm = digest_to_hash_md(ds->ds.digest_type); + +#if PREFER_OPENSSL + if (!md_algorithm) + return -EOPNOTSUPP; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; + uint8_t result[EVP_MAX_MD_SIZE]; - hash_size = gcry_md_get_algo_dlen(md_algorithm); + unsigned hash_size = EVP_MD_size(md_algorithm); assert(hash_size > 0); if (ds->ds.digest_size != hash_size) return 0; - r = dns_name_to_wire_format(dns_resource_key_name(dnskey->key), wire_format, sizeof(wire_format), true); - if (r < 0) + ctx = EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + + if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + return -EIO; + + if (EVP_DigestUpdate(ctx, wire_format, r) <= 0) + return -EIO; + + if (mask_revoke) + md_add_uint16(ctx, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(ctx, dnskey->dnskey.flags); + + r = md_add_uint8(ctx, dnskey->dnskey.protocol); + if (r <= 0) + return r; + r = md_add_uint8(ctx, dnskey->dnskey.algorithm); + if (r <= 0) return r; + if (EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) + return -EIO; + + if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return -EIO; + +#else + if (md_algorithm < 0) + return -EOPNOTSUPP; + + _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; + + size_t hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + if (ds->ds.digest_size != hash_size) + return 0; - err = gcry_md_open(&md, md_algorithm, 0); + gcry_error_t err = gcry_md_open(&md, md_algorithm, 0); if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) return -EIO; @@ -1145,9 +1417,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, md_add_uint8(md, dnskey->dnskey.algorithm); gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); - result = gcry_md_read(md, 0); + void *result = gcry_md_read(md, 0); if (!result) return -EIO; +#endif return memcmp(result, ds->ds.digest, ds->ds.digest_size) == 0; } @@ -1190,27 +1463,22 @@ int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *vali return 0; } -static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { +static hash_md_t nsec3_hash_to_hash_md(uint8_t algorithm) { - /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ + /* Translates a DNSSEC NSEC3 hash algorithm into an openssl/gcrypt digest identifier */ switch (algorithm) { case NSEC3_ALGORITHM_SHA1: - return GCRY_MD_SHA1; + return OPENSSL_OR_GCRYPT(EVP_sha1(), GCRY_MD_SHA1); default: - return -EOPNOTSUPP; + return OPENSSL_OR_GCRYPT(NULL, -EOPNOTSUPP); } } int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { uint8_t wire_format[DNS_WIRE_FORMAT_HOSTNAME_MAX]; - _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; - gcry_error_t err; - size_t hash_size; - int algorithm; - void *result; int r; assert(nsec3); @@ -1225,13 +1493,55 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { "Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); - algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); + hash_md_t algorithm = nsec3_hash_to_hash_md(nsec3->nsec3.algorithm); +#if PREFER_OPENSSL + if (!algorithm) + return -EOPNOTSUPP; + + size_t hash_size = EVP_MD_size(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + + if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + return -EIO; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + if (EVP_DigestUpdate(ctx, wire_format, r) <= 0) + return -EIO; + if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + return -EIO; + + uint8_t result[EVP_MAX_MD_SIZE]; + if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return -EIO; + + for (unsigned k = 0; k < nsec3->nsec3.iterations; k++) { + if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + return -EIO; + if (EVP_DigestUpdate(ctx, result, hash_size) <= 0) + return -EIO; + if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + return -EIO; + + if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return -EIO; + } +#else if (algorithm < 0) return algorithm; initialize_libgcrypt(false); - hash_size = gcry_md_get_algo_dlen(algorithm); + unsigned hash_size = gcry_md_get_algo_dlen(algorithm); assert(hash_size > 0); if (nsec3->nsec3.next_hashed_name_size != hash_size) @@ -1241,14 +1551,15 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { if (r < 0) return r; - err = gcry_md_open(&md, algorithm, 0); + _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; + gcry_error_t err = gcry_md_open(&md, algorithm, 0); if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) return -EIO; gcry_md_write(md, wire_format, r); gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - result = gcry_md_read(md, 0); + void *result = gcry_md_read(md, 0); if (!result) return -EIO; @@ -1264,6 +1575,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { if (!result) return -EIO; } +#endif memcpy(ret, result, hash_size); return (int) hash_size; @@ -1283,8 +1595,14 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { return 0; /* Ignore NSEC3 RRs whose algorithm we don't know */ - if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) +#if PREFER_OPENSSL + if (!nsec3_hash_to_hash_md(rr->nsec3.algorithm)) + return 0; +#else + if (nsec3_hash_to_hash_md(rr->nsec3.algorithm) < 0) return 0; +#endif + /* Ignore NSEC3 RRs with an excessive number of required iterations */ if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) return 0; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index a70ec17743..d45f87ff5d 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_GCRYPT -#include <gcrypt.h> +# include <gcrypt.h> #endif #include "alloc-util.h" @@ -776,7 +776,7 @@ int dns_packet_append_opt( static const uint8_t rfc6975[] = { 0, 5, /* OPTION_CODE: DAU */ -#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600 +#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600) 0, 7, /* LIST_LENGTH */ #else 0, 6, /* LIST_LENGTH */ @@ -787,7 +787,7 @@ int dns_packet_append_opt( DNSSEC_ALGORITHM_RSASHA512, DNSSEC_ALGORITHM_ECDSAP256SHA256, DNSSEC_ALGORITHM_ECDSAP384SHA384, -#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600 +#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600) DNSSEC_ALGORITHM_ED25519, #endif diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index dd219f297c..0013cd0b7f 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -414,9 +414,9 @@ void link_set_dnssec_mode(Link *l, DnssecMode mode) { assert(l); -#if ! HAVE_GCRYPT +#if !HAVE_OPENSSL_OR_GCRYPT if (IN_SET(mode, DNSSEC_YES, DNSSEC_ALLOW_DOWNGRADE)) - log_warning("DNSSEC option for the link cannot be enabled or set to allow-downgrade when systemd-resolved is built without gcrypt support. Turning off DNSSEC support."); + log_warning("DNSSEC option for the link cannot be enabled or set to allow-downgrade when systemd-resolved is built without a cryptographic library. Turning off DNSSEC support."); return; #endif diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index b0763694dc..3263095360 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -1,20 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <arpa/inet.h> -#if HAVE_GCRYPT -#include <gcrypt.h> -#endif #include <netinet/in.h> #include <sys/socket.h> +#if HAVE_GCRYPT +# include <gcrypt.h> +#endif + #include "alloc-util.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-rr.h" #include "string-util.h" #include "hexdecoct.h" -#if HAVE_GCRYPT - static void test_dnssec_verify_dns_key(void) { static const uint8_t ds1_fprint[] = { @@ -173,8 +172,8 @@ static void test_dnssec_verify_rfc8080_ed25519_example1(void) { assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, - rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); -#if GCRYPT_VERSION_NUMBER >= 0x010600 + rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); +#if PREFER_OPENSSL || GCRYPT_VERSION_NUMBER >= 0x010600 assert_se(result == DNSSEC_VALIDATED); #else assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM); @@ -265,13 +264,196 @@ static void test_dnssec_verify_rfc8080_ed25519_example2(void) { assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, - rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); -#if GCRYPT_VERSION_NUMBER >= 0x010600 + rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); +#if PREFER_OPENSSL || GCRYPT_VERSION_NUMBER >= 0x010600 assert_se(result == DNSSEC_VALIDATED); #else assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM); #endif } + +static void test_dnssec_verify_rfc6605_example1(void) { + static const uint8_t signature_blob[] = { + 0xab, 0x1e, 0xb0, 0x2d, 0x8a, 0xa6, 0x87, 0xe9, 0x7d, 0xa0, 0x22, 0x93, 0x37, 0xaa, 0x88, 0x73, + 0xe6, 0xf0, 0xeb, 0x26, 0xbe, 0x28, 0x9f, 0x28, 0x33, 0x3d, 0x18, 0x3f, 0x5d, 0x3b, 0x7a, 0x95, + 0xc0, 0xc8, 0x69, 0xad, 0xfb, 0x74, 0x8d, 0xae, 0xe3, 0xc5, 0x28, 0x6e, 0xed, 0x66, 0x82, 0xc1, + 0x2e, 0x55, 0x33, 0x18, 0x6b, 0xac, 0xed, 0x9c, 0x26, 0xc1, 0x67, 0xa9, 0xeb, 0xae, 0x95, 0x0b, + }; + + static const uint8_t ds_fprint[] = { + 0x6f, 0x87, 0x3c, 0x73, 0x57, 0xde, 0xd9, 0xee, 0xf8, 0xef, 0xbd, 0x76, 0xed, 0xbd, 0xbb, 0xd7, + 0x5e, 0x7a, 0xe7, 0xa6, 0x9d, 0xeb, 0x6e, 0x7a, 0x7f, 0x8d, 0xb8, 0xeb, 0x6e, 0x5b, 0x7f, 0x97, + 0x35, 0x7b, 0x6e, 0xfb, 0xd1, 0xc7, 0xba, 0x77, 0xa7, 0xb7, 0xed, 0xd7, 0xfa, 0xd5, 0xdd, 0x7b, + }; + + static const uint8_t dnskey_blob[] = { + 0x1a, 0x88, 0xc8, 0x86, 0x15, 0xd4, 0x37, 0xfb, 0xb8, 0xbf, 0x9e, 0x19, 0x42, 0xa1, 0x92, 0x9f, + 0x28, 0x56, 0x27, 0x06, 0xae, 0x6c, 0x2b, 0xd3, 0x99, 0xe7, 0xb1, 0xbf, 0xb6, 0xd1, 0xe9, 0xe7, + 0x5b, 0x92, 0xb4, 0xaa, 0x42, 0x91, 0x7a, 0xe1, 0xc6, 0x1b, 0x70, 0x1e, 0xf0, 0x35, 0xc3, 0xfe, + 0x7b, 0xe3, 0x00, 0x9c, 0xba, 0xfe, 0x5a, 0x2f, 0x71, 0x31, 0x6c, 0x90, 0x2d, 0xcf, 0x0d, 0x00, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *a = 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.net."); + assert_se(dnskey); + + dnskey->dnskey.flags = 257; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ECDSAP256SHA256; + 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.net."); + assert_se(ds); + + ds->ds.key_tag = 55648; + ds->ds.algorithm = DNSSEC_ALGORITHM_ECDSAP256SHA256; + 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))); + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "www.example.net"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("192.0.2.1"); + + log_info("A: %s", strna(dns_resource_record_to_string(a))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "www.example.net."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ECDSAP256SHA256; + rrsig->rrsig.labels = 3; + rrsig->rrsig.expiration = 1284026679; + rrsig->rrsig.inception = 1281607479; + rrsig->rrsig.key_tag = 55648; + rrsig->rrsig.original_ttl = 3600; + rrsig->rrsig.signer = strdup("example.net."); + 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(a->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, a, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); + + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, + rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_verify_rfc6605_example2(void) { + static const uint8_t signature_blob[] = { + 0xfc, 0xbe, 0x61, 0x0c, 0xa2, 0x2f, 0x18, 0x3c, 0x88, 0xd5, 0xf7, 0x00, 0x45, 0x7d, 0xf3, 0xeb, + 0x9a, 0xab, 0x98, 0xfb, 0x15, 0xcf, 0xbd, 0xd0, 0x0f, 0x53, 0x2b, 0xe4, 0x21, 0x2a, 0x3a, 0x22, + 0xcf, 0xf7, 0x98, 0x71, 0x42, 0x8b, 0xae, 0xae, 0x81, 0x82, 0x79, 0x93, 0xaf, 0xcc, 0x56, 0xb1, + 0xb1, 0x3f, 0x06, 0x96, 0xbe, 0xf8, 0x85, 0xb6, 0xaf, 0x44, 0xa6, 0xb2, 0x24, 0xdb, 0xb2, 0x74, + 0x2b, 0xb3, 0x59, 0x34, 0x92, 0x3d, 0xdc, 0xfb, 0xc2, 0x7a, 0x97, 0x2f, 0x96, 0xdd, 0x70, 0x9c, + 0xee, 0xb1, 0xd9, 0xc8, 0xd1, 0x14, 0x8c, 0x44, 0xec, 0x71, 0xc0, 0x68, 0xa9, 0x59, 0xc2, 0x66, + + }; + + static const uint8_t ds_fprint[] = { + 0xef, 0x67, 0x7b, 0x6f, 0xad, 0xbd, 0xef, 0xa7, 0x1e, 0xd3, 0xae, 0x37, 0xf1, 0xef, 0x5c, 0xd1, + 0xb7, 0xf7, 0xd7, 0xdd, 0x35, 0xdd, 0xc7, 0xfc, 0xd3, 0x57, 0xf4, 0xf5, 0xe7, 0x1c, 0xf3, 0x86, + 0xfc, 0x77, 0xb7, 0xbd, 0xe3, 0xde, 0x5f, 0xdb, 0xb7, 0xb7, 0xd3, 0x97, 0x3a, 0x6b, 0xd6, 0xf4, + 0xe7, 0xad, 0xda, 0xf5, 0xbe, 0x5f, 0xe1, 0xdd, 0xbc, 0xf3, 0x8d, 0x39, 0x73, 0x7d, 0x34, 0xf1, + 0xaf, 0x78, 0xe9, 0xd7, 0xfd, 0xf3, 0x77, 0x7a, + }; + + static const uint8_t dnskey_blob[] = { + 0xc4, 0xa6, 0x1a, 0x36, 0x15, 0x9d, 0x18, 0xe7, 0xc9, 0xfa, 0x73, 0xeb, 0x2f, 0xcf, 0xda, 0xae, + 0x4c, 0x1f, 0xd8, 0x46, 0x37, 0x30, 0x32, 0x7e, 0x48, 0x4a, 0xca, 0x8a, 0xf0, 0x55, 0x4a, 0xe9, + 0xb5, 0xc3, 0xf7, 0xa0, 0xb1, 0x7b, 0xd2, 0x00, 0x3b, 0x4d, 0x26, 0x1c, 0x9e, 0x9b, 0x94, 0x42, + 0x3a, 0x98, 0x10, 0xe8, 0xaf, 0x17, 0xd4, 0x34, 0x52, 0x12, 0x4a, 0xdb, 0x61, 0x0f, 0x8e, 0x07, + 0xeb, 0xfc, 0xfe, 0xe5, 0xf8, 0xe4, 0xd0, 0x70, 0x63, 0xca, 0xe9, 0xeb, 0x91, 0x7a, 0x1a, 0x5b, + 0xab, 0xf0, 0x8f, 0xe6, 0x95, 0x53, 0x60, 0x17, 0xa5, 0xbf, 0xa9, 0x32, 0x37, 0xee, 0x6e, 0x34, + }; + + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *a = 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.net."); + assert_se(dnskey); + + dnskey->dnskey.flags = 257; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ECDSAP384SHA384; + 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.net."); + assert_se(ds); + + ds->ds.key_tag = 10771; + ds->ds.algorithm = DNSSEC_ALGORITHM_ECDSAP384SHA384; + ds->ds.digest_type = DNSSEC_DIGEST_SHA384; + 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))); + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "www.example.net"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("192.0.2.1"); + + log_info("A: %s", strna(dns_resource_record_to_string(a))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "www.example.net."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ECDSAP384SHA384; + rrsig->rrsig.labels = 3; + rrsig->rrsig.expiration = 1284027625; + rrsig->rrsig.inception = 1281608425; + rrsig->rrsig.key_tag = 10771; + rrsig->rrsig.original_ttl = 3600; + rrsig->rrsig.signer = strdup("example.net."); + 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(a->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, a, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); + + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, + rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + static void test_dnssec_verify_rrset(void) { static const uint8_t signature_blob[] = { @@ -605,19 +787,16 @@ static void test_dnssec_nsec3_hash(void) { assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); } -#endif - 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_rfc6605_example1(); + test_dnssec_verify_rfc6605_example2(); test_dnssec_verify_rrset(); test_dnssec_verify_rrset2(); test_dnssec_verify_rrset3(); test_dnssec_nsec3_hash(); -#endif return 0; } diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index bd728e6c7c..fdfe465594 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -2,8 +2,46 @@ #include "openssl-util.h" #include "alloc-util.h" +#include "hexdecoct.h" #if HAVE_OPENSSL +int openssl_hash(const EVP_MD *alg, + const void *msg, + size_t msg_len, + uint8_t *ret_hash, + size_t *ret_hash_len) { + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; + unsigned len; + int r; + + ctx = EVP_MD_CTX_new(); + if (!ctx) + /* This function just calls OPENSSL_zalloc, so failure + * here is almost certainly a failed allocation. */ + return -ENOMEM; + + /* The documentation claims EVP_DigestInit behaves just like + * EVP_DigestInit_ex if passed NULL, except it also calls + * EVP_MD_CTX_reset, which deinitializes the context. */ + r = EVP_DigestInit_ex(ctx, alg, NULL); + if (r == 0) + return -EIO; + + r = EVP_DigestUpdate(ctx, msg, msg_len); + if (r == 0) + return -EIO; + + r = EVP_DigestFinal_ex(ctx, ret_hash, &len); + if (r == 0) + return -EIO; + + if (ret_hash_len) + *ret_hash_len = len; + + return 0; +} + int rsa_encrypt_bytes( EVP_PKEY *pkey, const void *decrypted_key, @@ -70,4 +108,33 @@ int rsa_pkey_to_suitable_key_size( *ret_suitable_key_size = suitable_key_size; return 0; } + +# if PREFER_OPENSSL +int string_hashsum( + const char *s, + size_t len, + const EVP_MD *md_algorithm, + char **ret) { + + uint8_t hash[EVP_MAX_MD_SIZE]; + size_t hash_size; + char *enc; + int r; + + hash_size = EVP_MD_size(md_algorithm); + assert(hash_size > 0); + + r = openssl_hash(md_algorithm, s, len, hash, NULL); + if (r < 0) + return r; + + enc = hexmem(hash, hash_size); + if (!enc) + return -ENOMEM; + + *ret = enc; + return 0; + +} +# endif #endif diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 5840d57d16..d5b1855987 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -5,6 +5,8 @@ #if HAVE_OPENSSL # include <openssl/bio.h> +# include <openssl/bn.h> +# include <openssl/err.h> # include <openssl/evp.h> # include <openssl/pkcs7.h> # include <openssl/ssl.h> @@ -13,7 +15,15 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_POINT*, EC_POINT_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_GROUP*, EC_GROUP_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIGNUM*, BN_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BN_CTX*, BN_CTX_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); @@ -26,8 +36,41 @@ static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { sk_X509_pop_free(*sk, X509_free); } +int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *ret_hash, size_t *ret_hash_len); + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); +#endif + +#if PREFER_OPENSSL +/* The openssl definition */ +typedef const EVP_MD* hash_md_t; +typedef const EVP_MD* hash_algorithm_t; +typedef int elliptic_curve_t; +typedef EVP_MD_CTX* hash_context_t; +# define OPENSSL_OR_GCRYPT(a, b) (a) +#elif HAVE_GCRYPT + +# include <gcrypt.h> + +/* The gcrypt definition */ +typedef int hash_md_t; +typedef const char* hash_algorithm_t; +typedef const char* elliptic_curve_t; +typedef gcry_md_hd_t hash_context_t; +# define OPENSSL_OR_GCRYPT(a, b) (b) +#endif + +#if PREFER_OPENSSL +int string_hashsum(const char *s, size_t len, hash_algorithm_t md_algorithm, char **ret); + +static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, EVP_sha224(), ret); +} + +static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, EVP_sha256(), ret); +} #endif diff --git a/src/test/meson.build b/src/test/meson.build index de0b8d7fd2..71d2422caf 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -594,8 +594,10 @@ tests += [ [['src/test/test-id128.c']], - [['src/test/test-gcrypt-util.c'], - [], [], [], 'HAVE_GCRYPT'], + [['src/test/test-cryptolib.c'], + [libshared], + [lib_openssl_or_gcrypt], + [], 'HAVE_OPENSSL_OR_GCRYPT'], [['src/test/test-nss-hosts.c', 'src/test/nss-test-util.c', diff --git a/src/test/test-gcrypt-util.c b/src/test/test-cryptolib.c index 8eb63cd385..ef39bda653 100644 --- a/src/test/test-gcrypt-util.c +++ b/src/test/test-cryptolib.c @@ -3,25 +3,34 @@ #include "alloc-util.h" #include "gcrypt-util.h" #include "macro.h" +#include "openssl-util.h" #include "string-util.h" #include "tests.h" TEST(string_hashsum) { _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; - assert_se(string_hashsum("asdf", 4, GCRY_MD_SHA224, &out1) == 0); + assert_se(string_hashsum("asdf", 4, + OPENSSL_OR_GCRYPT(EVP_sha224(), GCRY_MD_SHA224), + &out1) == 0); /* echo -n 'asdf' | sha224sum - */ assert_se(streq(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a")); - assert_se(string_hashsum("asdf", 4, GCRY_MD_SHA256, &out2) == 0); + assert_se(string_hashsum("asdf", 4, + OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256), + &out2) == 0); /* echo -n 'asdf' | sha256sum - */ assert_se(streq(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); - assert_se(string_hashsum("", 0, GCRY_MD_SHA224, &out3) == 0); + assert_se(string_hashsum("", 0, + OPENSSL_OR_GCRYPT(EVP_sha224(), GCRY_MD_SHA224), + &out3) == 0); /* echo -n '' | sha224sum - */ assert_se(streq(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f")); - assert_se(string_hashsum("", 0, GCRY_MD_SHA256, &out4) == 0); + assert_se(string_hashsum("", 0, + OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256), + &out4) == 0); /* echo -n '' | sha256sum - */ assert_se(streq(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); } |