diff --git a/src/pubkey-pinning.c b/src/pubkey-pinning.c index f2935dbb..2f685ad3 100644 --- a/src/pubkey-pinning.c +++ b/src/pubkey-pinning.c @@ -45,10 +45,13 @@ * given a such a pinset, we should be able to validate a chain * properly according to section 2.6 of RFC 7469. */ +#include "config.h" +#include "debug.h" #include #include #include #include +#include #include #include "context.h" @@ -358,4 +361,84 @@ _getdns_associate_upstream_with_SSL(SSL *ssl, * might call ERR_get_error (see CRYPTO_set_ex_data(3ssl))*/ } +getdns_return_t +_getdns_verify_pinset_match(const sha256_pin_t *pinset, + X509_STORE_CTX *store) +{ + getdns_return_t ret = GETDNS_RETURN_GENERIC_ERROR; + X509 *x; + int i, len; + unsigned char raw[4096]; + unsigned char *next = raw; + unsigned char buf[sizeof(pinset->pin)]; + const sha256_pin_t *p; + + if (pinset == NULL || store == NULL) + return GETDNS_RETURN_GENERIC_ERROR; + + /* start at the base of the chain (the end-entity cert) and + * make sure that some valid element of the chain does match + * the pinset. */ + + /* Testing with OpenSSL 1.0.1e-1 on debian indicates that + * store->untrusted holds the chain offered by the server in + * the order that the server offers it. If the server offers + * bogus certificates (that is, matching and valid certs that + * belong to private keys that the server does not control), + * the the verification will succeed (including this pinset + * check), but the handshake will fail outside of this + * verification. */ + + /* TODO: how do we handle raw public keys? */ + + for (i = 0; i < sk_X509_num(store->untrusted); i++) { + if (i > 0) { + /* TODO: how do we ensure that the certificates in + * each stage appropriately sign the previous one? + * for now, to be safe, we only examine the end-entity + * cert: */ + return GETDNS_RETURN_GENERIC_ERROR; + } + + x = sk_X509_value(store->untrusted, i); + if (x->cert_info == NULL) + continue; +#if defined(STUB_DEBUG) && STUB_DEBUG + DEBUG_STUB("--- %s: name of cert %d:\n", __FUNCTION__, i); + if (x->cert_info->subject != NULL) + X509_NAME_print_ex_fp(stderr, x->cert_info->subject, 4, XN_FLAG_ONELINE); + fprintf(stderr, "\n"); +#endif + if (x->cert_info->key == NULL) + continue; + + /* digest the cert with sha256 */ + len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x), NULL); + if (len > sizeof(raw)) { + DEBUG_STUB("--- %s: pubkey %d is larger than %ld octets\n", + __FUNCTION__, i, sizeof(raw)); + continue; + } + i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x), &next); + if (next - raw != len) { + DEBUG_STUB("--- %s: pubkey %d claimed it needed %d octets, really needed %ld\n", + __FUNCTION__, i, len, next - raw); + continue; + } + SHA256(raw, len, buf); + + /* compare it */ + for (p = pinset; p; p = p->next) + if (0 == memcmp(buf, p->pin, sizeof(p->pin))) { + DEBUG_STUB("--- %s: pubkey %d matched pin %p (%ld)!\n", + __FUNCTION__, i, p, sizeof(p->pin)); + return GETDNS_RETURN_GOOD; + } else + DEBUG_STUB("--- %s: pubkey %d did not match pin %p!\n", + __FUNCTION__, i, p); + } + + return ret; +} + /* pubkey-pinning.c */ diff --git a/src/pubkey-pinning.h b/src/pubkey-pinning.h index c5585567..894ccf00 100644 --- a/src/pubkey-pinning.h +++ b/src/pubkey-pinning.h @@ -60,6 +60,9 @@ getdns_return_t _getdns_associate_upstream_with_SSL(SSL *ssl, getdns_upstream *upstream); - +getdns_return_t +_getdns_verify_pinset_match(const sha256_pin_t *pinset, + X509_STORE_CTX *store); + #endif /* pubkey-pinning.h */ diff --git a/src/stub.c b/src/stub.c index 44c0e58e..98c12efb 100644 --- a/src/stub.c +++ b/src/stub.c @@ -844,6 +844,7 @@ tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { int err; getdns_upstream *upstream; + getdns_return_t pinset_ret = GETDNS_RETURN_GOOD; err = X509_STORE_CTX_get_error(ctx); upstream = _getdns_upstream_from_x509_store(ctx); @@ -855,6 +856,15 @@ tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) if (upstream && upstream->tls_fallback_ok && err == X509_V_ERR_HOSTNAME_MISMATCH) DEBUG_STUB("--- %s, PROCEEDING WITHOUT HOSTNAME VALIDATION!!\n", __FUNCTION__); #endif + if (upstream && upstream->tls_pubkey_pinset) + pinset_ret = _getdns_verify_pinset_match(upstream->tls_pubkey_pinset, ctx); + + if (pinset_ret != GETDNS_RETURN_GOOD) { + DEBUG_STUB("--- %s, PINSET VALIDATION FAILURE!!\n", __FUNCTION__); + preverify_ok = 0; + if (upstream->tls_fallback_ok) + DEBUG_STUB("--- %s, PROCEEDING WITHOUT PINSET VALIDATION!!\n", __FUNCTION__); + } return (upstream && upstream->tls_fallback_ok) ? 1 : preverify_ok; }