-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcertificates.go
281 lines (233 loc) · 9.58 KB
/
certificates.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"log"
"math/big"
"time"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/idna"
)
// The white list of domains for self signed certificates.
var allowedDomainsSelfSignedWhiteList map[string]bool = nil
// certCache holds the cached self signed TLS certificates.
var certCache map[string]*tls.Certificate = nil
// certCacheBytes holds the cached PEM-encoded Let's Encrypt TLS certificates.
var certCacheBytes map[string][]byte = nil
// Create a new autocert manager.
var m *autocert.Manager = nil
//
// ===========================================
//
// DirCache implements Cache using a directory on the local filesystem.
// If the directory does not exist, it will be created with 0700 permissions.
type DirCache string
// Get reads a certificate data from the specified file name.
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
cert := certCacheBytes[name]
if cert != nil {
return cert, nil
}
command := Command{Type: cmdGet, Name: name}
childToParentCh <- command
// Wait for a response message from the parentToChildCh channel or the timeout.
select {
case response := <-parentToChildCh:
// Handle the command from the child program.
switch response.Type {
case cmdGet:
// Handle the "get" command
if response.Name != name {
break
}
if len(response.Data) == 0 {
return nil, autocert.ErrCacheMiss
}
certCacheBytes[name] = response.Data
return response.Data, nil
default:
// Do nothing.
}
case <-time.After(5 * time.Second):
// Handle the timeout by returning an error.
return nil, errors.New("Timeout while waiting for response from parent")
}
return nil, autocert.ErrCacheMiss
}
// Put writes the certificate data to the specified file name.
// The file will be created with 0600 permissions.
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
if len(data) == 0 {
return errors.New("Could not store certificate: " + name)
}
certCacheBytes[name] = data
command := Command{Type: cmdPut, Name: name, Data: data}
childToParentCh <- command
return nil
}
// Delete removes the specified file name.
func (d DirCache) Delete(ctx context.Context, name string) error {
certCacheBytes[name] = nil
command := Command{Type: cmdDelete, Name: name, Data: nil}
childToParentCh <- command
return nil
}
//
// ===========================================
//
// initCertificates initializes the white list of domains for self signed certificates and also the cache for the self signed certificates.
func initCertificates(manager *autocert.Manager) {
m = manager
// Initialize the white list of domains for self signed certificates.
allowedDomainsSelfSignedWhiteList = make(map[string]bool, len(config.SelfSignedDomains))
for _, h := range config.SelfSignedDomains {
if h, err := idna.Lookup.ToASCII(h); err == nil {
allowedDomainsSelfSignedWhiteList[h] = true
}
}
// Initialize the cache for the self signed certificates.
certCache = make(map[string]*tls.Certificate, len(allowedDomainsSelfSignedWhiteList))
certCacheBytes = make(map[string][]byte, len(config.letsEncryptDomains))
// Initialize certificates before going to jail.
for serverName := range config.allDomains {
_, err := MyGetCertificate(&tls.ClientHelloInfo{ServerName: serverName})
if err != nil {
log.Println("Error when initializing certificate for:", serverName, "Error:", err)
continue
}
// // Parse the certificate from a PEM-encoded byte slice.
// if cert.Leaf == nil {
// parsedCert, err := x509.ParseCertificate(cert.Certificate[0])
// if err != nil {
// log.Fatal(err)
// }
// cert.Leaf = parsedCert
// }
// // Set the cache.
// certCache[serverName] = cert
}
}
// GetSelfSignedCertificate creates a self-signed TLS certificate.
func GetSelfSignedCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
name := hello.ServerName
if name == "" {
return nil, errors.New("self signed certificate: missing server name")
}
// Note that this conversion is necessary because some server names in the handshakes
// started by some clients (such as cURL) are not converted to Punycode, which will
// prevent us from obtaining certificates for them. In addition, we should also treat
// example.com and EXAMPLE.COM as equivalent and return the same certificate for them.
// Fortunately, this conversion also helped us deal with this kind of mixedcase problems.
//
// Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
// idna.Punycode.ToASCII (or just idna.ToASCII) here.
asciiName, err := idna.Lookup.ToASCII(name)
if err != nil {
return nil, fmt.Errorf("self signed certificate: server name contains invalid character: %s", name)
}
name = asciiName
// Check if the domain name is in the white list.
if !allowedDomainsSelfSignedWhiteList[name] {
return nil, errors.New("self signed certificate: server name not in white list: " + name)
}
// Generate a new private key.
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, fmt.Errorf("self signed certificate: failed to generate private key for %s: %v", name, err)
}
// Create a template for the certificate.
template := x509.Certificate{
SerialNumber: big.NewInt(412294),
Subject: pkix.Name{
CommonName: name,
Organization: []string{"Acme Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(config.CertificateExpiryRefreshThreshold + 14*24*time.Hour), // valid for two weeks plus durationToCertificateExpiryRefresh.
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
// Create the certificate.
publicKey := &privateKey.PublicKey
certificate, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
if err != nil {
return nil, fmt.Errorf("self signed certificate: failed to create certificate for %s: %v", name, err)
}
// Encode the private key and certificate in PEM format.
privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
certificatePEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate})
// Create a TLS certificate using the PEM-encoded bytes.
cert, err := tls.X509KeyPair(certificatePEM, privateKeyPEM)
if err != nil {
return nil, fmt.Errorf("self signed certificate: failed to create X509 key pair: %v", err)
}
return &cert, nil
}
// MyGetCertificate tries to fetch a certificate from Let's Encrypt and, if that fails,
// creates a self-signed certificate.
func MyGetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
// Return the self signed certificate if it was created before.
// Only try to switch back to Let's Encrypt, after the self signed certificate expires.
// Get and validate the domain name.
name := hello.ServerName
if name == "" {
return nil, errors.New("certificate: cannot get certificate because of missing server name")
}
// Convert the domain name to ASCII.
// Note that this conversion is necessary because some server names in the handshakes
// started by some clients (such as cURL) are not converted to Punycode, which will
// prevent us from obtaining certificates for them. In addition, we should also treat
// example.com and EXAMPLE.COM as equivalent and return the same certificate for them.
// Fortunately, this conversion also helped us deal with this kind of mixedcase problems.
//
// Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
// idna.Punycode.ToASCII (or just idna.ToASCII) here.
name, err := idna.Lookup.ToASCII(name)
if err != nil {
return nil, fmt.Errorf("certificate: server name contains invalid character: %s", name)
}
// Check the cache for an existing certificate.
cachedCert := certCache[name]
if cachedCert != nil {
// Parse the certificate from a PEM-encoded byte slice if not already parsed.
if cachedCert.Leaf == nil {
parsedCert, err := x509.ParseCertificate(cachedCert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("certificate: error parsing cached certificate: %v", err)
}
cachedCert.Leaf = parsedCert
}
// Check certificate expiration.
if time.Until(cachedCert.Leaf.NotAfter) >= config.CertificateExpiryRefreshThreshold {
// Certificate is still valid.
return cachedCert, nil
}
// Clear expired certificate from cache.
certCache[name] = nil
log.Printf("certificate: cert for %s expired or about to expire, fetching new certificate", name)
}
// Fetch a new certificate from Let's Encrypt.
cert, err := m.GetCertificate(hello)
if err == nil {
log.Printf("certificate: got Let's Encrypt certificate for: %s", name)
certCache[name] = cert
return cert, nil
}
log.Printf("certificate: Let's Encrypt error for %s: %v, creating self-signed certificate", name, err)
// Create a self-signed certificate if fetching from Let's Encrypt failed.
cert, err = GetSelfSignedCertificate(hello)
if err != nil {
return nil, fmt.Errorf("certificate: failed to create self-signed certificate: %v", err)
}
log.Printf("certificate: created self-signed certificate for: %s", name)
certCache[name] = cert
return cert, nil
}