]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
crypto verify routines.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 15 Aug 2007 08:39:00 +0000 (08:39 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 15 Aug 2007 08:39:00 +0000 (08:39 +0000)
git-svn-id: file:///svn/unbound/trunk@520 be551aaa-1e26-0410-a405-d3ace91eadb9

configure.ac
daemon/daemon.c
doc/Changelog
validator/val_sigcrypt.c

index a19c7ca9d74712f92b04030e6e2e0a3ac5fa3699..16d89fe8bf9626515f645680ff0ddcf736408f1a 100644 (file)
@@ -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 <getopt.h>
 #endif
 
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+
 #ifdef HAVE_ATTR_FORMAT
 #  define ATTR_FORMAT(archetype, string_index, first_to_check) \
     __attribute__ ((format (archetype, string_index, first_to_check)))
index 92672fc01e9278440abaeb2bceed8990c0ad8571..3ed1f78a165d887eca4cbf39b15781c1f3830095 100644 (file)
@@ -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();
 }
index d0604a39a41d5335875838839e27a07cd940192d..d97837c02832e2b6f566cd9ec7ed5cc0df9a1855 100644 (file)
@@ -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.
index 9de8ed358a5e2a0adb28a76146472229cca4c3dd..1b6705e2d3f9d442cf7fb79e840e075d946eb0f7 100644 (file)
@@ -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);
 }