tls_alpn_challenge.go raw
1 package tlsalpn01
2
3 import (
4 "crypto/rsa"
5 "crypto/sha256"
6 "crypto/tls"
7 "crypto/x509/pkix"
8 "encoding/asn1"
9 "fmt"
10 "time"
11
12 "github.com/go-acme/lego/v4/acme"
13 "github.com/go-acme/lego/v4/acme/api"
14 "github.com/go-acme/lego/v4/certcrypto"
15 "github.com/go-acme/lego/v4/challenge"
16 "github.com/go-acme/lego/v4/log"
17 )
18
19 // idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
20 // Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
21 var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
22
23 type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
24
25 type ChallengeOption func(*Challenge) error
26
27 // SetDelay sets a delay between the start of the TLS listener and the challenge validation.
28 func SetDelay(delay time.Duration) ChallengeOption {
29 return func(chlg *Challenge) error {
30 chlg.delay = delay
31 return nil
32 }
33 }
34
35 type Challenge struct {
36 core *api.Core
37 validate ValidateFunc
38 provider challenge.Provider
39 delay time.Duration
40 }
41
42 func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
43 chlg := &Challenge{
44 core: core,
45 validate: validate,
46 provider: provider,
47 }
48
49 for _, opt := range opts {
50 err := opt(chlg)
51 if err != nil {
52 log.Infof("challenge option error: %v", err)
53 }
54 }
55
56 return chlg
57 }
58
59 func (c *Challenge) SetProvider(provider challenge.Provider) {
60 c.provider = provider
61 }
62
63 // Solve manages the provider to validate and solve the challenge.
64 func (c *Challenge) Solve(authz acme.Authorization) error {
65 domain := authz.Identifier.Value
66 log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
67
68 chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
69 if err != nil {
70 return err
71 }
72
73 // Generate the Key Authorization for the challenge
74 keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
75 if err != nil {
76 return err
77 }
78
79 err = c.provider.Present(domain, chlng.Token, keyAuth)
80 if err != nil {
81 return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
82 }
83
84 defer func() {
85 err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
86 if err != nil {
87 log.Warnf("[%s] acme: cleaning up failed: %v", challenge.GetTargetedDomain(authz), err)
88 }
89 }()
90
91 if c.delay > 0 {
92 time.Sleep(c.delay)
93 }
94
95 chlng.KeyAuthorization = keyAuth
96
97 return c.validate(c.core, domain, chlng)
98 }
99
100 // ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
101 // and domain name for the `tls-alpn-01` challenge.
102 func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
103 // Compute the SHA-256 digest of the key authorization.
104 zBytes := sha256.Sum256([]byte(keyAuth))
105
106 value, err := asn1.Marshal(zBytes[:sha256.Size])
107 if err != nil {
108 return nil, nil, err
109 }
110
111 // Add the keyAuth digest as the acmeValidation-v1 extension
112 // (marked as critical such that it won't be used by non-ACME software).
113 // Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
114 extensions := []pkix.Extension{
115 {
116 Id: idPeAcmeIdentifierV1,
117 Critical: true,
118 Value: value,
119 },
120 }
121
122 // Generate a new RSA key for the certificates.
123 tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
124 if err != nil {
125 return nil, nil, err
126 }
127
128 rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
129
130 // Generate the PEM certificate using the provided private key, domain, and extra extensions.
131 tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
132 if err != nil {
133 return nil, nil, err
134 }
135
136 // Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
137 rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
138
139 return tempCertPEM, rsaPrivatePEM, nil
140 }
141
142 // ChallengeCert returns a certificate with the acmeValidation-v1 extension
143 // and domain name for the `tls-alpn-01` challenge.
144 func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
145 tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
146 if err != nil {
147 return nil, err
148 }
149
150 cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
151 if err != nil {
152 return nil, err
153 }
154
155 return &cert, nil
156 }
157