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  }