This repository has been archived by the owner on Jul 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
signer.go
130 lines (116 loc) · 3.59 KB
/
signer.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
// See also: https://github.com/elazarl/goproxy/pull/314
// And: https://github.com/elazarl/goproxy/pull/256 - This could be important; there's an fd leak
package inkfish
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"net"
"sort"
"sync"
"time"
)
const signerVersion = ":inkfish1"
const hardLifetime = 5 * time.Hour // Published cert lifetime, from generation
const softLifetime = 4 * time.Hour // Refresh after certificate is "this old"
const allowedClockDrift = 5 * time.Minute // Issue this many minutes in past to forgive skew
const rsaKeyBits = 2048
type CertSigner struct {
CA *tls.Certificate
TlsConfig *tls.Config
CertCache map[string]tls.Certificate
CertCacheMutex *sync.Mutex
CertHardLifetime time.Duration
CertSoftLifetime time.Duration
AllowedClockDrift time.Duration
Now func() time.Time
}
func NewCertSigner(ca *tls.Certificate) *CertSigner {
return &CertSigner{
CA: ca,
TlsConfig: &tls.Config{
InsecureSkipVerify: false,
},
CertCache: map[string]tls.Certificate{},
CertCacheMutex: &sync.Mutex{},
CertHardLifetime: hardLifetime,
CertSoftLifetime: softLifetime,
AllowedClockDrift: allowedClockDrift,
Now: time.Now,
}
}
func hashSorted(lst []string) []byte {
c := make([]string, len(lst))
copy(c, lst)
sort.Strings(c)
h := sha256.New()
for _, s := range c {
h.Write([]byte(s + ","))
}
return h.Sum(nil)
}
func (certSigner *CertSigner) needsRefresh(cert *x509.Certificate, now time.Time) bool {
return cert.NotAfter.Sub(now) < certSigner.CertSoftLifetime
}
func (certSigner *CertSigner) signHost(hosts []string) (cert tls.Certificate, err error) {
var x509ca *x509.Certificate
// Fast path; is it cached?
hash := hashSorted(append(hosts, signerVersion))
certSigner.CertCacheMutex.Lock()
defer certSigner.CertCacheMutex.Unlock()
cachedCert, found := certSigner.CertCache[string(hash)]
now := certSigner.Now()
if found && !certSigner.needsRefresh(cachedCert.Leaf, now) {
return cachedCert, nil
}
// Slow path; the cert is either not there, or expiring soon.
if x509ca, err = x509.ParseCertificate(certSigner.CA.Certificate[0]); err != nil {
return
}
randomSerial := make([]byte, 20)
_, err = rand.Read(randomSerial)
if err != nil {
panic(err)
}
serial := new(big.Int)
serial.SetBytes(randomSerial)
x509cert := x509.Certificate{
SerialNumber: serial,
Issuer: x509ca.Subject,
Subject: pkix.Name{
Organization: []string{"Inkfish MITM Proxy"},
},
NotBefore: now.Add(-certSigner.AllowedClockDrift),
NotAfter: now.Add(certSigner.CertHardLifetime),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
x509cert.IPAddresses = append(x509cert.IPAddresses, ip)
} else {
x509cert.DNSNames = append(x509cert.DNSNames, h)
x509cert.Subject.CommonName = h
}
}
var certpriv *rsa.PrivateKey
if certpriv, err = rsa.GenerateKey(rand.Reader, rsaKeyBits); err != nil {
return
}
var derBytes []byte
if derBytes, err = x509.CreateCertificate(rand.Reader, &x509cert, x509ca, &certpriv.PublicKey, certSigner.CA.PrivateKey); err != nil {
return
}
leafCert := tls.Certificate{
Certificate: [][]byte{derBytes, certSigner.CA.Certificate[0]},
PrivateKey: certpriv,
Leaf: &x509cert,
}
certSigner.CertCache[string(hash)] = leafCert
return leafCert, nil
}