From f06b1e8ef38c71ecc84b8e08f0ba5f5cb7ee6633 Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Wed, 15 Aug 2007 08:39:00 +0000 Subject: [PATCH] crypto verify routines. git-svn-id: file:///svn/unbound/trunk@520 be551aaa-1e26-0410-a405-d3ace91eadb9 --- configure.ac | 5 + daemon/daemon.c | 2 + doc/Changelog | 3 + validator/val_sigcrypt.c | 258 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 260 insertions(+), 8 deletions(-) diff --git a/configure.ac b/configure.ac index a19c7ca9d..16d89fe8b 100644 --- a/configure.ac +++ b/configure.ac @@ -392,6 +392,7 @@ AC_ARG_WITH(ssl, AC_HELP_STRING([--with-ssl=pathname], AC_SUBST(RUNTIME_PATH) fi AC_CHECK_HEADERS([openssl/ssl.h],,, [AC_INCLUDES_DEFAULT]) +AC_CHECK_HEADERS([openssl/err.h],,, [AC_INCLUDES_DEFAULT]) # check for thread library. AC_ARG_WITH(pthreads, AC_HELP_STRING([--with-pthreads], @@ -568,6 +569,10 @@ AH_BOTTOM([ #include #endif +#ifdef HAVE_OPENSSL_ERR_H +#include +#endif + #ifdef HAVE_ATTR_FORMAT # define ATTR_FORMAT(archetype, string_index, first_to_check) \ __attribute__ ((format (archetype, string_index, first_to_check))) diff --git a/daemon/daemon.c b/daemon/daemon.c index 92672fc01..3ed1f78a1 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -125,6 +125,7 @@ daemon_init() return NULL; signal_handling_record(); checklock_start(); + ERR_load_crypto_strings(); daemon->need_to_exit = 0; daemon->num_modules = 0; if(!(daemon->env = (struct module_env*)calloc(1, @@ -463,5 +464,6 @@ daemon_delete(struct daemon* daemon) free(daemon->pidfile); free(daemon->env); free(daemon); + ERR_free_strings(); checklock_stop(); } diff --git a/doc/Changelog b/doc/Changelog index d0604a39a..d97837c02 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +15 August 2007: Wouter + - crypto calls to verify signatures. + 14 August 2007: Wouter - default outgoing ports changed to avoid port 2049 by default. This port is widely blocked by firewalls. diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 9de8ed358..1b6705e2d 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -145,6 +145,23 @@ dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx) return (int)rdata[2+3]; } +/** get public key rdata field from a dnskey RR and do some checks */ +static void +dnskey_get_pubkey(struct ub_packed_rrset_key* k, size_t idx, + unsigned char** pk, size_t* pklen) +{ + uint8_t* rdata; + size_t len; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+5) { + *pk = NULL; + *pklen = 0; + return; + } + *pk = (unsigned char*)rdata+2+4; + *pklen = len-2-4; +} + int ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx) { @@ -1004,19 +1021,236 @@ check_dates(struct val_env* ve, uint8_t* expi_p, uint8_t* incep_p) } +/** + * Output a libcrypto openssl error to the logfile. + * @param str: string to add to it. + * @param e: the error to output, error number from ERR_get_error(). + */ +static void +log_crypto_error(const char* str, unsigned long e) +{ + char buf[128]; + /* or use ERR_error_string if ERR_error_string_n is not avail TODO */ + ERR_error_string_n(e, buf, sizeof(buf)); + /* buf now contains */ + /* error:[error code]:[library name]:[function name]:[reason string] */ + log_err("%s crypto %s", str, buf); +} + +/** + * Convert DSA RRsig sigblock to a DSA_SIG structure. + * @param sig: sigblock field of RRSIG + * @param siglen: length of sig. + * @return DSA_SIG or NULL + */ +static DSA_SIG* +dsa_rrsig_to_dsa_sig(unsigned char* sig, unsigned int siglen) +{ + uint8_t t; + BIGNUM *R, *S; + DSA_SIG *dsasig; + + /* extract the R and S field from the sig buffer */ + if(siglen < 1 + SHA_DIGEST_LENGTH*2) { + verbose(VERB_ALGO, "verify: short DSA RRSIG"); + return NULL; + } + t = sig[0]; + R = BN_new(); + if(!R) { + log_err("verify: alloc failure"); + return NULL; + } + S = BN_new(); + if(!S) { + BN_free(R); + log_err("verify: alloc failure"); + return NULL; + } + if(!BN_bin2bn(sig + 1, SHA_DIGEST_LENGTH, R)) { + log_err("verify: bignum failure"); + BN_free(R); + BN_free(S); + return NULL; + } + if(!BN_bin2bn(sig + 21, SHA_DIGEST_LENGTH, S)) { + log_err("verify: bignum failure"); + BN_free(R); + BN_free(S); + return NULL; + } + + dsasig = DSA_SIG_new(); + if(!dsasig) { + log_err("verify: alloc failure"); + BN_free(R); + BN_free(S); + return NULL; + } + dsasig->r = R; + dsasig->s = S; + return dsasig; +} + +/** + * Convert DSA signature to a DER signature. + * @param sig: signature, used to read, then replaced with malloced + * DER signature on success. + * @param siglen: read to get input len, then updated to new length. + * @return false on (alloc) error. + */ +static int +dsa_convert_to_der(unsigned char** sig, unsigned int* siglen) +{ + DSA_SIG* dsasig; + int res; + + dsasig = dsa_rrsig_to_dsa_sig(*sig, *siglen); + if(!dsasig) { + return 0; + } + *sig = NULL; /* needs libcrypto >= 0.9.7 */ + res = i2d_DSA_SIG(dsasig, sig); + if(res < 0) { + log_crypto_error("verify: bad dsa sig ", + ERR_get_error()); + DSA_SIG_free(dsasig); + return 0; + } + DSA_SIG_free(dsasig); + *siglen = (unsigned int)res; + return 1; +} + +/** + * Setup key and digest for verification. Adjust sig if necessary. + * + * @param algo: key algorithm + * @param evp_key: EVP PKEY public key to update. + * @param digest_type: digest type to use + * @param key: key to setup for. + * @param keylen: length of key. + * @param sig: sig to update if necessary. + * @param siglen: length of sig. + * @return false on failure. + */ +static int +setup_key_digest(int algo, EVP_PKEY* evp_key, const EVP_MD** digest_type, + unsigned char* key, size_t keylen, + unsigned char** sig, unsigned int* siglen) +{ + switch(algo) { + case LDNS_DSA: + case LDNS_DSA_NSEC3: + EVP_PKEY_assign_DSA(evp_key, + ldns_key_buf2dsa_raw(key, keylen)); + *digest_type = EVP_dss1(); + if(!dsa_convert_to_der(sig, siglen)) + return 0; + + break; + case LDNS_RSASHA1: + case LDNS_RSASHA1_NSEC3: + EVP_PKEY_assign_RSA(evp_key, + ldns_key_buf2rsa_raw(key, keylen)); + *digest_type = EVP_sha1(); + + break; + case LDNS_RSAMD5: + EVP_PKEY_assign_RSA(evp_key, + ldns_key_buf2rsa_raw(key, keylen)); + *digest_type = EVP_md5(); + + break; + default: + verbose(VERB_ALGO, "verify: unknown algorithm %d", + algo); + return 0; + } + return 1; +} + +/** + * Desetup what setup_key_digest setup. + * @param algo: key algorithm + * @param sig: signature. + */ +static void +desetup_key_digest(int algo, unsigned char* sig) +{ + switch(algo) { + case LDNS_DSA: + case LDNS_DSA_NSEC3: + /* free converted signature */ + free(sig); + break; + } +} + +/** + * Check a canonical sig+rrset and signature against a dnskey + * @param buf: buffer with data to verify, the first rrsig part and the + * canonicalized rrset. + * @param algo: DNSKEY algorithm. + * @param sigblock: signature rdata field from RRSIG + * @param sigblock_len: length of sigblock data. + * @param key: public key data from DNSKEY RR. + * @param keylen: length of keydata. + * @return secure if verification succeeded, bogus on crypto failure, + * unchecked on format errors and alloc failures. + */ +static enum sec_status +verify_canonrrset(ldns_buffer* buf, int algo, unsigned char* sigblock, + unsigned int sigblock_len, unsigned char* key, unsigned int keylen) +{ + const EVP_MD *digest_type; + EVP_MD_CTX ctx; + int res; + EVP_PKEY *evp_key = EVP_PKEY_new(); + if(!evp_key) { + log_err("verify: malloc failure in crypto"); + return sec_status_unchecked; + } + + if(!setup_key_digest(algo, evp_key, &digest_type, key, keylen, + &sigblock, &sigblock_len)) { + EVP_PKEY_free(evp_key); + return sec_status_bogus; + } + + /* do the signature cryptography work */ + EVP_MD_CTX_init(&ctx); + EVP_VerifyInit(&ctx, digest_type); + EVP_VerifyUpdate(&ctx, ldns_buffer_begin(buf), ldns_buffer_limit(buf)); + res = EVP_VerifyFinal(&ctx, sigblock, sigblock_len, evp_key); + EVP_MD_CTX_cleanup(&ctx); + EVP_PKEY_free(evp_key); + desetup_key_digest(algo, sigblock); + + if(res == 1) { + return sec_status_secure; + } else if(res == 0) { + return sec_status_bogus; + } + log_crypto_error("verify:", ERR_get_error()); + return sec_status_unchecked; +} + enum sec_status dnskey_verify_rrset_sig(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, size_t sig_idx) { - uint8_t* sig; /* rdata */ + uint8_t* sig; /* RRSIG rdata */ size_t siglen; size_t rrnum = rrset_get_count(rrset); - uint8_t* signer; + uint8_t* signer; /* rrsig signer name */ size_t signer_len; - uint8_t* sigblock; /* signature rdata field */ - size_t sigblock_len; - uint16_t ktag; + unsigned char* sigblock; /* signature rdata field */ + unsigned int sigblock_len; + uint16_t ktag; /* DNSKEY key tag */ + unsigned char* key; /* public key rdata field */ + unsigned int keylen; rrset_get_rdata(rrset, rrnum + sig_idx, &sig, &siglen); /* min length of rdatalen, fixed rrsig, root signer, 1 byte sig */ if(siglen < 2+20) { @@ -1036,12 +1270,12 @@ dnskey_verify_rrset_sig(struct module_env* env, struct val_env* ve, verbose(VERB_ALGO, "verify: malformed signer name"); return sec_status_bogus; /* signer name invalid */ } - sigblock = signer+signer_len; + sigblock = (unsigned char*)signer+signer_len; if(siglen < 2+18+signer_len+1) { verbose(VERB_ALGO, "verify: too short, no signature data"); return sec_status_bogus; /* sig rdf is < 1 byte */ } - sigblock_len = siglen - 2 - 18 - signer_len; + sigblock_len = (unsigned int)(siglen - 2 - 18 - signer_len); /* verify key dname == sig signer name */ if(query_dname_compare(signer, dnskey->rk.dname) != 0) { @@ -1086,6 +1320,14 @@ dnskey_verify_rrset_sig(struct module_env* env, struct val_env* ve, return sec_status_unchecked; } + /* check that dnskey is available */ + dnskey_get_pubkey(dnskey, dnskey_idx, &key, &keylen); + if(!key) { + verbose(VERB_ALGO, "verify: short DNSKEY RR"); + return sec_status_unchecked; + } + /* verify */ - return sec_status_unchecked; + return verify_canonrrset(env->scratch_buffer, (int)sig[2], + sigblock, sigblock_len, key, keylen); } -- 2.47.2