[getdns-users] public key pinning and tls_authentication models

Daniel Kahn Gillmor dkg at fifthhorseman.net
Tue Dec 22 06:03:03 UTC 2015


hi all--

I just submitted:

  https://github.com/getdnsapi/getdns/pull/130

This allows the user to require that the certificate offered by the
TLS-enabled DNS server use an X.509 certificate with a public key that
matches one of a list of pins.

It's a large-ish changeset overall, but I've tried to order the patches
in a series so that they're easy to read sequentially with clear steps
from one to the next.

As i wrote in the github pull request:


-----------

Configuration is done per-upstream, with an additional member of the
upstream object, tls_pubkey_pinset (by analogy with tls_auth_name).

this is a list of dicts, each of which describes a public key pin, which
looks like this:

  {
    "address_data": <bindata for 185.49.141.38>,
    "address_type": <bindata of "IPv4">,
    "tls_port": 853,
    "tls_pubkey_pinset":
    [
      {
        "digest": <bindata of "sha256">,
        "value": <bindata of 0x17d099f483436ddfb6791428cd8aaa6d...>
      },
      {
        "digest": <bindata of "sha256">,
        "value": <bindata of 0x7e8c59467221f606695a797ecc488a6b...>
      }
    ]
  }

The series includes a new argument for getdns_query -K to specify
pins. All pins are applied to all upstreams. For example:

   getdns_query -K 'pin-sha256="foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9S="' -l L -m '@185.49.141.38~getdnsapi.net' google.com

-------------

There are still a couple missing pieces, though.  In particular, the
interaction between getdns_context_get_tls_authentication() and the
dicts that comprise the list of upstreams seems complicated and
confusing.

We could make it so that your authentication choices are limited to
either NONE or HOSTNAME or PUBKEY_PINSET, but what do we do if the user
specifies a tls_auth_name combined with a PUBKEY_PINSET ?  it seems like
we're giving users extra ways to create internally-incompatible
configurations, and the API has a hard enough time already with nuanced
error reporting.

At the same time, i realize that it might be useful for some users to be
able to suggest a hostname or a public key pinset opportunistically,
just to see what would happen without getting in the way of
functionality.

One way to do this is to drop getdns_context_get_tls_authentication()
entirely.  I'm calling this the "upstream-dict" model of tls
authentication:

 * an upstream configured with tls_auth_name will do hostname
   verification checking;

 * an upstream configured with tls_pubkey_pinset will do public key
   pinset matching;

 * and an upstream configured without either of these will not be able
   to do strict TLS.

A user who wants to do opportunistic "verification" can specify a
non-TLS transport, which will enable all of these strict modes to become
advisory-only.

I think this is the simplest model we can offer that is internally
consistent, and i think it's less confusing to the user than requiring
them to set an explicit tls_authentication mode on the context object
that happens to match the values in each upstream dict.

There is some question about whether there is a legitimate use case for
wanting to do "only TLS", but in an opportunistic mode -- that is,
without fallback to a cleartext transport.  I'll call this "blinded-tls".
The proposed tls-authentication-in-the-upstream-dict model doesn't
support blinded-tls explicitly.

if we decide we do want to support blinded-tls, we could add a function
getdns_context_allow_opportunistic_tls() that makes this explicitly
possible.  This is semantically equivalent to having two possible values
for getdns_tls_authentication_t : OPPORTUNISTIC and STRICT, which we
could map to the current values of NONE and HOSTNAME and hope we get
away with a dirty tweak of the library's API :)

By default, a context would report STRICT if it has only TLS transports,
and OPPORTUNISTIC if it has non-TLS transports.  If the context has only
TLS transports, and the user sets OPPORTUNISTIC, that would be
"blinded-tls".  I'm not sure what we should do if the user sets STRICT
but has non-TLS transports available, though.  Perhaps that would be an
error in the configuration?

Again, it seems better to me to field an API that has fewer ways that a
user can get themselves into an unusable state, which is why i'm leaning
toward the pure "upstream-dict" model.

What do folks think about this?  What are the advantages of retaining
the current getdns_context_{set,get}_tls_authentication() approach
(other than API stability)?

     --dkg

PS fwiw, no package in the debian archive is currently using
   g_c_{s,g}et_tls_authentication -- this is the kind of API change that
   we could probably drop without a lot of pain if we wanted to:

 https://codesearch.debian.net/results/getdns_context_%5Bsg%5Det_tls_authentication/page_0



More information about the Users mailing list