Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/crypto/acme: Client.AcceptWithPayload to support device attestation extension #68674

Closed
zhsh opened this issue Jul 31, 2024 · 20 comments
Closed
Labels
Proposal Proposal-Accepted Proposal-Crypto Proposal related to crypto packages or other security issues
Milestone

Comments

@zhsh
Copy link

zhsh commented Jul 31, 2024

Proposal Details

The current RFC 8555, section 7.5.1 "Responding to Challenges" (https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.1) states that the client should send an empty JSON body ("{}") to the challenge URL. That is what Client.Accept() method does: https://github.com/golang/crypto/blob/3375612bf41a8cdb0cdfdc21e6ca2c7ae0f764b5/acme/acme.go#L517

A new extension to the ACME protocol is proposed: https://datatracker.ietf.org/doc/draft-acme-device-attest/
Based on the recent IETF meetings, the proposal is likely to be accepted.

To support the new extension, the ACME client should be able to send a non-empty JSON body:

A client responds with the response object containing the WebAuthn attestation object in the "attObj" field to acknowledge that the challenge can be validated by the server.

This proposal is for adding a new method to acme.Client (perhaps client.AcceptWithPayload) that is similar to client.Accept but allows to pass a non-default payload.

@zhsh zhsh added the Proposal label Jul 31, 2024
@gopherbot gopherbot added this to the Proposal milestone Jul 31, 2024
@zhsh
Copy link
Author

zhsh commented Jul 31, 2024

A quick note: Smallstep's implementation of ACME client and server already supports the new extension.

Smallstep client code that supports both the default and non-default payloads: https://github.com/smallstep/certificates/blob/1b2d999e4607bbe4796dce2a0f0f3c7a29cec463/ca/acmeClient.go#L243-L267

@ianlancetaylor ianlancetaylor moved this to Incoming in Proposals Jul 31, 2024
@ianlancetaylor ianlancetaylor added the Proposal-Crypto Proposal related to crypto packages or other security issues label Jul 31, 2024
@ianlancetaylor
Copy link
Member

CC @golang/security

@seankhliao seankhliao changed the title proposal: x/crypto/acme: Add an AcceptWithPayload method to Client proposal: x/crypto/acme: Client.AcceptWithPayload to support device attestation extension Aug 7, 2024
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/608975 mentions this issue: acme: support challenges that require the ACME client to send a non-empty JSON body in a response to the challenge.

@rsc rsc moved this from Incoming to Active in Proposals Aug 29, 2024
@rsc
Copy link
Contributor

rsc commented Aug 29, 2024

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Sep 4, 2024

Would it make sense to add a field to the Challenge type instead of having a whole new method?

@rolandshoemaker
Copy link
Member

We could also avoid adding a field entirely, and add an internal check for Challenge.Type == "hardware-module". This would be somewhat opaque, but doesn't require an API change.

@zhsh
Copy link
Author

zhsh commented Sep 5, 2024

Would it make sense to add a field to the Challenge type instead of having a whole new method?

This works too.

If I understand correctly, the change will look like the below. Does it make sense?

  1. Add a field to Challenge.
type Challenge struct {
  ...
  // If a challenge requires a non-empty response (e.g. "device-attest-01"),
  // the client must populate Response
  Response sometype
}
  1. Code that uses the new challenge type will look like this:
  // retrieve a challenge from the server
  var challenge acme.Challenge
  challenge = ...

  // construct the response from challenge.Token
  challenge.Response = ...

  // inform the server that the challenge is ready
  acmeClient.Accept(ctx, challenge)
  1. Client.Accept() is modified to handle non-empty response:
  if chal.Response is not initialized {
    res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), ...)
  } else {
    res, err := c.post(ctx, nil, chal.URI, chal.Response, ...)
  }

@zhsh
Copy link
Author

zhsh commented Sep 5, 2024

We could also avoid adding a field entirely, and add an internal check for Challenge.Type == "hardware-module". This would be somewhat opaque, but doesn't require an API change.

I may be missing something here... How does the client pass the response to the server in this case?

@zhsh
Copy link
Author

zhsh commented Sep 5, 2024

Thank you for the review and suggestions!

@djm-google
Copy link

BTW this ACME extension as specified in the draft is already widely implemented and deployed in iOS and MacOS, so even if the draft evolves further (which is unlikely; it's AFAIK at the IETF last-call stage), there will still be a desire to support it for this large client base.

@rolandshoemaker
Copy link
Member

I may be missing something here... How does the client pass the response to the server in this case?

Whoops, my bad I was thinking of the wrong extension. Adding a field to Challenge makes the most sense. The description in #68674 (comment) sounds accurate to me.

@rsc
Copy link
Contributor

rsc commented Sep 11, 2024

OK, so it sounds like maybe adding

// Payload is the JSON-formatted payload that the client sends
// to the server to indicate it is ready to respond to the challenge.
// When unset, it defaults to an empty JSON object: {}.
Payload json.Message

to Challenge is all we need?

@zhsh
Copy link
Author

zhsh commented Sep 17, 2024

OK, so it sounds like maybe adding

Payload json.Message

to Challenge is all we need?

This looks like the best path forward.
Just a minor clarification: should the type be json.RawMessage?

@aclements
Copy link
Member

The current proposal is to add the following field to acme.Challenge:

// Payload is the JSON-formatted payload that the client sends
// to the server to indicate it is ready to respond to the challenge.
// When unset, it defaults to an empty JSON object: {}.
Payload json.RawMessage

Is that right?

@aclements
Copy link
Member

I would like for the doc comment to be a little more prescriptive. Perhaps point at the extension spec and/or suggest why one would want to set this field. Could someone more familiar with the uses of this write that text?

Also, I see the extension proposal hasn't made any visible progress since version 1 was uploaded on 2023-07-24. Do we expect this to actually get ratified? Alternatively, is it in widespread enough use that it's de facto standard?

@rolandshoemaker
Copy link
Member

I would like for the doc comment to be a little more prescriptive. Perhaps point at the extension spec and/or suggest why one would want to set this field. Could someone more familiar with the uses of this write that text?

Yup I can add some additional text.

Also, I see the extension proposal hasn't made any visible progress since version 1 was uploaded on 2023-07-24. Do we expect this to actually get ratified? Alternatively, is it in widespread enough use that it's de facto standard?

It looks like the most recent revision was sent last month (draft-acme-device-attest-03), given there has been minimal feedback, it's likely to get moved to last-call at the next IETF meeting in November. The acme WG has taken a somewhat more reasonable approach to specification standardization, usually waiting for at least a couple working implementations before fully ossifying things.

@aclements
Copy link
Member

Have all remaining concerns about this proposal been addressed?

Add the following field to acme.Challenge:

// Payload is the JSON-formatted payload that the client sends
// to the server to indicate it is ready to respond to the challenge.
// When unset, it defaults to an empty JSON object: {}.
Payload json.RawMessage

@djm-google
Copy link

Alternatively, is it in widespread enough use that it's de facto standard?

Yes, the current draft is already implemented by IOS and MacOS.

@aclements
Copy link
Member

Based on the discussion above, this proposal seems like a likely accept.

Add the following field to acme.Challenge:

// Payload is the JSON-formatted payload that the client sends
// to the server to indicate it is ready to respond to the challenge.
// When unset, it defaults to an empty JSON object: {}.
Payload json.RawMessage

@aclements aclements moved this from Active to Likely Accept in Proposals Oct 23, 2024
@aclements aclements moved this from Likely Accept to Accepted in Proposals Oct 30, 2024
@aclements
Copy link
Member

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.

Add the following field to acme.Challenge:

// Payload is the JSON-formatted payload that the client sends
// to the server to indicate it is ready to respond to the challenge.
// When unset, it defaults to an empty JSON object: {}.
Payload json.RawMessage

@aclements aclements changed the title proposal: x/crypto/acme: Client.AcceptWithPayload to support device attestation extension x/crypto/acme: Client.AcceptWithPayload to support device attestation extension Oct 30, 2024
@aclements aclements modified the milestones: Proposal, Backlog Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Proposal Proposal-Accepted Proposal-Crypto Proposal related to crypto packages or other security issues
Projects
Status: Accepted
Development

No branches or pull requests

7 participants