github.com/letsencrypt/boulder@v0.20251208.0/issuance/issuer_test.go (about) 1 package issuance 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/ed25519" 6 "crypto/elliptic" 7 "crypto/rand" 8 "crypto/x509" 9 "crypto/x509/pkix" 10 "fmt" 11 "math/big" 12 "os" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/jmhodges/clock" 18 19 "github.com/letsencrypt/boulder/cmd" 20 "github.com/letsencrypt/boulder/config" 21 "github.com/letsencrypt/boulder/core" 22 "github.com/letsencrypt/boulder/test" 23 ) 24 25 func defaultProfileConfig() ProfileConfig { 26 return ProfileConfig{ 27 MaxValidityPeriod: config.Duration{Duration: time.Hour}, 28 MaxValidityBackdate: config.Duration{Duration: time.Hour}, 29 IgnoredLints: []string{ 30 // Ignore the two SCT lints because these tests don't get SCTs. 31 "w_ct_sct_policy_count_unsatisfied", 32 "e_scts_from_same_operator", 33 // Ignore the warning about including the SubjectKeyIdentifier extension: 34 // we include it on purpose, but plan to remove it soon. 35 "w_ext_subject_key_identifier_not_recommended_subscriber", 36 }, 37 } 38 } 39 40 func defaultIssuerConfig() IssuerConfig { 41 return IssuerConfig{ 42 Active: true, 43 IssuerURL: "http://issuer-url.example.org", 44 CRLURLBase: "http://crl-url.example.org/", 45 CRLShards: 10, 46 Profiles: []string{"modern"}, 47 } 48 } 49 50 var issuerCert *Certificate 51 var issuerSigner *ecdsa.PrivateKey 52 53 func TestMain(m *testing.M) { 54 tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 55 cmd.FailOnError(err, "failed to generate test key") 56 issuerSigner = tk 57 template := &x509.Certificate{ 58 SerialNumber: big.NewInt(123), 59 BasicConstraintsValid: true, 60 IsCA: true, 61 Subject: pkix.Name{ 62 CommonName: "big ca", 63 }, 64 KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 65 } 66 issuer, err := x509.CreateCertificate(rand.Reader, template, template, tk.Public(), tk) 67 cmd.FailOnError(err, "failed to generate test issuer") 68 cert, err := x509.ParseCertificate(issuer) 69 cmd.FailOnError(err, "failed to parse test issuer") 70 issuerCert = &Certificate{Certificate: cert} 71 os.Exit(m.Run()) 72 } 73 74 func TestLoadCertificate(t *testing.T) { 75 t.Parallel() 76 tests := []struct { 77 name string 78 path string 79 wantErr string 80 }{ 81 {"invalid cert file", "../test/hierarchy/int-e1.crl.pem", "loading issuer certificate"}, 82 {"non-CA cert file", "../test/hierarchy/ee-e1.cert.pem", "not a CA certificate"}, 83 {"happy path", "../test/hierarchy/int-e1.cert.pem", ""}, 84 } 85 for _, tc := range tests { 86 t.Run(tc.name, func(t *testing.T) { 87 t.Parallel() 88 _, err := LoadCertificate(tc.path) 89 if err != nil { 90 if tc.wantErr != "" { 91 test.AssertContains(t, err.Error(), tc.wantErr) 92 } else { 93 t.Errorf("expected no error but got %v", err) 94 } 95 } else { 96 if tc.wantErr != "" { 97 t.Errorf("expected error %q but got none", tc.wantErr) 98 } 99 } 100 }) 101 } 102 } 103 104 func TestLoadSigner(t *testing.T) { 105 t.Parallel() 106 107 // We're using this for its pubkey. This definitely doesn't match the private 108 // key loaded in any of the tests below, but that's okay because it still gets 109 // us through all the logic in loadSigner. 110 fakeKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) 111 test.AssertNotError(t, err, "generating test key") 112 113 tests := []struct { 114 name string 115 loc IssuerLoc 116 wantErr string 117 }{ 118 {"empty IssuerLoc", IssuerLoc{}, "must supply"}, 119 {"invalid key file", IssuerLoc{File: "../test/hierarchy/int-e1.crl.pem"}, "unable to parse"}, 120 {"ECDSA key file", IssuerLoc{File: "../test/hierarchy/int-e1.key.pem"}, ""}, 121 {"RSA key file", IssuerLoc{File: "../test/hierarchy/int-r3.key.pem"}, ""}, 122 {"invalid config file", IssuerLoc{ConfigFile: "../test/ident-policy.yaml"}, "invalid character"}, 123 // Note that we don't have a test for "valid config file" because it would 124 // always fail -- in CI, the softhsm hasn't been initialized, so there's no 125 // key to look up; locally even if the softhsm has been initialized, the 126 // keys in it don't match the fakeKey we generated above. 127 } 128 for _, tc := range tests { 129 t.Run(tc.name, func(t *testing.T) { 130 t.Parallel() 131 _, err := loadSigner(tc.loc, fakeKey.Public()) 132 if err != nil { 133 if tc.wantErr != "" { 134 test.AssertContains(t, err.Error(), tc.wantErr) 135 } else { 136 t.Errorf("expected no error but got %v", err) 137 } 138 } else { 139 if tc.wantErr != "" { 140 t.Errorf("expected error %q but got none", tc.wantErr) 141 } 142 } 143 }) 144 } 145 } 146 147 func TestLoadIssuer(t *testing.T) { 148 _, err := newIssuer( 149 defaultIssuerConfig(), 150 issuerCert, 151 issuerSigner, 152 clock.NewFake(), 153 ) 154 test.AssertNotError(t, err, "newIssuer failed") 155 } 156 157 func TestNewIssuerUnsupportedKeyType(t *testing.T) { 158 _, err := newIssuer( 159 defaultIssuerConfig(), 160 &Certificate{ 161 Certificate: &x509.Certificate{ 162 PublicKey: &ed25519.PublicKey{}, 163 }, 164 }, 165 &ed25519.PrivateKey{}, 166 clock.NewFake(), 167 ) 168 test.AssertError(t, err, "newIssuer didn't fail") 169 test.AssertEquals(t, err.Error(), "unsupported issuer key type") 170 } 171 172 func TestNewIssuerKeyUsage(t *testing.T) { 173 t.Parallel() 174 175 tests := []struct { 176 name string 177 ku x509.KeyUsage 178 wantErr string 179 }{ 180 {"missing certSign", x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, "does not have keyUsage certSign"}, 181 {"missing crlSign", x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, "does not have keyUsage crlSign"}, 182 {"missing digitalSignature", x509.KeyUsageCertSign | x509.KeyUsageCRLSign, "does not have keyUsage digitalSignature"}, 183 {"all three", x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, ""}, 184 } 185 for _, tc := range tests { 186 t.Run(tc.name, func(t *testing.T) { 187 t.Parallel() 188 _, err := newIssuer( 189 defaultIssuerConfig(), 190 &Certificate{ 191 Certificate: &x509.Certificate{ 192 SerialNumber: big.NewInt(123), 193 PublicKey: &ecdsa.PublicKey{ 194 Curve: elliptic.P256(), 195 }, 196 KeyUsage: tc.ku, 197 }, 198 }, 199 issuerSigner, 200 clock.NewFake(), 201 ) 202 if err != nil { 203 if tc.wantErr != "" { 204 test.AssertContains(t, err.Error(), tc.wantErr) 205 } else { 206 t.Errorf("expected no error but got %v", err) 207 } 208 } else { 209 if tc.wantErr != "" { 210 t.Errorf("expected error %q but got none", tc.wantErr) 211 } 212 } 213 }) 214 } 215 } 216 217 func TestLoadChain_Valid(t *testing.T) { 218 chain, err := LoadChain([]string{ 219 "../test/hierarchy/int-e1.cert.pem", 220 "../test/hierarchy/root-x2.cert.pem", 221 }) 222 test.AssertNotError(t, err, "Should load valid chain") 223 224 expectedIssuer, err := core.LoadCert("../test/hierarchy/int-e1.cert.pem") 225 test.AssertNotError(t, err, "Failed to load test issuer") 226 227 chainIssuer := chain[0] 228 test.AssertNotNil(t, chainIssuer, "Failed to decode chain PEM") 229 230 test.AssertByteEquals(t, chainIssuer.Raw, expectedIssuer.Raw) 231 } 232 233 func TestLoadChain_TooShort(t *testing.T) { 234 _, err := LoadChain([]string{"/path/to/one/cert.pem"}) 235 test.AssertError(t, err, "Should reject too-short chain") 236 } 237 238 func TestLoadChain_Unloadable(t *testing.T) { 239 _, err := LoadChain([]string{ 240 "does-not-exist.pem", 241 "../test/hierarchy/root-x2.cert.pem", 242 }) 243 test.AssertError(t, err, "Should reject unloadable chain") 244 245 _, err = LoadChain([]string{ 246 "../test/hierarchy/int-e1.cert.pem", 247 "does-not-exist.pem", 248 }) 249 test.AssertError(t, err, "Should reject unloadable chain") 250 251 invalidPEMFile, _ := os.CreateTemp("", "invalid.pem") 252 err = os.WriteFile(invalidPEMFile.Name(), []byte(""), 0640) 253 test.AssertNotError(t, err, "Error writing invalid PEM tmp file") 254 _, err = LoadChain([]string{ 255 invalidPEMFile.Name(), 256 "../test/hierarchy/root-x2.cert.pem", 257 }) 258 test.AssertError(t, err, "Should reject unloadable chain") 259 } 260 261 func TestLoadChain_InvalidSig(t *testing.T) { 262 _, err := LoadChain([]string{ 263 "../test/hierarchy/int-e1.cert.pem", 264 "../test/hierarchy/root-x1.cert.pem", 265 }) 266 test.AssertError(t, err, "Should reject invalid signature") 267 test.Assert(t, strings.Contains(err.Error(), "root-x1.cert.pem"), 268 fmt.Sprintf("Expected error to mention filename, got: %s", err)) 269 test.Assert(t, strings.Contains(err.Error(), "signature from \"CN=(TEST) Ineffable Ice X1"), 270 fmt.Sprintf("Expected error to mention subject, got: %s", err)) 271 }