mic.git


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 }