Subject: Re: [Acme] Combine "requirements" and "authorizations."

From: Richard Barnes <[email-masked]>
Date: Sun, 2 Oct 2016 10:46:42 -0400

I'm worried that we're losing a lot of useful, clear semantics here in
order to save a few bytes.

I understand the impulse here; it is challenging to have multiple status
values that could conflict with each other, and it's a pain for the client
to have to go fetch a separate authorization resource to find out what it
has to do.  That doesn't mean that we've gotten the semantics wrong; we've
just put the bits in the wrong place.

If we want to do something here, I think the right way to think about it is
as "inlining" the authorization document in the authorization requirement,
just as challenge resources are inlined into authorization resources.  That
is, we would have "authorization" requirement objects contain the contents
of the authorization resource, in addition to a URL for the authorization
resource.

However, I'm not really sure that even that is worthwhile.  Consider that
authorizations can be re-usable for multiple applications; we envisage
clients being able to authorize a bunch of domains, then issue certificates
for arbitrary combinations.  It's wasteful to replicate the authorization
document in each application that uses already-valid authorizations, in
terms of bytes on the wire, but also in terms of the server's state storage
(if it stores the authorizations in the application object) or processing
(if it re-assembles them each time).  At the very least, we need to have
authorizations continue to be independently addressable in order to
indicate clearly that the same authorization is being used for different
applications -- a need that does not necessarily apply to other types of
requirement objects.

In any case, we should not lose the semantic distinction between "proving
that you control an identifier" and other things a server requires the
client to do.

This PR also removes two important pieces of functionality:
- The generic OOB requirement type - Would need to be replaced by new
authorization types
- The ability of the protocol to cleanly support new identifier types;
instead of just defining a new identifier type and some challenges, you
need to copy / paste the whole definition of the dns-name challenge.

So my preference here would be to be more targeted:
- Clarify that the "status" of an "authorization" requirement MUST equal
the status of the underlying authorization
- Possibly allow inlining of the authorization (not require, for the reuse
reasons above)

On Thu, Sep 29, 2016 at 5:28 PM, Ted Hardie <[email-masked]> wrote:

> On Thu, Sep 29, 2016 at 1:50 PM, Jacob Hoffman-Andrews <[email-masked]>
> wrote:
>
>>
>> This also makes sense. I think URIs make more sense, rather than
>>> defining a new namespace. And the URIs can contain more information
>>> about the type. What do other folks think?
>>>
>>>
>> I'm a little confused about the proposal, especially how the URI would
>> contain more information about the type.   Would you mind fleshing this out
>> a bit?
>>
>> Something like this:
>>
>> Servers that create authorization objects with a non-standard type should
>> provide a URL as the type string. This allows non-standard types to be
>> created in a well-defined namespace, to avoid conflicts. Visiting the URL
>> in a web browser should result in a human-readable web page describing the
>> non-standard authorization type.
>>
>> However, I'm very open to other suggestions!
>>
>
> Well, I guess I have two thoughts here.  The first is that if we do want
> to shift to URIs, then we should move DNSname to also being a URI (a URN at
> IANA or an IANA registry entry).  If the client recognizes it, then it
> doesn't need to dereference the page, as the content is guaranteed to be
> the same (especially so in the case of URNs), but it is getting the same
> sort of identifier for standard and nonstandard types.
>
> But I'm not sure that this is the right direction, as I worry that we
> might lose interoperability over time.  For out-of-band authorizations, I
> think we could use a single common type and an additional field for
> dereferencing the out-of-band steps.  But for new general mechanisms, it
> would be useful if the client knew from examination whether it could or
> should use the authorization type.  If service A and service B use
> different URIs for the same mechanism, you have the possibility of a client
> not recognizing that it can use service B.
>
> Imagine for a second that someone creates a method to use an entry in a
> block chain as an authorization mechanism; if more than one service wants
> to use the method the 2nd and later either have to refer to the type using
> the first's URI or making up new URIs that aren't automatically recognized
> by clients built in response to the first.
>
> I agree with the need to collision, but I'm not sure that we have likely
> collisions unless we try to differentiate each out-of-band set of steps
> into types.  That doesn't seem that useful to me, personally.
>
> Just an initial reaction,
>
> Ted
>
>
> _______________________________________________
> Acme mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/acme
>
>

---

From: Eric Rescorla <[email-masked]>
Date: Sun, 2 Oct 2016 08:26:02 -0700

On Sun, Oct 2, 2016 at 7:46 AM, Richard Barnes <[email-masked]> wrote:

> If we want to do something here, I think the right way to think about it
> is as "inlining" the authorization document in the authorization
> requirement, just as challenge resources are inlined into authorization
> resources.  That is, we would have "authorization" requirement objects
> contain the contents of the authorization resource, in addition to a URL
> for the authorization resource.
>

+1

I think this is most obvious in the case of payment. A CA might ask me to
prove that I own the identifier once
but pay each time. This seems like it should be first-class.

-Ekr

>>> Servers that create authorization objects with a non-standard type
>>> should provide a URL as the type string. This allows non-standard types to
>>> be created in a well-defined namespace, to avoid conflicts. Visiting the
>>> URL in a web browser should result in a human-readable web page describing
>>> the non-standard authorization type.
>>>

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Wed, 26 Oct 2016 11:27:10 -0700

> order to save a few bytes.
This misunderstands the intent of this change. I'm not trying to save
bytes here, I'm trying to clarify the semantics by (a) having fewer
distinct types of objects, and (b) having a single, canonical place for
clients to check the status of things.

The current approach of inlining the status field, or even inlining the
whole authorization as your propose, would reduce semantic clarity and
potentially introduce adding bugs, for the sake of saving a request.

Here's another way of looking at it: The "requirements" notion has a lot
in common with authorizations, in particular the need to do an action,
poll for ready status, and ensure that each one present in a set is
satisfied. Why define an entirely separate type? Also: Why are they not
individually addressable, when authorizations are? If we make them
individually addressable, why not make them the same type of object?
This will make it easier for clients to implement them.

> However, I'm not really sure that even that is worthwhile.  Consider
> that authorizations can be re-usable for multiple applications; we
> envisage clients being able to authorize a bunch of domains, then
> issue certificates for arbitrary combinations.
I don't see how my proposal conflicts with authorization reuse. The
server chooses which authorizations to require for a new application. It
can choose to reuse those that are reusable (dns-name) and generate a
new one each time for those that are a per-issuance requirement (payments).

> - The generic OOB requirement type - Would need to be replaced by new
This is a good point, I'll re-introduce an OOB authorization type to
satisfy this need.

> instead of just defining a new identifier type and some challenges,
> you need to copy / paste the whole definition of the dns-name challenge.
I'm not sure what this means. There's no "dns-name challenge." There are
a set of challenges that are valid for dns-name authorizations. If
somone defines a new identifier type, say email address, the set of
challenges that are valid for that identifier type will definitely be
different. This PR actually clarifies that situation.

---

From: Richard Barnes <[email-masked]>
Date: Fri, 28 Oct 2016 16:57:41 -0400

OK, I gave the PR a closer read, and recognize that I misunderstood some
stuff below.  Let me try again :)  It seems like this PR does basically two
things:

1. Remove "requirements" as a level of abstraction
2. Convert from "identifier authorization" to "DNS name authorization"

Can you explain to me why (2) is desirable?  It seems like a pure reduction
in extensibility.

As for (1), I don't really think you're getting what you think you're
getting.  The functionality we need here is going to require two levels of
abstraction:

1. The CA wants you to do something -- "requirement" vs. "authorization"
2. The CA wants you to do this specific thing -- "authorization" vs.
"subtype-of-authorization"

You just haven't gotten to the second level of abstraction in this PR
because you haven't re-added OOB.  I think when you do that (or any other
function), you'll find that you're going to want separate subsections for
"authorization authorizations" and "OOB authorizations", with different
content, etc.

The net effect of this is just to push the multiplexing out a layer -- you
have to dereference the URL in the requirements array before you figure out
what the specific task is (from the "type" in the authorization object), as
opposed to being able to figure it out from a requirement object.

--Richard

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Fri, 28 Oct 2016 15:01:52 -0700

On 10/28/2016 01:57 PM, Richard Barnes wrote:
> OK, I gave the PR a closer read, and recognize that I misunderstood
> some stuff below.  Let me try again :)  It seems like this PR does
> basically two things:
>

Yep, good summary.

> Can you explain to me why (2) is desirable?  It seems like a pure
> reduction in extensibility.
Same reason as (1), to remove a level abstraction. If we do just (1), we
would have authorizations with two type fields:

{
  "type": "identifier",
  "identifier": { "type": "dns", "value": "example.com"},
  ...
}

This is a bit awkward, but more to the point, it doesn't increase
expressivity. A DNS identifier authorization doesn't have anything in
common with a hypothetical telephone number authorization, except (a)
they both have a status, and (b) they both have a list of challenges. It
turns out those properties are also shared in common with an "oob"
authorization. So we can flatten that abstraction neatly. We could have
authorizations with types: "dns-name", "telephone-number",
"email-address", and "oob".

> getting.  The functionality we need here is going to require two
> levels of abstraction:
>
Can you explain this more? I don't think I understand the distinction
you're getting at, or why it needs a separate type of object.

The main thing that you want a separate "requirement" type for is OOB
actions. That means both "the CA wants you to do something" AND "the CA
wants you do do this specific thing (click on the link and follow the
instructions)."

---

From: Richard Barnes <[email-masked]>
Date: Fri, 28 Oct 2016 18:18:49 -0400

On Fri, Oct 28, 2016 at 6:01 PM, Jacob Hoffman-Andrews <[email-masked]> wrote:

> they both have a status, and (b) they both have a list of challenges.

This is where you lose me.  You're underestimating the degree of
commonality here.  Yes, you're going to do different challenges per
identifier type (SMS vs. HTTP, say), but the overall flow at the *ACME*
level is the same:

1. Get the list of challenges offered by the server
2. Choose one and prepare to respond to it
3. POST a response to the challenge

That means you can build clients and servers that can be dynamically
configured with challenge/validation types, and use all the same ACME
code.  For example:

https://github.com/bifurcation/rocket-skates/#architecture

I don't think it's worth losing this flexibility just to go from
"type+value" to "value".

> It

Like I said, you would have to reinvent or copy/paste the whole
challenge/response machinery for each new identifier type.

Also: OOB has no challenges.  Not sure what you're referring to there.
It's totally different from the authz flow.

> wants you do do this specific thing (click on the link and follow the

I think the crux of our disagreement is actually above.  You seem to be
arguing that authz for different identifier types are as different from one
another as OOB is from DNS-name.  I'm saying that the identifier validation
process is going to be the same for many different identifiers, so the
"authorization" abstraction is meaningful.

--Richard

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Fri, 28 Oct 2016 15:34:04 -0700

On 10/28/2016 03:18 PM, Richard Barnes wrote:
> I think the crux of our disagreement is actually above.  You seem to
> be arguing that authz for different identifier types are as different
> from one another as OOB is from DNS-name.  I'm saying that the
> identifier validation process is going to be the same for many
> different identifiers, so the "authorization" abstraction is meaningful.
Can you give an example of how they will be the same? The currently
defined dns-01, http-01, and tls-sni-02 challenges can only be used for
DNS identifiers.

The "oob" challenges can be used by what you propose currently fill the
"requirements" niche. Specifically, you would have an authorization of
type "oob", containing a single challenge of type "oob."

---

From: Richard Barnes <[email-masked]>
Date: Fri, 28 Oct 2016 18:59:55 -0400

> defined dns-01, http-01, and tls-sni-02 challenges can only be used for

Sure.  As I said before, you'll use different sets of challenges, but the
overall flow remains the same.

Imagine an ACME client / server has a module that implements the challenge
/ validation logic for a challenge types.  For an HTTP challenge, it things
like make the HTTP challenge object, configure an HTTP server, do the HTTP
requests.  For a putative SMS-based challenge, it would generate the
challenge object, and orchestrate the sending / responding-to of SMS
messages.

Then the flow from the server's perspective is:

1. On new-app requiring a new authz:
1.1. Query the list of challenge modules to see which support $IDENTIFIER
1.2. For each supported challenge type, construct a challenge object for
$IDENTIFIER
1.3. Assemble the challenges into an authorization object
2. When the client responds to a challenge:
2.1. Call out to the module that generated the challenge
2.2. [[ module goes off and validates the challenge ]]
2.3. When the module has finished validating, update the authz with the
results.

None of that is dependent on the type of identifier being validated.  This
is a very clean abstraction boundary.  You can see this with the simplicity
of the API in rocket-skates; the above is exactly what it does.

> type "oob", containing a single challenge of type "oob."

Now you're the one adding unnecessary levels of abstraction ;)

--Richard

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Fri, 28 Oct 2016 16:09:22 -0700

On 10/28/2016 03:59 PM, Richard Barnes wrote:
> 1.2. For each supported challenge type, construct a challenge object
> for $IDENTIFIER
> 2.3. When the module has finished validating, update the authz with
> the results.
This flow seems reasonable. Why would it be different for what is
currently called a "requirement?"

> Now you're the one adding unnecessary levels of abstraction ;)
Nope, the "oob" challenge is already defined, and the "oob"
authorization type is equivalent to what is currently called a
"requirement." So I'm making better use of things we already define.

---

From: Eric Rescorla <[email-masked]>
Date: Sat, 29 Oct 2016 11:24:11 +1100

I'm basically with Richard here. I don't think that the change that Jacob
suggests is disastrous but it also doesn't seem very well-motivated and
given that we're trying to get to done, the bar for changes like this
should be fairly high. I don't think this rises to it.

---

From: Eric Rescorla <[email-masked]>
Date: Sat, 29 Oct 2016 11:51:29 +1100

---

From: Jacob Hoffman-Andrews <[email-masked]>
Date: Fri, 28 Oct 2016 17:55:29 -0700

Let me put it a different way: Offering the same value in multiple
places is really bad API design. I'm trying to fix that. We could remove
the inlined authorization status, leaving the requirements with an
inline status. But while we're at it, why not give requirements their
own addressable URLs? And what, fundamentally, is different about
requirements vs authorizations?

---

From: Richard Barnes <[email-masked]>
Date: Sat, 29 Oct 2016 12:26:14 -0400

> requirements vs authorizations?

Good, these are the right questions.  I do think there's a clear set of
concepts here:

* Requirement == What you have to do to get *this* *particular* certificate
issued
* Authz == You must do one of N things to prove you control the identifier
* Challenge == Do this to prove you control the identifier

The current structure is basically a conjunctive normal form:

App <= (Chall1 || Chall2) && (Chall3 || Chall4) && Req1

That is, there are two things that tell the client what to do, requirements
and challenges.  Authorizations allow the CA to offer groups of challenges
"or"ed together.

So if you wanted to simplify things, I guess the thing you would want to do
is define a general "or" grouping.  But I don't think even that's
worthwhile at this point, given that (1) the specialization of the authz
grouping means you don't have to repeat the identifier in each challenge,
and (2) we have no other requirements that require such an "or" grouping
right now.

As far as addressable URLs, I don't have a strong objection, but I don't
see any need for it.  If future types of requirements want to be
addressable, they can define pointers to addressable objects, like the
authz does right now.

--Richard