Subject: Re: [dtn] PKIX Extended Key Purpose recommendations

From: Benjamin Kaduk <[email-masked]>
Date: Wed, 2 Dec 2020 23:29:42 -0800

Hi Brian,

Replying just on the OpenSSL front for now, since I am not sure how much
longer I will still be awake...

On Fri, Nov 20, 2020 at 05:34:50PM +0000, Brian Sipos wrote:
> 
> 
> The good news for rationale and understanding of these uses are that they are very similar to SMTP/TLS [1] and S/MIME [2] respectively. Although it appears that the id-kp-emailProtection OID was only ever specified for S/MIME and not for email transport between MTAs. I don't know what kinds of policies SMTP MTAs have with regard to PKIX certificates, even recent MTA-STS spec [3] doesn't define or reference a more detailed PKIX profile and doesn't mention any X.509 extensions at all.
> 
> The bad news is that in some very simple testing of adding a new purpose OID I see that OpenSSL, which is used by both of my reference libraries Qt5 and python "ssl" package, appears to assume [4] that when I want "TLS on a socket" what I mean is "TLS for Web client/server purposes" and requires either id-kp-serverAuth or id-kp-clientAuth. There also appear to be quite a number of other implicit uses of the Web purposes in OpenSSL and no real access via higher-level APIs to affect this. The other key purposes seem to be usable only through different APIs unrelated to TLS.

It ends up not actually being too hard to get OpenSSL to behave differently
in this regard, though it is very much one of those "the answer is simple
only once you already know it" situations, and is further complicated by
the cornucopia of similarly named API functions.  (Neither of these is
unusual for OpenSSL, sadly, nor will they get fixed anytime soon, due to
the API stability promise.)

It is perhaps easiest to explain by reference to the libssl code that
surrounds the behavior in question;
https://github.com/openssl/openssl/blob/master/ssl/ssl_cert.c#L413-L430 :

<BEGIN CODE>
     * We need to inherit the verify parameters. These can be determined by
     * the context: if its a server it will verify SSL client certificates
     * or
     * vice versa.
     */

    X509_STORE_CTX_set_default(ctx, s->server ? "ssl_client" : "ssl_server");
    /*
     * Anything non-default in "s->param" should overwrite anything in the
     * ctx.
     */
    X509_VERIFY_PARAM_set1(param, s->param);

    if (s->verify_callback)
        X509_STORE_CTX_set_verify_cb(ctx, s->verify_callback);

    if (s->ctx->app_verify_callback != NULL)
        i = s->ctx->app_verify_callback(ctx, s->ctx->app_verify_arg);
    else
        i = X509_verify_cert(ctx);
<END CODE>

The X509_STORE_CTX_set_default() call is what ends up causing the X.509
implementation to look for the id-kp-serverAuth/id-kp-clientAuth EKU value,
when X509_verify_cert() is called.  However, it is also clear from the last
quoted part that the "app_verify_callback" can take the form of a thin
wrapper around X509_verify_cert() that applies the needed configuration
changes to the X509_STORE_CTX configuration.  (Note that there is also the
verify_callback that can be used to override verification decisions at a
very fine-grained level but is also much more sensitive to implement
safely.  The man page at least does have a prominent disclaimer to not
confuse the two...) However, the the table of standard purpose values that
openssl natively supports (static X509_PURPOSE xstandard[] in v3_purp.c) is
pretty limited, and so in practice when using other EKUs we end up using
the "Any Purpose" entry and making the application code do the rest.

The X509_PURPOSE_add() API does to let the application add one at runtime,
but that involves getting a "NID" (OpenSSL-internal integer shorthand for
an ASN.1 object identifier), which involves OBJ_create() at runtime, as
well as specifying a function to do the actual checking.  If it's only
going to be used at one place in the application, that's a lot of overhead
when it can just be coded inline in the app verify callback!  (The relevant
checks that are done by default for the ssl_client/ssl_cerver uses are in
check_purpose_ssl_client() and check_purpose_ssl_server(), also in
v3_purp.c.  The xku_reject() macro they use is not helpful, since openssl
doesn't have a defined flag for the new EKU type, so this would entail the
loop over the X509_get_ext_d2i(., NID_ext_key_usage, ...) results and
looking for the new OID.)

I would like to write some code and have a bit more of an example for you,
but am wary about promising too much...

In short, there are no higher-level APIs because the OpenSSL implementation
requires too much knowledge in the library to make it easy to provide such
a high-level API.  But there is a hook to let application code run in
exactly the right place to take over the bits of X509 verification that are
affected, so the application can do what it needs to with only a relatively
small amount of low-level code.

-Ben

---

From: Brian Sipos <[email-masked]>
Date: Thu, 3 Dec 2020 21:56:25 +0000

Ben,
Thanks for this detailed explanation. It's still a bit confusing how interpretations of the EKU value labeled "TLS Web server authentication" sometimes take it to mean "any TLS server use" and specs for LDAP/TLS, IMAP/TLS, and SNMP/TLS don't mention EKU at all. I am tempted to mention it as non-normative interoperability note, because it's a big gotcha that bit me.

After looking through these kinds of implementation details, and also observing that the active TCPCL entity (TLS client) requires authentication but can only use NODE-ID or IPADDR-ID (not DNS-ID), I think the best specification-level logic is to:

  1.   Leave the procedures for how and when to do DNS-ID and IPADDR-ID validation in place, in some environments this is the only choice.
  2.  Make the recommended security policy to require mutual authentication based on NODE-ID validation alone and allow lower-level claims to be absent. This means to allow the "absent" result (cert just doesn't have DNS-ID or IPADDR-ID), but an earlier suggestion of yours was to allow a "failed" result (cert has other claims but we ignore them). Do you still believe that allowing lower-level failure is a good policy?
  3.  Add a specific requirement that the received peer Node ID should not be used for discovery/routing decisions if it is not validated by TLS. This is catch-all to just avoid the peer node impersonation threat.
  4.  Add an extended key purpose for "bundleSecurity" (which can mirror existing concepts for "emailSecurity") but don't mandate its use.

This recommended policy is strict, but users are free to adjust to whatever fits their needs. Also, when TLS authentication was originally discussed, there was no concept of a NODE-ID claim at all and no mechanism for a CA to verify such a claim (the original behavior was based on web PKI). Both such mechanisms are now proposed so achieving this stricter policy seems reasonable (though aspirational).
________________________________
From: Benjamin Kaduk <[email-masked]>
Sent: Thursday, December 3, 2020 02:29
To: Brian Sipos <[email-masked]>
Cc: Russ Housley <[email-masked]>; [email-masked] <[email-masked]>
Subject: Re: PKIX Extended Key Purpose recommendations

Hi Brian,

It is perhaps easiest to explain by reference to the libssl code that
surrounds the behavior in question;
https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fopenssl%2Fopenssl%2Fblob%2Fmaster%2Fssl%2Fssl_cert.c%23L413-L430&amp;data=04%7C01%7CBSipos%40rkf-eng.com%7C8d446895452541f18cef08d8975d38e4%7C4ed8b15b911f42bc8524d89148858535%7C1%7C0%7C637425773935215065%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=GmxDfBiUBCGBkAmQvLmCRmeJO%2FmMwhSEAfBSJjxMYCw%3D&amp;reserved=0 :

<BEGIN CODE>

    if (s->verify_callback)

-Ben