Subject: [Acme] Implicit vs. explicit keys (was Re: Unparallelizing roll-over)

From: Richard Barnes <[email-masked]>
Date: Fri, 19 Aug 2016 16:02:08 -0400

[Forking this into two threads]

[Fork 1 of 2]

On Fri, Aug 19, 2016 at 2:30 PM, Jacob Hoffman-Andrews <[email-masked]> wrote:

> On 08/19/2016 09:31 AM, Richard Barnes wrote:
> > Actually I thought of another reason, which is more compelling: There's
> > no specified algorithm for comparing JWKs.  Do you include, e.g., "kid"
> > or "x5t"?
> >
> > So we would need to specify a comparison algorithm.
>
> This is already a problem for each authenticated ACME POST. Right now
> the user is identified solely by their full account key, and it's up to
> the ACME server to determine how to look up a user by that key. I think
> this isn't a problem by itself. The server can do a thumbprint itself,
> preserving the option to switch hashes without breaking any clients.
>
> However, this relates to another change I've been meaning to propose:
> Identifying POSTs by account URLs instead of by key.
>

Just for clarity, I assume the proposal would be to require something like
{ "account": "accountURL" } in the JWS header instead of the "jwk" field.
Then the server would look up the appropriate JWK to verify the signature.

> This encourages servers to implement this logic:
>
>  - verify a POST using the account key from the JWS header
>  - then, look up that account key in a database of users
>
> Instead of this logic:
>
>  - look up an account's key in a database of users
>  - then, verify the POST using that account key
>
> The latter better fits the best practice of "don't process untrusted
> input until after you've verified the signature." The former is
> vulnerable if the attacker can make their own key that happens to
> coincide with another in the database.
>

However, we've already said that you can look up an account based on a
public key (#163), so if you can somehow reproduce a valid account key,
it's game over anyway.

This reminds me of the debate we had in JOSE about applying integrity
protection to parameters like "alg" and "jwk".  Yes, in general, you should
only act on verified data, but there's no point to applying integrity
protection to the inputs to the signature verification.  The only thing an
attacker can do by changing those inputs is make something fail to validate.

Given that there's no security difference between the two scenarios, I have
a pretty strong preference for identifying the key by value rather than by
the account URI.  It lets you have a clean transport layer that can do
validation before any ACME logic gets involved.

For example, if you identify keys by account URI, you need to a lookup in
the ACME DB before you can reject a bad request.  If you identify keys
directly, you can just have validation as a middleware step, which is a
natural fit for how modern web services are built.  See
https://github.com/bifurcation/rocket-skates/blob/master/lib/server/transport-server.js#L41

>
> Instead, the client can use the account URL rather than the full key to
> identify each request after registration. This requires that the server
> keep track of keys and properly validate them. It also makes lookup more
> straightforward for the server with regards to the question you raised
> about comparison of JWKs.
>
> For instance, the client could provide the account URL in the JWS "kid"
> header instead of the full key in the JWS "jwk" header.
>
> This matches nicely with your proposed use of the account URL in the
> inner key-change payload. I think providing the account URL there also
> removes the need for the oldKey field. I still strongly believe we need
> the full newKey. So we'd wind up with:
>
> account (required, string):
> The URL for account being modified.  The content of this field MUST be
> the exact string provided in the Location header field in response to
> the new-registration request that created the account.
>
> newKey (requrired, JWK):
> The JWK representation of the new key
>

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Sun, 21 Aug 2016 15:49:12 -0700

On 08/19/2016 01:02 PM, Richard Barnes wrote:
> Just for clarity, I assume the proposal would be to require something
> like { "account": "accountURL" } in the JWS header instead of the "jwk"
> field.  Then the server would look up the appropriate JWK to verify the
> signature.

Approximately correct. As I mentioned in my earlier email, I think the
JWS "kid" header is a natural fit for this, since the the client is
identifying to the server which key should be used to validate its request.

> public key (#163), so if you can somehow reproduce a valid account key,

The threat is not that someone could illegitimately find an account URL.

The threat is that someone can come up with key X, sign a payload with
key X, and present that signed payload such that when the ACME server
looks up key X, it finds key Y. For instance, this could be possible
with a broken hash function, or an ambiguity in JWK representation, or a
silently truncated database field.

> Given that there's no security difference between the two scenarios, I
> have a pretty strong preference for identifying the key by value rather
> than by the account URI.  It lets you have a clean transport layer that
> can do validation before any ACME logic gets involved.

I've described above the potential security difference. There's also a
philosophical question here: Should raw keypairs be the fundamental unit
identifying users of ACME, with accounts being some metadata stapled
weakly on the side? or should and account be the fundamental identifying
factor, with a public key associated for authentication purposes?

I think best practice in general is not to treat raw keypairs as an
identifier on their own. For x509, we wrap keypairs in certificates to
associate them with data. For SSH, keypairs are listed in an
authorization file associated with a login account.

Additionally, the raw keypair notion is not a good fit for how any CA
actually models their users. I believe all paid CAs organize usage
primarily around accounts. And even Let's Encrypt, which carries
relatively little long-term state around the account, still treats the
account as the fundamental unit of organization, rather than the raw
keypair.

> For example, if you identify keys by account URI, you need to a lookup
> in the ACME DB before you can reject a bad request.  If you identify
> keys directly, you can just have validation as a middleware step, which
> is a natural fit for how modern web services are built.  See

As someone who has worked on the security team for a very large, modern
web service, and implemented their authentication code, I strongly disagree.

Additionally, there is nothing about "middleware" that implies "no
database lookup."

---

From: Richard Barnes <[email-masked]>
Date: Sun, 21 Aug 2016 19:31:28 -0400

How about this as a compromise proposal: Have the JWS header contain *both*
the account URL and the account public key.  That way you get fast
rejection based on crypto failures, and you also get protection against any
issues related to relying on public keys alone.

On Sun, Aug 21, 2016 at 6:49 PM, Jacob Hoffman-Andrews <[email-masked]> wrote:

> _______________________________________________
> Acme mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/acme
>

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Sun, 21 Aug 2016 17:32:07 -0700

On 08/21/2016 04:31 PM, Richard Barnes wrote:
> How about this as a compromise proposal: Have the JWS header contain
> *both* the account URL and the account public key.  That way you get
> fast rejection based on crypto failures, and you also get protection
> against any issues related to relying on public keys alone.
This doesn't achieve the goal of making sure that ACME servers are
validating based on account data they already have, rather than
validating based on a key provided in the request.

---

From: Richard Barnes <[email-masked]>
Date: Sun, 21 Aug 2016 22:35:50 -0400

> validating based on a key provided in the request.

What do you mean by "validating"?

There are two types of validation to do here:
1. Validating that the signature on the message is good
2. Validating that the key belongs to a the right account for this request

You can do the first of these without any knowledge of the account status
of a key.  Doesn't require any database lookup if you provide the key in
the header.  There's no harm to doing this validation without knowing
whether the key is good, and there's benefit in rejecting bad signatures
quickly.

--Richard

---

From: "Manger, James" <[email-masked]>
Date: Mon, 22 Aug 2016 13:33:30 +1000

On Sun, Aug 21, 2016 at 8:32 PM, Jacob Hoffman-Andrews <[email-masked]<mailto:[email-masked]>> wrote:

>You can do the first of these without any knowledge of the account status of a key.  Doesn't require any database lookup if you provide the key in >the header.  There's no harm to doing this validation without knowing whether the key is good, and there's benefit in rejecting bad signatures quickly.

The point is that if you: take an account id from message → lookup account → use public key from account to verify signature; then it is hard to see how an implementer can get #2 wrong. But you have to do the DB lookup before the crypto verification.

Richard’s alternative approach takes a public key from a message and tries to match that to a public key in an account. That should work, but it is less clear that there are not easy implementer traps. Matching keys is less obvious. How to do it isn’t specified. You could calculate JWK Thumbprints [RFC7638] and match those strings. Thumbprints ignore optional fields; hopefully that’s okay. Thumbprints can’t be relied on to be unique (eg shouldn’t be used in a blacklist); hopefully Acme implementations only need them to be unambiguous, not unique, for security. I hope an implementation doesn’t assume a “kid” (key-id) provided by a client is a thumbprint.

Having both (public key and account-id) in a message raises the risk that a sloppy implementation verifies with the former, does the lookup with the latter, then fails to properly link the two.

--
James Manger