Subject: [Acme] Conflicting requirements for retrieving an ACME account that is already bound to an external account

From: Rob Stradling <[email-masked]>
Date: Wed, 11 Nov 2020 16:02:47 +0000

https://tools.ietf.org/html/rfc8555#section-7.1.2 says (emphasis mine):

  'externalAccountBinding (optional, object):  Including this field in a
      newAccount request indicates approval by the holder of an existing
      non-ACME account to bind that account to this ACME account.  This
      field is not updateable by the client (see Section 7.3.4<https://tools.ietf.org/html/rfc8555#section-7.3.4>).

https://tools.ietf.org/html/rfc8555#section-7.3.1 says (emphasis mine):

  'If the server receives a newAccount request signed with a key for
   which it already has an account registered with the provided account
   key, then it MUST return a response with status code 200 (OK) and
   provide the URL of that account in the Location header field.  The

   body of this response represents the account object as it existed on
   the server before this request; any fields in the request object MUST
   be ignored.'

https://tools.ietf.org/html/rfc8555#section-7.3.4 says:

  'When a CA receives a newAccount request containing an
   "externalAccountBinding" field, it decides whether or not to verify
   the binding.  If the CA does not verify the binding, then it MUST NOT
   reflect the "externalAccountBinding" field in the resulting account
   object (if any).'

ISTM that there's a conflict between these requirements when an ACME account has previously been created and bound to an external non-ACME account (as per section 7.3.4) and a user now wants to retrieve that ACME account by "Finding an Account URL Given a Key" (as per section 7.3.1):
  - The ACME server's directory object provides (quoting section 7.3.4)...

  'the value "true"
   in the "externalAccountRequired" subfield of the "meta" field in the
   directory object.'

...and so the client is required to include the "externalAccountBinding" field in the newAccount request.
  - The server is required to ignore "any fields in the request object" (per the part of section 7.3.1 quoted above).
  - Whilst the server is normally at liberty to choose "whether or not to verify the binding" (per the part of section 7.3.4 quoted above), the requirement to ignore "any fields in the request object" forces the server to not verify the binding provided by the client during this account retrieval operation.
  - Since the server has not verified the binding, its response 'MUST NOT reflect the "externalAccountBinding" field in the resulting account object'.

So by attempting to retrieve a previously-externally-bound account, the ACME client has caused the "externalAccountBinding" field in the account object to be updated (from present to absent).  Except that this can't have happened, because "This field is not updateable by the client" (per the part of section 7.1.2 quoted above) and because "The body of this response represents the account object as it existed on the server before this request" (per the part of section 7.3.1 quoted above).  The upshot is that retrieving a previously-externally-bound account is currently impossible.

At Sectigo we currently have customers affected by this problem, and I think an erratum to RFC8555 is needed to address it.  I'm not proposing text just yet, but I propose that the outcome of the erratum would be that:

  1.  A client should be permitted to omit "externalAccountBinding" from a newAccount request when retrieving an existing account from a server that requires external account binding.  (External account binding is a one-time operation; as soon as an ACME account and an external account are bound once, they're bound forever).
  2.  When a client does provide "externalAccountBinding" in a newAccount request to retrieve an existing account from a server that requires external account binding, the server should be required to verify that binding and ensure that it links to the exact same external account to which the ACME account is already bound.

Do folks agree with my analysis and proposal?

--
Rob Stradling
Senior Research & Development Scientist
Sectigo Limited

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Tue, 17 Nov 2020 12:23:04 -0800

I think this is the key bit:

>   - Since the server has not verified the binding, its response 'MUST NOT
reflect the "externalAccountBinding" field in the resulting account object'.

I understand "reflect" to mean "echo what the client sent." So I think it's
acceptable for the server to ignore the externalAccountBind field / not
re-verify it, and return an account object whose externalAccountBinding
field is set based on the contents of the CA's database.

> 1. A client should be permitted to omit "externalAccountBinding" from a
newAccount request when retrieving an existing account from a server that
requires external account binding.  (External account binding is a one-time
operation; as soon as an ACME account and an external account are bound
once, they're bound forever).
> 2. When a client does provide "externalAccountBinding" in a newAccount
request to retrieve an existing account from a server that requires
external account binding, the server should be required to verify that
binding and ensure that it links to the exact same external account to
which the ACME account is already bound.

I think an errata is not needed to make forward progress, but if you want
to do an errata for 1, I'd support it. I'm not enthusiastic about 2.

In general I think providing the option to look up an account by key was a
mistake. Clients already have to store one piece of state (the account
key), and asking them to store a second piece of state (the account URL) is
not onerous. Particularly in the case where the client is storing both an
account key and an external account binding, I think it's reasonable to ask
that the client store the account URL rather than add additional complexity
to the account-lookup part of the standard.

---

From: Rob Stradling <[email-masked]>
Date: Tue, 17 Nov 2020 22:49:44 +0000

> I understand "reflect" to mean "echo what the client sent."

Thanks Jacob!  I hadn't thought of interpreting it that way.

I've just done a quick review of other instances of the word "reflect" in the document.  I think some instances obviously mean "echo what the client sent" (e.g., [1]), whereas other instances seem to mean "send the server's current view" (e.g., [2]), and at least one instance combines both of these meanings (e.g., [3]).

The context of 'MUST NOT reflect the "externalAccountBinding" field in the resulting account object' is...

  'When a CA receives a newAccount request containing an
   "externalAccountBinding" field,'

...which does seem to suggest that "echo what the client sent" is the intended meaning of this particular instance of "reflect".

So I'm going to choose to interpret it that way so that I can make forward progress.  🙂

> ...if you want to do an errata for 1, I'd support it.

I suppose an erratum for 1 might break compatibility with some existing EAB-requiring servers, so it's probably best not to.

> I'm not enthusiastic about 2.

If a client tries to bind an ACME account that is already bound to external account EA1 to a different external account EA2, but the "externalAccountBinding" field in that client request is silently ignored, how does the client know that (despite the successful account retrieval) their ACME account has not been bound to EA2?  My thought was just that it might be better to throw an error so that the client is aware.

[1] Section 7.3 says:

  'The server MUST NOT reflect the
   "onlyReturnExisting" field or any unrecognized fields in the
   resulting account object.'

      I think the only place "any unrecognized fields" could have come from is the client's request, so this instance of "reflect" seems to mean "echo what the client sent".

[2] Section 7.4.1 says:

  'In some cases, a CA running an ACME server might have a completely
   external, non-ACME process for authorizing a client to issue
   certificates for an identifier.  In these cases, the CA should
   provision its ACME server with authorization objects corresponding to
   these authorizations and reflect them as already valid in any orders
   submitted by the client.'

      Since these authorization objects are determined by the ACME server without any involvement of an ACME client, this "reflect" seems to mean "send the server's current view".

[3] Section 7.4 says:

  'The body of this response is
   an order object reflecting the client's request and any
   authorizations the client must complete before the certificate will
   be issued.'

      The "reflecting the client's request" is obviously "what the client sent"; but "reflecting the...authorizations the client must complete" must mean "send the server's current view", because those authorizations are determined by the server.

________________________________
From: Jacob Hoffman-Andrews <[email-masked]>
Sent: 17 November 2020 20:23
To: Rob Stradling <[email-masked]>
Cc: [email-masked] <[email-masked]>
Subject: Re: [Acme] Conflicting requirements for retrieving an ACME account that is already bound to an external account

CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.

>   - Since the server has not verified the binding, its response 'MUST NOT reflect the "externalAccountBinding" field in the resulting account object'.

I understand "reflect" to mean "echo what the client sent." So I think it's acceptable for the server to ignore the externalAccountBind field / not re-verify it, and return an account object whose externalAccountBinding field is set based on the contents of the CA's database.

> 2. When a client does provide "externalAccountBinding" in a newAccount request to retrieve an existing account from a server that requires external account binding, the server should be required to…

I think an errata is not needed to make forward progress, but if you want to do an errata for 1, I'd support it. I'm not enthusiastic about 2.

In general I think providing the option to look up an account by key was a mistake. Clients already have to store one piece of state (the account key), and asking them to store a second piece of state (the account URL) is not onerous. Particularly in the case where the client is storing both an account key and an external account binding, I think it's reasonable to ask that the client store the account URL rather than add additional complexity to the account-lookup part of the standard.