github.com/letsencrypt/boulder@v0.20251208.0/cmd/ceremony/cert_test.go (about) 1 package main 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "crypto/x509" 8 "crypto/x509/pkix" 9 "encoding/asn1" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "io/fs" 14 "math/big" 15 "testing" 16 "time" 17 18 "github.com/miekg/pkcs11" 19 20 "github.com/letsencrypt/boulder/pkcs11helpers" 21 "github.com/letsencrypt/boulder/test" 22 ) 23 24 // samplePubkey returns a slice of bytes containing an encoded 25 // SubjectPublicKeyInfo for an example public key. 26 func samplePubkey() []byte { 27 pubKey, err := hex.DecodeString("3059301306072a8648ce3d020106082a8648ce3d03010703420004b06745ef0375c9c54057098f077964e18d3bed0aacd54545b16eab8c539b5768cc1cea93ba56af1e22a7a01c33048c8885ed17c9c55ede70649b707072689f5e") 28 if err != nil { 29 panic(err) 30 } 31 return pubKey 32 } 33 34 func realRand(_ pkcs11.SessionHandle, length int) ([]byte, error) { 35 r := make([]byte, length) 36 _, err := rand.Read(r) 37 return r, err 38 } 39 40 func TestParseOID(t *testing.T) { 41 _, err := parseOID("") 42 test.AssertError(t, err, "parseOID accepted an empty OID") 43 _, err = parseOID("a.b.c") 44 test.AssertError(t, err, "parseOID accepted an OID containing non-ints") 45 _, err = parseOID("1.0.2") 46 test.AssertError(t, err, "parseOID accepted an OID containing zero") 47 oid, err := parseOID("1.2.3") 48 test.AssertNotError(t, err, "parseOID failed with a valid OID") 49 test.Assert(t, oid.Equal(asn1.ObjectIdentifier{1, 2, 3}), "parseOID returned incorrect OID") 50 } 51 52 func TestMakeSubject(t *testing.T) { 53 profile := &certProfile{ 54 CommonName: "common name", 55 Organization: "organization", 56 Country: "country", 57 } 58 expectedSubject := pkix.Name{ 59 CommonName: "common name", 60 Organization: []string{"organization"}, 61 Country: []string{"country"}, 62 } 63 test.AssertDeepEquals(t, profile.Subject(), expectedSubject) 64 } 65 66 func TestMakeTemplateRoot(t *testing.T) { 67 s, ctx := pkcs11helpers.NewSessionWithMock() 68 profile := &certProfile{} 69 randReader := newRandReader(s) 70 pubKey := samplePubkey() 71 ctx.GenerateRandomFunc = realRand 72 73 profile.NotBefore = "1234" 74 _, err := makeTemplate(randReader, profile, pubKey, nil, rootCert) 75 test.AssertError(t, err, "makeTemplate didn't fail with invalid not before") 76 77 profile.NotBefore = "2018-05-18 11:31:00" 78 profile.NotAfter = "1234" 79 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 80 test.AssertError(t, err, "makeTemplate didn't fail with invalid not after") 81 82 profile.NotAfter = "2018-05-18 11:31:00" 83 profile.SignatureAlgorithm = "nope" 84 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 85 test.AssertError(t, err, "makeTemplate didn't fail with invalid signature algorithm") 86 87 profile.SignatureAlgorithm = "SHA256WithRSA" 88 ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) { 89 return nil, errors.New("bad") 90 } 91 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 92 test.AssertError(t, err, "makeTemplate didn't fail when GenerateRandom failed") 93 94 ctx.GenerateRandomFunc = realRand 95 96 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 97 test.AssertError(t, err, "makeTemplate didn't fail with empty key usages") 98 99 profile.KeyUsages = []string{"asd"} 100 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 101 test.AssertError(t, err, "makeTemplate didn't fail with invalid key usages") 102 103 profile.KeyUsages = []string{"Digital Signature", "CRL Sign"} 104 profile.Policies = []policyInfoConfig{{}} 105 _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) 106 test.AssertError(t, err, "makeTemplate didn't fail with invalid (empty) policy OID") 107 108 profile.Policies = []policyInfoConfig{{OID: "1.2.3"}, {OID: "1.2.3.4"}} 109 profile.CommonName = "common name" 110 profile.Organization = "organization" 111 profile.Country = "country" 112 profile.CRLURL = "crl" 113 profile.IssuerURL = "issuer" 114 cert, err := makeTemplate(randReader, profile, pubKey, nil, rootCert) 115 test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") 116 test.AssertEquals(t, cert.Subject.CommonName, profile.CommonName) 117 test.AssertEquals(t, len(cert.Subject.Organization), 1) 118 test.AssertEquals(t, cert.Subject.Organization[0], profile.Organization) 119 test.AssertEquals(t, len(cert.Subject.Country), 1) 120 test.AssertEquals(t, cert.Subject.Country[0], profile.Country) 121 test.AssertEquals(t, len(cert.CRLDistributionPoints), 1) 122 test.AssertEquals(t, cert.CRLDistributionPoints[0], profile.CRLURL) 123 test.AssertEquals(t, len(cert.IssuingCertificateURL), 1) 124 test.AssertEquals(t, cert.IssuingCertificateURL[0], profile.IssuerURL) 125 test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature|x509.KeyUsageCRLSign) 126 test.AssertEquals(t, len(cert.Policies), 2) 127 test.AssertEquals(t, len(cert.ExtKeyUsage), 0) 128 129 cert, err = makeTemplate(randReader, profile, pubKey, nil, intermediateCert) 130 test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") 131 test.Assert(t, cert.MaxPathLenZero, "MaxPathLenZero not set in intermediate template") 132 test.AssertEquals(t, len(cert.ExtKeyUsage), 1) 133 test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageServerAuth) 134 } 135 136 func TestMakeTemplateRestrictedCrossCertificate(t *testing.T) { 137 s, ctx := pkcs11helpers.NewSessionWithMock() 138 ctx.GenerateRandomFunc = realRand 139 randReader := newRandReader(s) 140 pubKey := samplePubkey() 141 profile := &certProfile{ 142 SignatureAlgorithm: "SHA256WithRSA", 143 CommonName: "common name", 144 Organization: "organization", 145 Country: "country", 146 KeyUsages: []string{"Digital Signature", "CRL Sign"}, 147 CRLURL: "crl", 148 IssuerURL: "issuer", 149 NotAfter: "2020-10-10 11:31:00", 150 NotBefore: "2020-10-10 11:31:00", 151 } 152 153 tbcsCert := x509.Certificate{ 154 SerialNumber: big.NewInt(666), 155 Subject: pkix.Name{ 156 Organization: []string{"While Eek Ayote"}, 157 }, 158 NotBefore: time.Now(), 159 NotAfter: time.Now().Add(365 * 24 * time.Hour), 160 KeyUsage: x509.KeyUsageDigitalSignature, 161 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 162 BasicConstraintsValid: true, 163 } 164 165 cert, err := makeTemplate(randReader, profile, pubKey, &tbcsCert, crossCert) 166 test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") 167 test.Assert(t, !cert.MaxPathLenZero, "MaxPathLenZero was set in cross-sign") 168 test.AssertEquals(t, len(cert.ExtKeyUsage), 1) 169 test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageServerAuth) 170 } 171 172 func TestVerifyProfile(t *testing.T) { 173 for _, tc := range []struct { 174 profile certProfile 175 certType []certType 176 expectedErr string 177 }{ 178 { 179 profile: certProfile{}, 180 certType: []certType{intermediateCert, crossCert}, 181 expectedErr: "not-before is required", 182 }, 183 { 184 profile: certProfile{ 185 NotBefore: "a", 186 }, 187 certType: []certType{intermediateCert, crossCert}, 188 expectedErr: "not-after is required", 189 }, 190 { 191 profile: certProfile{ 192 NotBefore: "a", 193 NotAfter: "b", 194 }, 195 certType: []certType{intermediateCert, crossCert}, 196 expectedErr: "signature-algorithm is required", 197 }, 198 { 199 profile: certProfile{ 200 NotBefore: "a", 201 NotAfter: "b", 202 SignatureAlgorithm: "c", 203 }, 204 certType: []certType{intermediateCert, crossCert}, 205 expectedErr: "common-name is required", 206 }, 207 { 208 profile: certProfile{ 209 NotBefore: "a", 210 NotAfter: "b", 211 SignatureAlgorithm: "c", 212 CommonName: "d", 213 }, 214 certType: []certType{intermediateCert, crossCert}, 215 expectedErr: "organization is required", 216 }, 217 { 218 profile: certProfile{ 219 NotBefore: "a", 220 NotAfter: "b", 221 SignatureAlgorithm: "c", 222 CommonName: "d", 223 Organization: "e", 224 }, 225 certType: []certType{intermediateCert, crossCert}, 226 expectedErr: "country is required", 227 }, 228 { 229 profile: certProfile{ 230 NotBefore: "a", 231 NotAfter: "b", 232 SignatureAlgorithm: "c", 233 CommonName: "d", 234 Organization: "e", 235 Country: "f", 236 }, 237 certType: []certType{intermediateCert, crossCert}, 238 expectedErr: "crl-url is required for subordinate CAs", 239 }, 240 { 241 profile: certProfile{ 242 NotBefore: "a", 243 NotAfter: "b", 244 SignatureAlgorithm: "c", 245 CommonName: "d", 246 Organization: "e", 247 Country: "f", 248 CRLURL: "h", 249 }, 250 certType: []certType{intermediateCert, crossCert}, 251 expectedErr: "issuer-url is required for subordinate CAs", 252 }, 253 { 254 profile: certProfile{ 255 NotBefore: "a", 256 NotAfter: "b", 257 SignatureAlgorithm: "c", 258 CommonName: "d", 259 Organization: "e", 260 Country: "f", 261 CRLURL: "h", 262 IssuerURL: "i", 263 }, 264 certType: []certType{intermediateCert, crossCert}, 265 expectedErr: "policy should be exactly BRs domain-validated for subordinate CAs", 266 }, 267 { 268 profile: certProfile{ 269 NotBefore: "a", 270 NotAfter: "b", 271 SignatureAlgorithm: "c", 272 CommonName: "d", 273 Organization: "e", 274 Country: "f", 275 CRLURL: "h", 276 IssuerURL: "i", 277 Policies: []policyInfoConfig{{OID: "1.2.3"}, {OID: "4.5.6"}}, 278 }, 279 certType: []certType{intermediateCert, crossCert}, 280 expectedErr: "policy should be exactly BRs domain-validated for subordinate CAs", 281 }, 282 { 283 profile: certProfile{ 284 NotBefore: "a", 285 NotAfter: "b", 286 SignatureAlgorithm: "c", 287 CommonName: "d", 288 Organization: "e", 289 Country: "f", 290 }, 291 certType: []certType{rootCert}, 292 }, 293 { 294 profile: certProfile{ 295 NotBefore: "a", 296 }, 297 certType: []certType{requestCert}, 298 expectedErr: "not-before cannot be set for a CSR", 299 }, 300 { 301 profile: certProfile{ 302 NotAfter: "a", 303 }, 304 certType: []certType{requestCert}, 305 expectedErr: "not-after cannot be set for a CSR", 306 }, 307 { 308 profile: certProfile{ 309 SignatureAlgorithm: "a", 310 }, 311 certType: []certType{requestCert}, 312 expectedErr: "signature-algorithm cannot be set for a CSR", 313 }, 314 { 315 profile: certProfile{ 316 CRLURL: "a", 317 }, 318 certType: []certType{requestCert}, 319 expectedErr: "crl-url cannot be set for a CSR", 320 }, 321 { 322 profile: certProfile{ 323 IssuerURL: "a", 324 }, 325 certType: []certType{requestCert}, 326 expectedErr: "issuer-url cannot be set for a CSR", 327 }, 328 { 329 profile: certProfile{ 330 Policies: []policyInfoConfig{{OID: "1.2.3"}}, 331 }, 332 certType: []certType{requestCert}, 333 expectedErr: "policies cannot be set for a CSR", 334 }, 335 { 336 profile: certProfile{ 337 KeyUsages: []string{"a"}, 338 }, 339 certType: []certType{requestCert}, 340 expectedErr: "key-usages cannot be set for a CSR", 341 }, 342 } { 343 for _, ct := range tc.certType { 344 err := tc.profile.verifyProfile(ct) 345 if err != nil { 346 if tc.expectedErr != err.Error() { 347 t.Fatalf("Expected %q, got %q", tc.expectedErr, err.Error()) 348 } 349 } else if tc.expectedErr != "" { 350 t.Fatalf("verifyProfile didn't fail, expected %q", tc.expectedErr) 351 } 352 } 353 } 354 } 355 356 func TestGenerateCSR(t *testing.T) { 357 profile := &certProfile{ 358 CommonName: "common name", 359 Organization: "organization", 360 Country: "country", 361 } 362 363 signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 364 test.AssertNotError(t, err, "failed to generate test key") 365 366 csrBytes, err := generateCSR(profile, &wrappedSigner{signer}) 367 test.AssertNotError(t, err, "failed to generate CSR") 368 369 csr, err := x509.ParseCertificateRequest(csrBytes) 370 test.AssertNotError(t, err, "failed to parse CSR") 371 test.AssertNotError(t, csr.CheckSignature(), "CSR signature check failed") 372 test.AssertEquals(t, len(csr.Extensions), 0) 373 374 test.AssertEquals(t, csr.Subject.String(), fmt.Sprintf("CN=%s,O=%s,C=%s", 375 profile.CommonName, profile.Organization, profile.Country)) 376 } 377 378 func TestLoadCert(t *testing.T) { 379 _, err := loadCert("../../test/hierarchy/int-e1.cert.pem") 380 test.AssertNotError(t, err, "should not have errored") 381 382 _, err = loadCert("/path/that/will/not/ever/exist/ever") 383 test.AssertError(t, err, "should have failed opening certificate at non-existent path") 384 test.AssertErrorIs(t, err, fs.ErrNotExist) 385 386 _, err = loadCert("../../test/hierarchy/int-e1.key.pem") 387 test.AssertError(t, err, "should have failed when trying to parse a private key") 388 } 389 390 func TestGenerateSKID(t *testing.T) { 391 sha256skid, err := generateSKID(samplePubkey()) 392 test.AssertNotError(t, err, "Error generating SKID") 393 test.AssertEquals(t, len(sha256skid), 20) 394 test.AssertEquals(t, cap(sha256skid), 20) 395 }