From 35b4969216322ca9baf03f009bc198f78c66b2a1 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 11 Dec 2018 18:03:00 +0000 Subject: [PATCH] Abstract out OpenSSL specific parts of getdns_pubkey_pin_create_from_string(). The only OpenSSL function is decoding Base64. --- src/gnutls/pubkey-pinning-internal.c | 9 +-- src/gnutls/tls.c | 110 +++++++++++++++++++++++++- src/openssl/pubkey-pinning-internal.c | 69 ++-------------- src/pubkey-pinning.c | 62 +++++++++++++++ src/pubkey-pinning.h | 4 +- 5 files changed, 180 insertions(+), 74 deletions(-) diff --git a/src/gnutls/pubkey-pinning-internal.c b/src/gnutls/pubkey-pinning-internal.c index 6568f52a..2ae97bf7 100644 --- a/src/gnutls/pubkey-pinning-internal.c +++ b/src/gnutls/pubkey-pinning-internal.c @@ -47,12 +47,7 @@ _getdns_associate_upstream_with_connection(_getdns_tls_connection *conn, return GETDNS_RETURN_GOOD; } -/** - ** Interfaces from getdns_extra.h. - **/ - -getdns_dict* -getdns_pubkey_pin_create_from_string(getdns_context* context, const char* str) +getdns_return_t _getdns_decode_base64(const char* str, uint8_t* res, size_t res_size) { - return NULL; + return GETDNS_RETURN_GENERIC_ERROR; } diff --git a/src/gnutls/tls.c b/src/gnutls/tls.c index c4d48bd1..33d0047a 100644 --- a/src/gnutls/tls.c +++ b/src/gnutls/tls.c @@ -485,12 +485,116 @@ getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* c getdns_return_t _getdns_tls_connection_certificate_verify(_getdns_tls_connection* conn, long* errnum, const char** errmsg) { - (void) errnum; - (void) errmsg; - if (!conn || !conn->tls) return GETDNS_RETURN_INVALID_PARAMETER; + /* Most of the internals of dane_verify_session_crt() */ + + const gnutls_datum_t* cert_list; + unsigned int cert_list_size = 0; + unsigned int type; + int ret; + const gnutls_datum_t* cl; + gnutls_datum_t* new_cert_list = NULL; + int clsize; + unsigned int verify; + + cert_list = gnutls_certificate_get_peers(conn->tls, &cert_list_size); + if (cert_list_size == 0) { + *errnum = 1; + *errmsg = "No peer certificate"; + return GETDNS_RETURN_GENERIC_ERROR; + } + cl = cert_list; + + type = gnutls_certificate_type_get(conn->tls); + + /* this list may be incomplete, try to get the self-signed CA if any */ + if (cert_list_size > 0) { + gnutls_x509_crt_t crt, ca; + gnutls_certificate_credentials_t sc; + + ret = gnutls_x509_crt_init(&crt); + if (ret < 0) + goto failsafe; + + ret = gnutls_x509_crt_import(crt, &cert_list[cert_list_size-1], GNUTLS_X509_FMT_DER); + if (ret < 0) { + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + + /* if it is already self signed continue normally */ + ret = gnutls_x509_crt_check_issuer(crt, crt); + if (ret != 0) { + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + + /* chain does not finish in a self signed cert, try to obtain the issuer */ + ret = gnutls_credentials_get(conn->tls, GNUTLS_CRD_CERTIFICATE, (void**)&sc); + if (ret < 0) { + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + + ret = gnutls_certificate_get_issuer(sc, crt, &ca, 0); + if (ret < 0) { + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + + /* make the new list */ + new_cert_list = GETDNS_XMALLOC(*conn->mfs, gnutls_datum_t, cert_list_size + 1); + if (new_cert_list == NULL) { + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + + memcpy(new_cert_list, cert_list, cert_list_size*sizeof(gnutls_datum_t)); + cl = new_cert_list; + + ret = gnutls_x509_crt_export2(ca, GNUTLS_X509_FMT_DER, &new_cert_list[cert_list_size]); + if (ret < 0) { + GETDNS_FREE(*conn->mfs, new_cert_list); + gnutls_x509_crt_deinit(crt); + goto failsafe; + } + } + +failsafe: + + clsize = cert_list_size; + if (cl == new_cert_list) + clsize += 1; + + ret = dane_verify_crt_raw(NULL, cl, clsize, type, conn->dane_query, 0, 0, &verify); + + if (new_cert_list) { + gnutls_free(new_cert_list[cert_list_size].data); + GETDNS_FREE(*conn->mfs, new_cert_list); + } + + if (ret != DANE_E_SUCCESS) + return GETDNS_RETURN_GENERIC_ERROR; + + switch (verify) { + case DANE_VERIFY_CA_CONSTRAINTS_VIOLATED: + *errnum = 2; + *errmsg = "CA constraints violated"; + return GETDNS_RETURN_GENERIC_ERROR; + + case DANE_VERIFY_CERT_DIFFERS: + *errnum = 3; + *errmsg = "Certificate differs"; + return GETDNS_RETURN_GENERIC_ERROR; + + case DANE_VERIFY_UNKNOWN_DANE_INFO: + *errnum = 4; + *errmsg = "Unknown DANE info"; + return GETDNS_RETURN_GENERIC_ERROR; + } + return GETDNS_RETURN_GOOD; } diff --git a/src/openssl/pubkey-pinning-internal.c b/src/openssl/pubkey-pinning-internal.c index ab2d7c08..5ef02db2 100644 --- a/src/openssl/pubkey-pinning-internal.c +++ b/src/openssl/pubkey-pinning-internal.c @@ -70,82 +70,25 @@ updated and follow the guidance in rfc7469bis) */ -static const getdns_bindata sha256 = { - .size = sizeof("sha256") - 1, - .data = (uint8_t*)"sha256" -}; - - -#define PIN_PREFIX "pin-sha256=\"" -#define PIN_PREFIX_LENGTH (sizeof(PIN_PREFIX) - 1) /* b64 turns every 3 octets (or fraction thereof) into 4 octets */ #define B64_ENCODED_SHA256_LENGTH (((SHA256_DIGEST_LENGTH + 2)/3) * 4) - -/* convert an HPKP-style pin description to an appropriate getdns data - structure. An example string is: (with the quotes, without any - leading or trailing whitespace): - - pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" - - getdns_build_pin_from_string returns a dict created from ctx, or - NULL if the string did not match. If ctx is NULL, the dict is - created via getdns_dict_create(). - - It is the caller's responsibility to call getdns_dict_destroy when - it is no longer needed. - */ -getdns_dict* getdns_pubkey_pin_create_from_string( - getdns_context* context, - const char* str) +getdns_return_t _getdns_decode_base64(const char* str, uint8_t* res, size_t res_size) { BIO *bio = NULL; - size_t i; - uint8_t buf[SHA256_DIGEST_LENGTH]; char inbuf[B64_ENCODED_SHA256_LENGTH + 1]; - getdns_bindata value = { .size = SHA256_DIGEST_LENGTH, .data = buf }; - getdns_dict* out = NULL; - - /* we only do sha256 right now, make sure this is well-formed */ - if (!str || strncmp(PIN_PREFIX, str, PIN_PREFIX_LENGTH)) - return NULL; - for (i = PIN_PREFIX_LENGTH; i < PIN_PREFIX_LENGTH + B64_ENCODED_SHA256_LENGTH - 1; i++) - if (!((str[i] >= 'a' && str[i] <= 'z') || - (str[i] >= 'A' && str[i] <= 'Z') || - (str[i] >= '0' && str[i] <= '9') || - (str[i] == '+') || (str[i] == '/'))) - return NULL; - if (str[i++] != '=') - return NULL; - if (str[i++] != '"') - return NULL; - if (str[i++] != '\0') - return NULL; + getdns_return_t ret = GETDNS_RETURN_GOOD; /* openssl needs a trailing newline to base64 decode */ - memcpy(inbuf, str + PIN_PREFIX_LENGTH, B64_ENCODED_SHA256_LENGTH); + memcpy(inbuf, str, B64_ENCODED_SHA256_LENGTH); inbuf[B64_ENCODED_SHA256_LENGTH] = '\n'; bio = BIO_push(BIO_new(BIO_f_base64()), BIO_new_mem_buf(inbuf, sizeof(inbuf))); - if (BIO_read(bio, buf, sizeof(buf)) != sizeof(buf)) - goto fail; - - if (context) - out = getdns_dict_create_with_context(context); - else - out = getdns_dict_create(); - if (out == NULL) - goto fail; - if (getdns_dict_set_bindata(out, "digest", &sha256)) - goto fail; - if (getdns_dict_set_bindata(out, "value", &value)) - goto fail; - return out; + if (BIO_read(bio, res, res_size) != (int) res_size) + ret = GETDNS_RETURN_GENERIC_ERROR; - fail: BIO_free_all(bio); - getdns_dict_destroy(out); - return NULL; + return ret; } /* this should only happen once ever in the life of the library. it's diff --git a/src/pubkey-pinning.c b/src/pubkey-pinning.c index aaff6eb3..983888fa 100644 --- a/src/pubkey-pinning.c +++ b/src/pubkey-pinning.c @@ -52,6 +52,7 @@ #include "context.h" #include "util-internal.h" +#include "pubkey-pinning.h" #include "pubkey-pinning-internal.h" /* we only support sha256 at the moment. adding support for another @@ -67,6 +68,67 @@ static const getdns_bindata sha256 = { .data = (uint8_t*)"sha256" }; +#define PIN_PREFIX "pin-sha256=\"" +#define PIN_PREFIX_LENGTH (sizeof(PIN_PREFIX) - 1) +/* b64 turns every 3 octets (or fraction thereof) into 4 octets */ +#define B64_ENCODED_SHA256_LENGTH (((SHA256_DIGEST_LENGTH + 2)/3) * 4) +/* convert an HPKP-style pin description to an appropriate getdns data + structure. An example string is: (with the quotes, without any + leading or trailing whitespace): + + pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" + + getdns_build_pin_from_string returns a dict created from ctx, or + NULL if the string did not match. If ctx is NULL, the dict is + created via getdns_dict_create(). + + It is the caller's responsibility to call getdns_dict_destroy when + it is no longer needed. + */ +getdns_dict* getdns_pubkey_pin_create_from_string( + getdns_context* context, + const char* str) +{ + size_t i; + uint8_t buf[SHA256_DIGEST_LENGTH]; + getdns_bindata value = { .size = SHA256_DIGEST_LENGTH, .data = buf }; + getdns_dict* out = NULL; + + /* we only do sha256 right now, make sure this is well-formed */ + if (!str || strncmp(PIN_PREFIX, str, PIN_PREFIX_LENGTH)) + return NULL; + for (i = PIN_PREFIX_LENGTH; i < PIN_PREFIX_LENGTH + B64_ENCODED_SHA256_LENGTH - 1; i++) + if (!((str[i] >= 'a' && str[i] <= 'z') || + (str[i] >= 'A' && str[i] <= 'Z') || + (str[i] >= '0' && str[i] <= '9') || + (str[i] == '+') || (str[i] == '/'))) + return NULL; + if (str[i++] != '=') + return NULL; + if (str[i++] != '"') + return NULL; + if (str[i++] != '\0') + return NULL; + + if (_getdns_decode_base64(str + PIN_PREFIX_LENGTH, buf, sizeof(buf)) != GETDNS_RETURN_GOOD) + goto fail; + + if (context) + out = getdns_dict_create_with_context(context); + else + out = getdns_dict_create(); + if (out == NULL) + goto fail; + if (getdns_dict_set_bindata(out, "digest", &sha256)) + goto fail; + if (getdns_dict_set_bindata(out, "value", &value)) + goto fail; + return out; + + fail: + getdns_dict_destroy(out); + return NULL; +} /* Test whether a given pinset is reasonable, including: diff --git a/src/pubkey-pinning.h b/src/pubkey-pinning.h index 5f12baf2..b94f58af 100644 --- a/src/pubkey-pinning.h +++ b/src/pubkey-pinning.h @@ -39,7 +39,7 @@ /** ** Internal functions, implemented in pubkey-pinning-internal.c. **/ -getdns_dict* getdns_pubkey_pin_create_from_string(getdns_context* context, const char* str); +getdns_return_t _getdns_decode_base64(const char* str, uint8_t* res, size_t res_size); /** ** Public interface. @@ -62,5 +62,7 @@ getdns_return_t _getdns_associate_upstream_with_connection(_getdns_tls_connection *conn, getdns_upstream *upstream); +getdns_dict* getdns_pubkey_pin_create_from_string(getdns_context* context, const char* str); + #endif /* pubkey-pinning.h */