-
-
Notifications
You must be signed in to change notification settings - Fork 38
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
Make simp_le work with ACME v2 endpoints #119
Conversation
@buchdag 👋 I haven't read through your diff but I think I can give some high level feedback.
In ACME v1 there was only one way to compose a JWS for an ACME request: You signed a JSON body and you put your whole public JWK into the protected headers of the JWS. In RFC 8555 there's also a different way, used for the majority of requests. The idea is that after you've created an ACME account with So in summary, RFC 8555 has two sorts of JWS:
Based on your error it looks like your code is probably using the old kind of JWS for requests types expecting the new kind. RFC 8555 Section 6.2 has most of the gory details: https://tools.ietf.org/html/rfc8555#section-6.2 If it helps the Let's Encrypt server-side code for validating request JWS is mostly located here: https://github.com/letsencrypt/boulder/blob/3de2831c329932a58814110102df884d3d576e5f/wfe2/verify.go
Unfortunately there are a handful of ACME implementations I know of that targetted "ACME v1". As one example at the time of writing BuyPass's production ACME endpoint is not RFC 8555 and instead targeted Certbot compatibility. It's up to you whether that matters to Hope that helps! Happy to answer more q's if you have them. |
Oh! One other nice advantage to being ACME v2 only: You can implement integration tests using Pebble instead of having to deal with the complexity of Boulder or using the staging environment and real validations. Pebble has no ACME v1 implementation so you would either have untested code or need to use a full Boulder stack. |
Okay I think I got the Let say I register my new account this way : net = acme.ClientNetwork(key=key, args.user_agent)
directory = messages.Directory.from_json(net.get(args.server).json())
client = acme.ClientV2(directory, net=net)
reg = messages.NewRegistration.from_data(email=args.email)
reg = reg.update(terms_of_service_agreed=True)
client.new_account(reg) If I understood client.net.account This should be stored somewhere when the account is persisted to disk then passed along to |
Yup! That sounds right to me. Unfortunately my experience with the |
Ok, so just to be sure I'm on the right track, this is from a real account I just created on the Let's Encrypt ACME v2 staging endpoint : >>> print(client.net.account.json_dumps_pretty())
{
"body": {
"contact": [
"mailto:[email protected]"
],
"key": {
"e": "AQAB",
"kty": "RSA",
"n": "[...]"
},
"status": "valid"
},
"terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
"uri": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/9582881"
} The |
Yup! |
Thanks for taking the initiative on this. I'm in favor of switching over to v2-only; I don't think it's worth the trouble to support both protocols. I'll have a closer look at the patch this evening. |
Okay, I just added persistence of the ACME v2 account registration info by using / extending the existing Reusing a previously persisted ACME Tests haven't been updated yet, I'll take a look at that next. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High level looks reasonable so far. Obviously tests need fixing. Also make sure you update the Changelog, including describing the role of the new registration file.
Aha, that makes sense.
|
I confirm that after ee66ae6 cert issuance against a v2 endpoint still works and that calling The revocation doesn't work yet:
The only other part I still have issue with is the
|
Quoting Nicolas Duchon (2019-06-13 16:17:50)
The only other part I still have issue with is the ExternalIOPlugin()
class and the corresponding tests. To be very honest I hate this stuff,
I think it's coded in a very unclear way, the feature itself is almost
undocumented and I doubt anyone ever really used it. If it was just up
to me I'd happily just trash it.
I 100% agree; if you want to do the leg work to strip it out I'm totally
on board. The only reason I haven't is that since I picked it up there
hasn't been enough need to change things for it to be worth the trouble.
|
@cpu given the following from RFC8555:
should we allow users to not persist the account object and just fetch the |
Revocation is fixed, edit: yeah sure bro |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good to me, just the two inline comments, and the changelog still needs updating.
@@ -50,8 +50,7 @@ Manifest | |||
|
|||
9. Flexible storage capabilities. Built-in | |||
``simp_le -f fullchain.pem -f key.pem``, | |||
``simp_le -f chain.pem -f cert.pem -f key.pem``, etc. Extensions | |||
through ``simp_le -f external.sh``. | |||
``simp_le -f chain.pem -f cert.pem -f key.pem``, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just remove this list item entirely; without ExternalIOPlugin
I don't think it's fair to say that we have "flexible storage capabilities."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is still a bit of flexibility regarding how the private key / cert / chain are stored to disk. Is it enough to keep the line as-is or do you still prefer it to be removed ?
@buchdag You could, but I don't know that it's especially worthwhile. You have to persist the account key to be able to do that and so I'd probably be inclined to persist the whole account object. It also makes it slightly easier for an end-user to know their ACME account ID (checking an on-disk config somewhere). It's occasionally useful to know that (e.g. for requesting server-side rate limit adjustments). |
@cpu what I had in mind was more a way to still be able to use an existing account key if the persisted account object somehow got deleted. Does it make sense this way ?
|
@buchdag Ahhh! That makes sense, sorry I misunderstood. Yes, recovering the key ID in that scenario makes sense. |
@zenhack I'm still stuck with
For the following function, already refactored to use fewer local variables: def persist_new_data(args, existing_data):
"""Issue and persist new key/cert/chain."""
roots = compute_roots(args.vhosts, args.default_root)
logger.debug('Computed roots: %r', roots)
client = registered_client(
args, existing_data.account_key, existing_data.account_reg)
if args.reuse_key and existing_data.key is not None:
logger.info('Reusing existing certificate private key')
key = existing_data.key
else:
logger.info('Generating new certificate private key')
key = ComparablePKey(gen_pkey(args.cert_key_size))
csr = gen_csr(
key.wrapped, [vhost.name.encode() for vhost in args.vhosts]
)
csr = OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr
)
order = client.new_order(csr)
authorizations = dict(
[authorization.body.identifier.value, authorization]
for authorization in order.authorizations
)
if any(supported_challb(auth) is None
for auth in six.itervalues(authorizations)):
raise Error('CA did not offer http-01-only challenge combo. '
'This client is unable to solve any other challenges.')
for name, auth in six.iteritems(authorizations):
challb = supported_challb(auth)
response, validation = challb.response_and_validation(client.net.key)
save_validation(roots[name], challb, validation)
client.answer_challenge(challb, response)
try:
order = finalize_order(client, order)
pems = list(split_pems(order.fullchain_pem))
persist_data(args, existing_data, new_data=IOPlugin.Data(
account_key=client.net.key,
account_reg=client.net.account,
key=key,
cert=jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pems[0])),
chain=[
jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pem))
for pem in pems[1:]
],
))
except Error as error:
persist_data(args, existing_data, new_data=IOPlugin.Data(
account_key=client.net.key,
account_reg=client.net.account,
key=None,
cert=None,
chain=None,
))
raise error
finally:
for name, auth in six.iteritems(authorizations):
challb = supported_challb(auth)
remove_validation(roots[name], challb) and
caused by this dict comprehension: authorizations = dict(
[authorization.body.identifier.value, authorization]
for authorization in order.authorizations
) This is Python 2 specific, using
Given that Python 2 will be EOL'd in six months, should we target linting with Python 3 instead ? |
I've been rebasing this PR and making it Python 3 only on another branch, tests are passing ok : https://travis-ci.org/buchdag/simp_le/builds/547327956 BTW Python 3.4 was EOL'd a few months ago. |
I'm fine with dropping python 2 support (and 3.4). Make sure to update the classifiers in setup.py |
Ok two last question and I think I'll have everything covered:
|
Let's go with account_reg. Do the rebase first I guess. |
make the function work with v2 endpoints that don't offer challenge combinations
+ recover account registration if missing
Okay, PR rebased, tests are passing, doc updated, it should be down to that last change request. |
I guess I don't feel that strongly. Merging. I'll dogfood it later this week and then tag a release. |
This is a work in progress aiming to switch
simp_le
from ACME v1 compatibility to ACME v2 compatibility.At the moment it is half working : you can obtain a cert from an ACME v2 endpoint, but if you try again with an existing ACME account key, it will fail with the ACME v2 enpoint returning
I believe it has something to do with the fact that in its current form
simp_le
only persist the account private key but I did not understand what should be done yet (either by walking through RFC 8555 again or looking at the waycertbot
does this). @cpu I might need your help there.Once this is fixed, the test suite should pass minus one or two pylint issues.
In the current form of this PR there is no backward compatibility with ACME v1 for the following reasons:
However adding backward compatibility with ACME v1 endpoints should be entirely doable (and I did most of the work on a previous draft of this PR).