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

Ability to supply a CSR for signing. #196

Open
yombo opened this issue Jul 22, 2020 · 5 comments
Open

Ability to supply a CSR for signing. #196

yombo opened this issue Jul 22, 2020 · 5 comments

Comments

@yombo
Copy link

yombo commented Jul 22, 2020

What version of sewer are you using?

0.8.2

What is it that you would like to propose to add/remove/change?

Add ability to supply a CSR as a parameter to sewer.client init()

At:

def __init__(

def __init__(
        self,
        domain_name,
        dns_class=None,
        domain_alt_names=None,
        contact_email=None,
        account_key=None,
        certificate_key=None,
        bits=2048,
        digest="sha256",
        provider=None,
        csr=None,  # Add this.
        ACME_REQUEST_TIMEOUT=7,
        ACME_AUTH_STATUS_WAIT_PERIOD=8,
        ACME_AUTH_STATUS_MAX_CHECKS=3,
        ACME_DIRECTORY_URL=ACME_DIRECTORY_URL_PRODUCTION,
        ACME_VERIFY=True,
        LOG_LEVEL="INFO",
    ):

Then, a but down:

self.csr = self.create_csr()

Add something like:

if supplied_csr is not None:
  self.csr = csr
else:
  self.csr = self.create_csr()

Why do you want to add/remove/change that?

For security, we don't want access to the server's private key, so we just get their CSR. I want to be able to process the CSR using sewer. Without this change, cannot process CSRs.

How do you want to go about adding/removing/changing that?

See above.

@mmaney
Copy link
Collaborator

mmaney commented Jul 23, 2020

@yombo Have you got a process working with those changes, or is this all hypothetical? It might just be a failure of imagination on my part, but I can't see what the point of this is - it hides one private key, yes, but the entity it is hidden from has the account key and the means to validate any key pair it wants, as well as carrying out various DOS scenarios.

@yombo
Copy link
Author

yombo commented Jul 24, 2020

We get CSR requests thru automation from clients. These clients don't allow connections externally, so LetsEncrypt can't validate that way. They also don't have access to DNS. We do everything for them using DNS.

We get the CSR and send it to LetsEncrypt. When it's done, we send the signed cert back to the client. This is all handled automatically, including error handling. This way we don't get their private key, and they can get a signed cert fairly quickly.

Sewer should assume that if a CSR is provided, it's already been validated for domains and such. But a very basic check could be performed within sewer to ensure the CSR is at minimally valid.

from OpenSSL import crypto
crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_text)

Due to our requirements, we just made a private fork and customized it for our use case. :-) We have it integrated with Twisted Matrix & AMQP. Our customizations won't make sense to make for this project, but having the ability to supply a CSR using sewer might be beneficial to others.

And yes. Since I created this issue until now, a mutant version of sewer for Twisted is working perfectly fine with a supplied CSR. Of course, it's been converted to use yields and inlinecallbacks. This includes changing all the sleeps and such to keep it async.

@mmaney
Copy link
Collaborator

mmaney commented Jul 24, 2020

So there's really no point in adding this to upstream sewer - this is only a tiny part of the local changes you're carrying, and it's of no use without other parts of your local system.

@mmaney
Copy link
Collaborator

mmaney commented Aug 29, 2020

@yombo Although I can't see adding this special-use feature now, you might want to keep an eye on the 0.9 branch. The refactoring of Client is going to break things up in ways that ought to make it much better suited for your use as a library of parts.

Well, it will be 0.9 in a bit. Right now the crypto branch is the first piece that's been pulled out from the god-object Client. :-)

@mmaney
Copy link
Collaborator

mmaney commented Nov 28, 2020

@yombo Everything takes longer, though I can't see how I could say it costs more to complete the old saw now that I'm retired. Anyway, 0.9 is evolving, and it looks like CSR injection has indeed fallen out along the way. This is the current (partial) script I've been using for live testing the pieces so far assembled:

with ExitStack() as stack:
    svc = service.AcmeService(LE_STAGING_URL)
#    svc = service.AcmeService(BUYPASS_STAGING_URL)

    acct = account.create("P-256", svc, contacts=["mailto:sewertest@two14.net"], tos_agreed=True)
    # this might want to be unregistered if there's a later problem?

    print("New account key (%s) registered:\n    KID: %s" % (acct.ak.thumbprint(), acct.kid()))

    cert_key = key.create("rsa3072")

    req = csr.build(cn=CN, san=SAN, ak=cert_key)
    print("New req for %s, SAN = %s" % (req.subject(), req.san()))

    ord = acct.new_order(req)
    print("New order: %s" % ord)
    # there is no way to cancel an order - LE's advice is "just abandon it"

This script is more or less equivalent to the old get_certificate, which I'm sure you just tore up and threw away for your async version. Speaking of async, were you able to just hack on the GET &tc. methods, or did it require deeper surgery? It would be nearly as easy to replace the synchronous requests wrappers in the current 0.9 code as it would be to use an externally provided CSR in the above.

I can't say that I made any of these things work as described because of our discussion above, but it certainly had some influence on my thinking. But really, this mostly just fell out of reifying the various "records" described in rfc8555 into Python objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants