mic~ proxy for JA4-TLS fingerprinting evasion |
| git clone https://github.com/djnnvx/mic.git |
| log | files | refs | archive | README | LICENSE |
ca.go (5318B)
1 package proxy 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "crypto/tls" 8 "crypto/x509" 9 "crypto/x509/pkix" 10 "encoding/pem" 11 "fmt" 12 "math/big" 13 "net" 14 "os" 15 "sync" 16 "time" 17 ) 18 19 const ( 20 leafLifetime = 24 * time.Hour 21 // Re-issue this long before expiry so a leaf cannot expire mid-handshake. 22 leafRenewMargin = time.Minute 23 ) 24 25 type cachedLeaf struct { 26 cert tls.Certificate 27 notAfter time.Time 28 } 29 30 // LocalCA is a self-signed CA used to issue per-host certificates for 31 // MitM TLS interception in client-front mode. 32 type LocalCA struct { 33 cert *x509.Certificate 34 key *ecdsa.PrivateKey 35 derCert []byte // raw DER, appended as chain in issued certs 36 37 mu sync.Mutex 38 cache map[string]cachedLeaf 39 } 40 41 func GenerateCA() (*LocalCA, error) { 42 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 43 if err != nil { 44 return nil, err 45 } 46 return buildCA(key) 47 } 48 49 // LoadOrGenerateCA loads the CA from certPath + keyPath. If the files do not 50 // exist it generates a new CA and writes it to those paths. Pass empty strings 51 // for both to get an ephemeral in-memory CA. 52 func LoadOrGenerateCA(certPath, keyPath string) (*LocalCA, error) { 53 if certPath == "" && keyPath == "" { 54 return GenerateCA() 55 } 56 if certPath == "" || keyPath == "" { 57 return nil, fmt.Errorf("CA cert path and key path must both be set or both be empty") 58 } 59 60 ca, err := loadCA(certPath, keyPath) 61 if err == nil { 62 return ca, nil 63 } 64 if !os.IsNotExist(err) { 65 return nil, fmt.Errorf("loading CA: %w", err) 66 } 67 68 ca, err = GenerateCA() 69 if err != nil { 70 return nil, err 71 } 72 if err := ca.Save(certPath, keyPath); err != nil { 73 return nil, fmt.Errorf("saving CA: %w", err) 74 } 75 return ca, nil 76 } 77 78 func loadCA(certPath, keyPath string) (*LocalCA, error) { 79 pair, err := tls.LoadX509KeyPair(certPath, keyPath) 80 if err != nil { 81 return nil, err 82 } 83 cert, err := x509.ParseCertificate(pair.Certificate[0]) 84 if err != nil { 85 return nil, err 86 } 87 key, ok := pair.PrivateKey.(*ecdsa.PrivateKey) 88 if !ok { 89 return nil, fmt.Errorf("CA private key must be ECDSA") 90 } 91 return &LocalCA{ 92 cert: cert, 93 key: key, 94 derCert: pair.Certificate[0], 95 cache: make(map[string]cachedLeaf), 96 }, nil 97 } 98 99 func buildCA(key *ecdsa.PrivateKey) (*LocalCA, error) { 100 serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 101 if err != nil { 102 return nil, err 103 } 104 tmpl := &x509.Certificate{ 105 SerialNumber: serial, 106 Subject: pkix.Name{CommonName: "mic local CA"}, 107 NotBefore: time.Now().Add(-time.Hour), 108 NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), 109 IsCA: true, 110 KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, 111 BasicConstraintsValid: true, 112 } 113 der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) 114 if err != nil { 115 return nil, err 116 } 117 cert, err := x509.ParseCertificate(der) 118 if err != nil { 119 return nil, err 120 } 121 return &LocalCA{ 122 cert: cert, 123 key: key, 124 derCert: der, 125 cache: make(map[string]cachedLeaf), 126 }, nil 127 } 128 129 func (ca *LocalCA) Save(certPath, keyPath string) error { 130 cf, err := os.Create(certPath) 131 if err != nil { 132 return err 133 } 134 defer cf.Close() 135 if err := pem.Encode(cf, &pem.Block{Type: "CERTIFICATE", Bytes: ca.derCert}); err != nil { 136 return err 137 } 138 139 keyBytes, err := x509.MarshalECPrivateKey(ca.key) 140 if err != nil { 141 return err 142 } 143 kf, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) 144 if err != nil { 145 return err 146 } 147 defer kf.Close() 148 // O_CREATE's mode is ignored for an existing file and masked by umask for a 149 // new one, so set 0600 explicitly. 150 if err := kf.Chmod(0o600); err != nil { 151 return err 152 } 153 return pem.Encode(kf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}) 154 } 155 156 // CertPEM returns the CA certificate as PEM for curl --cacert or a trust store. 157 func (ca *LocalCA) CertPEM() []byte { 158 return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.derCert}) 159 } 160 161 // issueCert returns a cached leaf for host, re-issuing when it is unseen or 162 // near expiry. 163 func (ca *LocalCA) issueCert(host string) (tls.Certificate, error) { 164 ca.mu.Lock() 165 defer ca.mu.Unlock() 166 167 if c, ok := ca.cache[host]; ok && time.Now().Before(c.notAfter.Add(-leafRenewMargin)) { 168 return c.cert, nil 169 } 170 171 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 172 if err != nil { 173 return tls.Certificate{}, err 174 } 175 serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 176 if err != nil { 177 return tls.Certificate{}, err 178 } 179 notAfter := time.Now().Add(leafLifetime) 180 tmpl := &x509.Certificate{ 181 SerialNumber: serial, 182 Subject: pkix.Name{CommonName: host}, 183 NotBefore: time.Now().Add(-time.Hour), 184 NotAfter: notAfter, 185 KeyUsage: x509.KeyUsageDigitalSignature, 186 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 187 } 188 if ip := net.ParseIP(host); ip != nil { 189 tmpl.IPAddresses = []net.IP{ip} 190 } else { 191 tmpl.DNSNames = []string{host} 192 } 193 194 der, err := x509.CreateCertificate(rand.Reader, tmpl, ca.cert, &key.PublicKey, ca.key) 195 if err != nil { 196 return tls.Certificate{}, err 197 } 198 cert := tls.Certificate{ 199 Certificate: [][]byte{der, ca.derCert}, // CA appended so clients can verify the chain 200 PrivateKey: key, 201 } 202 ca.cache[host] = cachedLeaf{cert: cert, notAfter: notAfter} 203 return cert, nil 204 }