mic~ proxy for JA4-TLS fingerprinting evasion |
| git clone https://github.com/djnnvx/mic.git |
| log | files | refs | archive | README | LICENSE |
ca_test.go (8295B)
1 package proxy 2 3 import ( 4 "bytes" 5 "crypto/x509" 6 "net" 7 "os" 8 "path/filepath" 9 "testing" 10 "time" 11 ) 12 13 func TestIssueCert_SignedChain_DNS(t *testing.T) { 14 ca, err := GenerateCA() 15 if err != nil { 16 t.Fatalf("GenerateCA: %v", err) 17 } 18 19 cert, err := ca.issueCert("example.com") 20 if err != nil { 21 t.Fatalf("issueCert: %v", err) 22 } 23 24 if len(cert.Certificate) != 2 { 25 t.Fatalf("chain length = %d; want 2 (leaf + CA)", len(cert.Certificate)) 26 } 27 28 leaf, err := x509.ParseCertificate(cert.Certificate[0]) 29 if err != nil { 30 t.Fatalf("parse leaf: %v", err) 31 } 32 33 roots := x509.NewCertPool() 34 roots.AddCert(ca.cert) 35 if _, err := leaf.Verify(x509.VerifyOptions{ 36 DNSName: "example.com", 37 Roots: roots, 38 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 39 }); err != nil { 40 t.Errorf("leaf does not verify against CA: %v", err) 41 } 42 43 if len(leaf.DNSNames) != 1 || leaf.DNSNames[0] != "example.com" { 44 t.Errorf("DNSNames = %v; want [example.com]", leaf.DNSNames) 45 } 46 if len(leaf.IPAddresses) != 0 { 47 t.Errorf("IPAddresses = %v; want none for a hostname", leaf.IPAddresses) 48 } 49 } 50 51 func TestIssueCert_IP(t *testing.T) { 52 ca, err := GenerateCA() 53 if err != nil { 54 t.Fatalf("GenerateCA: %v", err) 55 } 56 57 cert, err := ca.issueCert("192.0.2.1") 58 if err != nil { 59 t.Fatalf("issueCert: %v", err) 60 } 61 leaf, err := x509.ParseCertificate(cert.Certificate[0]) 62 if err != nil { 63 t.Fatalf("parse leaf: %v", err) 64 } 65 66 roots := x509.NewCertPool() 67 roots.AddCert(ca.cert) 68 if _, err := leaf.Verify(x509.VerifyOptions{ 69 DNSName: "192.0.2.1", 70 Roots: roots, 71 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 72 }); err != nil { 73 t.Errorf("leaf does not verify for IP SAN: %v", err) 74 } 75 76 if len(leaf.IPAddresses) != 1 || !leaf.IPAddresses[0].Equal(net.ParseIP("192.0.2.1")) { 77 t.Errorf("IPAddresses = %v; want [192.0.2.1]", leaf.IPAddresses) 78 } 79 if len(leaf.DNSNames) != 0 { 80 t.Errorf("DNSNames = %v; want none for an IP", leaf.DNSNames) 81 } 82 } 83 84 func TestIssueCert_Caches(t *testing.T) { 85 ca, err := GenerateCA() 86 if err != nil { 87 t.Fatalf("GenerateCA: %v", err) 88 } 89 90 first, err := ca.issueCert("example.com") 91 if err != nil { 92 t.Fatalf("issueCert: %v", err) 93 } 94 second, err := ca.issueCert("example.com") 95 if err != nil { 96 t.Fatalf("issueCert (cached): %v", err) 97 } 98 99 if !bytes.Equal(first.Certificate[0], second.Certificate[0]) { 100 t.Error("second issueCert for same host returned a different cert; cache miss") 101 } 102 if _, err := ca.issueCert("other.com"); err != nil { 103 t.Fatalf("issueCert other host: %v", err) 104 } 105 if len(ca.cache) != 2 { 106 t.Errorf("cache size = %d; want 2", len(ca.cache)) 107 } 108 } 109 110 // A MitM leaf must never be usable as a CA: the leaf key could then mint certs 111 // for any host. 112 func TestIssueCert_LeafIsNotACA(t *testing.T) { 113 ca, err := GenerateCA() 114 if err != nil { 115 t.Fatalf("GenerateCA: %v", err) 116 } 117 cert, err := ca.issueCert("example.com") 118 if err != nil { 119 t.Fatalf("issueCert: %v", err) 120 } 121 leaf, err := x509.ParseCertificate(cert.Certificate[0]) 122 if err != nil { 123 t.Fatalf("parse leaf: %v", err) 124 } 125 126 if leaf.IsCA { 127 t.Error("leaf has IsCA = true; a MitM leaf must not be a CA") 128 } 129 if leaf.KeyUsage&x509.KeyUsageCertSign != 0 { 130 t.Error("leaf KeyUsage includes CertSign; a MitM leaf must not sign certs") 131 } 132 if leaf.KeyUsage&x509.KeyUsageCRLSign != 0 { 133 t.Error("leaf KeyUsage includes CRLSign") 134 } 135 if leaf.KeyUsage != x509.KeyUsageDigitalSignature { 136 t.Errorf("leaf KeyUsage = %v; want DigitalSignature only", leaf.KeyUsage) 137 } 138 if len(leaf.ExtKeyUsage) != 1 || leaf.ExtKeyUsage[0] != x509.ExtKeyUsageServerAuth { 139 t.Errorf("leaf ExtKeyUsage = %v; want [ServerAuth]", leaf.ExtKeyUsage) 140 } 141 } 142 143 func TestIssueCert_ReissuesExpiredLeaf(t *testing.T) { 144 ca, err := GenerateCA() 145 if err != nil { 146 t.Fatalf("GenerateCA: %v", err) 147 } 148 first, err := ca.issueCert("example.com") 149 if err != nil { 150 t.Fatalf("issueCert: %v", err) 151 } 152 153 ca.mu.Lock() 154 entry := ca.cache["example.com"] 155 entry.notAfter = time.Now().Add(-time.Second) 156 ca.cache["example.com"] = entry 157 ca.mu.Unlock() 158 159 second, err := ca.issueCert("example.com") 160 if err != nil { 161 t.Fatalf("issueCert after expiry: %v", err) 162 } 163 if bytes.Equal(first.Certificate[0], second.Certificate[0]) { 164 t.Fatal("expired cached leaf was served again instead of being re-issued") 165 } 166 leaf, err := x509.ParseCertificate(second.Certificate[0]) 167 if err != nil { 168 t.Fatalf("parse leaf: %v", err) 169 } 170 if !time.Now().Before(leaf.NotAfter) { 171 t.Errorf("re-issued leaf NotAfter = %v; already expired", leaf.NotAfter) 172 } 173 } 174 175 func TestIssueCert_ReissuesLeafInsideRenewMargin(t *testing.T) { 176 ca, err := GenerateCA() 177 if err != nil { 178 t.Fatalf("GenerateCA: %v", err) 179 } 180 first, err := ca.issueCert("example.com") 181 if err != nil { 182 t.Fatalf("issueCert: %v", err) 183 } 184 185 ca.mu.Lock() 186 entry := ca.cache["example.com"] 187 entry.notAfter = time.Now().Add(leafRenewMargin / 2) 188 ca.cache["example.com"] = entry 189 ca.mu.Unlock() 190 191 second, err := ca.issueCert("example.com") 192 if err != nil { 193 t.Fatalf("issueCert inside margin: %v", err) 194 } 195 if bytes.Equal(first.Certificate[0], second.Certificate[0]) { 196 t.Error("leaf expiring inside the renew margin was served again") 197 } 198 } 199 200 func TestSave_KeyIsNotWorldReadable(t *testing.T) { 201 ca, err := GenerateCA() 202 if err != nil { 203 t.Fatalf("GenerateCA: %v", err) 204 } 205 206 t.Run("fresh file", func(t *testing.T) { 207 dir := t.TempDir() 208 keyPath := filepath.Join(dir, "ca.key") 209 if err := ca.Save(filepath.Join(dir, "ca.pem"), keyPath); err != nil { 210 t.Fatalf("Save: %v", err) 211 } 212 assertKeyPerm(t, keyPath) 213 }) 214 215 t.Run("overwrites pre-existing world readable file", func(t *testing.T) { 216 dir := t.TempDir() 217 keyPath := filepath.Join(dir, "ca.key") 218 f, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE, 0o666) 219 if err != nil { 220 t.Fatalf("pre-create key: %v", err) 221 } 222 f.Close() 223 if err := os.Chmod(keyPath, 0o666); err != nil { 224 t.Fatalf("chmod: %v", err) 225 } 226 if err := ca.Save(filepath.Join(dir, "ca.pem"), keyPath); err != nil { 227 t.Fatalf("Save: %v", err) 228 } 229 assertKeyPerm(t, keyPath) 230 }) 231 } 232 233 func assertKeyPerm(t *testing.T, keyPath string) { 234 t.Helper() 235 fi, err := os.Stat(keyPath) 236 if err != nil { 237 t.Fatalf("stat key: %v", err) 238 } 239 if perm := fi.Mode().Perm(); perm&0o077 != 0 { 240 t.Errorf("CA key mode on disk = %#o; group/other must have no access", perm) 241 } 242 if _, err := loadCA(filepath.Join(filepath.Dir(keyPath), "ca.pem"), keyPath); err != nil { 243 t.Errorf("saved CA does not load back: %v", err) 244 } 245 } 246 247 func TestLoadOrGenerateCA_RoundTrip(t *testing.T) { 248 dir := t.TempDir() 249 certPath := filepath.Join(dir, "ca.pem") 250 keyPath := filepath.Join(dir, "ca.key") 251 252 first, err := LoadOrGenerateCA(certPath, keyPath) 253 if err != nil { 254 t.Fatalf("LoadOrGenerateCA (generate): %v", err) 255 } 256 if _, err := os.Stat(certPath); err != nil { 257 t.Fatalf("cert was not written: %v", err) 258 } 259 260 second, err := LoadOrGenerateCA(certPath, keyPath) 261 if err != nil { 262 t.Fatalf("LoadOrGenerateCA (load): %v", err) 263 } 264 if !bytes.Equal(first.derCert, second.derCert) { 265 t.Error("second call did not load the CA written by the first") 266 } 267 } 268 269 func TestLoadOrGenerateCA_CorruptCert(t *testing.T) { 270 dir := t.TempDir() 271 certPath := filepath.Join(dir, "ca.pem") 272 keyPath := filepath.Join(dir, "ca.key") 273 274 ca, err := GenerateCA() 275 if err != nil { 276 t.Fatalf("GenerateCA: %v", err) 277 } 278 if err := ca.Save(certPath, keyPath); err != nil { 279 t.Fatalf("Save: %v", err) 280 } 281 if err := os.WriteFile(certPath, []byte("not a certificate\n"), 0o644); err != nil { 282 t.Fatalf("corrupt cert: %v", err) 283 } 284 285 if _, err := LoadOrGenerateCA(certPath, keyPath); err == nil { 286 t.Fatal("LoadOrGenerateCA accepted a corrupt cert file") 287 } 288 } 289 290 func TestLoadOrGenerateCA_HalfSpecifiedPairRefused(t *testing.T) { 291 dir := t.TempDir() 292 293 if _, err := LoadOrGenerateCA(filepath.Join(dir, "ca.pem"), ""); err == nil { 294 t.Error("cert path without key path was accepted; want an error") 295 } 296 if _, err := LoadOrGenerateCA("", filepath.Join(dir, "ca.key")); err == nil { 297 t.Error("key path without cert path was accepted; want an error") 298 } 299 if _, err := os.Stat(filepath.Join(dir, "ca.pem")); !os.IsNotExist(err) { 300 t.Error("a refused half-specified pair still wrote a cert file") 301 } 302 303 if _, err := LoadOrGenerateCA("", ""); err != nil { 304 t.Errorf("both paths empty must stay valid (ephemeral CA): %v", err) 305 } 306 }