Check that the pinset matches if it is configured

if the upstream is configured to allow fallback, this will not be a
fatal error, but it will still be checked.

Future work:

 * verify any certs higher in the chain than the end-entity cert
 * deal with raw public keys
 * in the fallback case, report to the user whether the pinset match failed
This commit is contained in:
Daniel Kahn Gillmor 2015-12-21 21:23:13 -05:00 committed by Sara Dickinson
parent d09675539e
commit a9eb9ccca9
3 changed files with 97 additions and 1 deletions

View File

@ -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 <getdns/getdns.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <string.h>
#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 */

View File

@ -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 */

View File

@ -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;
}