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

send different certificate if acme-tls/1 negotated with alpn #26

Closed
wants to merge 1 commit into from

Conversation

dholth
Copy link

@dholth dholth commented Mar 22, 2019

As you probably know letsencrypt requires alpn negotiation instead of just SNI these days.

This change intercepts alpn negotiation so that acme-tls/1 is always used if the client supports it. Then txsni chooses certificates from a second mapping, currently just an alpn/ directory underneath the default mapping, to use for that connection. When letsencrypt sees that special cert it knows you control the domain.

So if you have already generated the special certificate and placed it in acme/hostname.pem, txsni will be able to make letsencrypt happy. Then your ACME client can finish issuing you a new cert.

I expect to use this with dehydrated, the shell script acme client, and possibly with a new mapping object that can do dehydrated's separate key / cert layout. There's not enough here for txsni's "even the first request succeeds after waiting for the new certificate" but it should be really handy for development.

@dholth
Copy link
Author

dholth commented Mar 22, 2019

fixes #25

@codecov-io
Copy link

codecov-io commented Mar 22, 2019

Codecov Report

Merging #26 into master will decrease coverage by 0.79%.
The diff coverage is 80%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master     #26     +/-   ##
========================================
- Coverage      95%   94.2%   -0.8%     
========================================
  Files           6       6             
  Lines         400     414     +14     
  Branches       28      30      +2     
========================================
+ Hits          380     390     +10     
- Misses         12      15      +3     
- Partials        8       9      +1
Impacted Files Coverage Δ
txsni/parser.py 100% <100%> (ø) ⬆️
txsni/snimap.py 86.6% <76.47%> (-2.4%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5014c14...c9650a7. Read the comment docs.

@dholth
Copy link
Author

dholth commented Mar 23, 2019

It's unfortunate that we have to choose a certificate for our hostname before we can immediately switch to the acme one

@dholth
Copy link
Author

dholth commented Mar 23, 2019

How dehydrated stores its certificates. letsencrypt doesn't agree with us yet, saying "detail": "remote error: tls: no application protocol"

import pem.twisted


class DehydratedMap(object):
    """
    Dehydrated's certs, in per-hostname subdirectories
    """
    def __init__(self, directoryPath):
        self.directoryPath = directoryPath

    def __getitem__(self, hostname):
        if hostname is None:
            hostname = b"DEFAULT"
        hostPath = self.directoryPath.child(hostname)
        keyPath = hostPath.child('privkey.pem')
        fullchainPath = hostPath.child('fullchain.pem')
        print('regular', hostname, keyPath, fullchainPath)
        if keyPath.isfile() and fullchainPath.isfile():
            print('regular', hostname, 'found')
            return pem.twisted.certificateOptionsFromFiles(keyPath.path, fullchainPath.path)
        else:
            raise KeyError("no pem file for " + hostname.decode('latin1'))


class DehydratedAcmeMap(object):
    """
    Dehydrated's ALPN certs, in files
    """
    def __init__(self, directoryPath):
        self.directoryPath = directoryPath

    def __getitem__(self, hostname):
        print('acme', hostname)
        certPath = self.directoryPath.child(hostname).siblingExtension(".crt.pem")
        keyPath = self.directoryPath.child(hostname).siblingExtension(".key.pem")
        if certPath.isfile() and keyPath.isfile():
            print('acme', hostname, 'found')
            return pem.twisted.certificateOptionsFromFiles(keyPath.path, certPath.path)
        else:
            raise KeyError("no alpn cert for " + hostname.decode('latin1'))


@dholth
Copy link
Author

dholth commented Mar 23, 2019

It at least works if the alpn certificate is the only one. SNIMap(acme_mapping)

@dholth
Copy link
Author

dholth commented Mar 24, 2019

I've cracked it. We already use bio so Python sees all the bytes, and we already proxy Connection. Trap the first call to bio to search for alpn in the first packet, if True load certificates from a different directory during SNI. Cheesy!

@dholth
Copy link
Author

dholth commented Mar 24, 2019

will replace

@dholth dholth closed this Mar 24, 2019
@glyph
Copy link
Owner

glyph commented Aug 3, 2019

@dholth What replaced this?

@dholth
Copy link
Author

dholth commented Aug 4, 2019 via email

@glyph
Copy link
Owner

glyph commented Aug 4, 2019

Thanks.

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

Successfully merging this pull request may close these issues.

None yet

3 participants