From 91f04ecd5edfe0a7a2f9ddf0bc216f149b75df88 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 20 Dec 2015 12:58:50 -0500 Subject: [PATCH] add getdns_pubkey_pin_create_from_string() --- src/Makefile.in | 7 +- src/getdns/getdns_extra.h.in | 37 +++++++++ src/libgetdns.symbols | 1 + src/pubkey-pinning.c | 140 +++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/pubkey-pinning.c diff --git a/src/Makefile.in b/src/Makefile.in index fc323242..f1e23c8a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -64,8 +64,8 @@ EXTENSION_LIBUV_LDFLAGS=@EXTENSION_LIBUV_LDFLAGS@ C99COMPATFLAGS=@C99COMPATFLAGS@ GETDNS_OBJ=const-info.lo convert.lo dict.lo dnssec.lo general.lo \ - list.lo request-internal.lo rr-dict.lo rr-iter.lo stub.lo sync.lo \ - util-internal.lo + list.lo request-internal.lo pubkey-pinning.lo rr-dict.lo \ + rr-iter.lo stub.lo sync.lo util-internal.lo GLDNS_OBJ=keyraw.lo gbuffer.lo wire2str.lo parse.lo parseutil.lo rrdef.lo \ str2wire.lo @@ -326,6 +326,9 @@ libevent.lo libevent.o: $(srcdir)/extension/libevent.c config.h $(srcdir)/types- libmini_event.lo libmini_event.o: $(srcdir)/extension/libmini_event.c config.h $(srcdir)/debug.h config.h \ $(srcdir)/types-internal.h getdns/getdns.h getdns/getdns_extra.h getdns/getdns.h \ $(srcdir)/util/rbtree.h $(srcdir)/extension/libmini_event.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h +<<<<<<< HEAD libuv.lo libuv.o: $(srcdir)/extension/libuv.c config.h $(srcdir)/debug.h config.h $(srcdir)/types-internal.h \ getdns/getdns.h getdns/getdns_extra.h getdns/getdns.h $(srcdir)/util/rbtree.h \ $(srcdir)/getdns/getdns_ext_libuv.h getdns/getdns_extra.h +pubkey-pinning.lo pubkey-pinning.o: $(srcdir)/pubkey-pinning.c \ + config.h getdns/getdns.h diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index a33e6f4a..12161e10 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -342,6 +342,41 @@ getdns_context_get_update_callback(getdns_context *context, void **userarg, */ const char *getdns_get_errorstr_by_id(uint16_t err); + + +/** + * Public Key Pinning functionality: + * + * a public key pinset is a list of dicts. each dict should have a + * "digest" and a "value". + * + * "digest": a string indicating the type of digest. at the moment, we + * only support a "digest" of "sha256". + * + * "value": a binary representation of the digest provided. + * + * given a such a pinset, we should be able to validate a chain + * properly according to section 2.6 of RFC 7469. + */ + +/** + * 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=" + * + * It is the caller's responsibility to call getdns_dict_destroy() on + * the dict returned when it is no longer needed. + * + * @param context a context to use to create the dict, or NULL to create + * it generically + * @param str the pinning string to parse + * @return a dict created from ctx, or NULL if the string did not match. + */ +getdns_dict* getdns_pubkey_pin_create_from_string( + getdns_context* context, + const char* str); /* WARNING! Function getdns_strerror is not in the API specification and * is likely to be removed from future versions of our implementation, to be @@ -381,6 +416,8 @@ typedef enum getdns_tls_authentication_t { #define GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE_TEXT "Change related to getdns_context_set_edns_client_subnet_private" #define GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE 620 #define GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE_TEXT "Change related to getdns_context_set_tls_query_padding_blocksize" +#define GETDNS_CONTEXT_CODE_PUBKEY_PINSET 621 +#define GETDNS_CONTEXT_CODE_PUBKEY_PINSET_TEXT "Change related to getdns_context_set_pubkey_pinset" getdns_return_t getdns_context_set_tls_authentication( diff --git a/src/libgetdns.symbols b/src/libgetdns.symbols index 2503c48c..9f7276cb 100644 --- a/src/libgetdns.symbols +++ b/src/libgetdns.symbols @@ -114,6 +114,7 @@ getdns_pretty_snprint_dict getdns_pretty_snprint_list getdns_print_json_dict getdns_print_json_list +getdns_pubkey_pin_create_from_string getdns_root_trust_anchor getdns_rr_dict2str getdns_rr_dict2str_buf diff --git a/src/pubkey-pinning.c b/src/pubkey-pinning.c new file mode 100644 index 00000000..b20f0817 --- /dev/null +++ b/src/pubkey-pinning.c @@ -0,0 +1,140 @@ +/** + * + * /brief functions for Public Key Pinning + * + */ + +/* + * Copyright (c) 2015, Daniel Kahn Gillmor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * getdns Public Key Pinning + * + * a public key pinset is a list of dicts. each dict should have a + * "digest" and a "value". + * + * "digest": a string indicating the type of digest. at the moment, we + * only support a "digest" of "sha256". + * + * "value": a binary representation of the digest provided. + * + * given a such a pinset, we should be able to validate a chain + * properly according to section 2.6 of RFC 7469. + */ +#include +#include +#include +#include +#include + +/* we only support sha256 at the moment. adding support for another + digest is more complex than just adding another entry here. in + particular, you'll probably need a match for a particular cert + against all supported algorithms. better to wait on doing that + until it is a better-understood problem (i.e. wait until hpkp is + 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) +{ + BIO *bio = NULL; + int 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 (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; + + /* openssl needs a trailing newline to base64 decode */ + memcpy(inbuf, str + PIN_PREFIX_LENGTH, 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; + + fail: + BIO_free_all(bio); + getdns_dict_destroy(out); + return NULL; +} + +/* pubkey-pinning.c */