896 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			896 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Go
		
	
	
	
| package issuance
 | |
| 
 | |
| import (
 | |
| 	"crypto"
 | |
| 	"crypto/dsa"
 | |
| 	"crypto/ecdsa"
 | |
| 	"crypto/elliptic"
 | |
| 	"crypto/rand"
 | |
| 	"crypto/rsa"
 | |
| 	"crypto/x509"
 | |
| 	"crypto/x509/pkix"
 | |
| 	"encoding/base64"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	ct "github.com/google/certificate-transparency-go"
 | |
| 	"github.com/jmhodges/clock"
 | |
| 
 | |
| 	"github.com/letsencrypt/boulder/config"
 | |
| 	"github.com/letsencrypt/boulder/ctpolicy/loglist"
 | |
| 	"github.com/letsencrypt/boulder/linter"
 | |
| 	"github.com/letsencrypt/boulder/test"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	goodSKID = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 | |
| )
 | |
| 
 | |
| func defaultProfile() *Profile {
 | |
| 	p, _ := NewProfile(defaultProfileConfig())
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func TestGenerateValidity(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Date(2015, time.June, 04, 11, 04, 38, 0, time.UTC))
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name      string
 | |
| 		backdate  time.Duration
 | |
| 		validity  time.Duration
 | |
| 		notBefore time.Time
 | |
| 		notAfter  time.Time
 | |
| 	}{
 | |
| 		{
 | |
| 			name:      "normal usage",
 | |
| 			backdate:  time.Hour, // 90% of one hour is 54 minutes
 | |
| 			validity:  7 * 24 * time.Hour,
 | |
| 			notBefore: time.Date(2015, time.June, 04, 10, 10, 38, 0, time.UTC),
 | |
| 			notAfter:  time.Date(2015, time.June, 11, 10, 10, 37, 0, time.UTC),
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "zero backdate",
 | |
| 			backdate:  0,
 | |
| 			validity:  7 * 24 * time.Hour,
 | |
| 			notBefore: time.Date(2015, time.June, 04, 11, 04, 38, 0, time.UTC),
 | |
| 			notAfter:  time.Date(2015, time.June, 11, 11, 04, 37, 0, time.UTC),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			p := Profile{maxBackdate: tc.backdate, maxValidity: tc.validity}
 | |
| 			notBefore, notAfter := p.GenerateValidity(fc.Now())
 | |
| 			test.AssertEquals(t, notBefore, tc.notBefore)
 | |
| 			test.AssertEquals(t, notAfter, tc.notAfter)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCRLURL(t *testing.T) {
 | |
| 	issuer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, clock.NewFake())
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("newIssuer: %s", err)
 | |
| 	}
 | |
| 	url := issuer.crlURL(4928)
 | |
| 	want := "http://crl-url.example.org/4928.crl"
 | |
| 	if url != want {
 | |
| 		t.Errorf("crlURL(4928)=%s, want %s", url, want)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRequestValid(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Add(time.Hour * 24)
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		issuer        *Issuer
 | |
| 		profile       *Profile
 | |
| 		request       *IssuanceRequest
 | |
| 		expectedError string
 | |
| 	}{
 | |
| 		{
 | |
| 			name:          "unsupported key type",
 | |
| 			issuer:        &Issuer{},
 | |
| 			profile:       &Profile{},
 | |
| 			request:       &IssuanceRequest{PublicKey: MarshalablePublicKey{&dsa.PublicKey{}}},
 | |
| 			expectedError: "unsupported public key type",
 | |
| 		},
 | |
| 		{
 | |
| 			name:          "inactive (rsa)",
 | |
| 			issuer:        &Issuer{},
 | |
| 			profile:       &Profile{},
 | |
| 			request:       &IssuanceRequest{PublicKey: MarshalablePublicKey{&rsa.PublicKey{}}},
 | |
| 			expectedError: "inactive issuer cannot issue precert",
 | |
| 		},
 | |
| 		{
 | |
| 			name:          "inactive (ecdsa)",
 | |
| 			issuer:        &Issuer{},
 | |
| 			profile:       &Profile{},
 | |
| 			request:       &IssuanceRequest{PublicKey: MarshalablePublicKey{&ecdsa.PublicKey{}}},
 | |
| 			expectedError: "inactive issuer cannot issue precert",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "skid too short",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: []byte{0, 1, 2, 3, 4},
 | |
| 			},
 | |
| 			expectedError: "unexpected subject key ID length",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "must staple not allowed",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:         MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId:      goodSKID,
 | |
| 				IncludeMustStaple: true,
 | |
| 			},
 | |
| 			expectedError: "must-staple extension cannot be included",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "both sct list and ct poison provided",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:       MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId:    goodSKID,
 | |
| 				IncludeCTPoison: true,
 | |
| 				sctList:         []ct.SignedCertificateTimestamp{},
 | |
| 			},
 | |
| 			expectedError: "cannot include both ct poison and sct list extensions",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "negative validity",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now().Add(time.Hour),
 | |
| 				NotAfter:     fc.Now(),
 | |
| 			},
 | |
| 			expectedError: "NotAfter must be after NotBefore",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "validity larger than max",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Minute,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now(),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour - time.Second),
 | |
| 			},
 | |
| 			expectedError: "validity period is more than the maximum allowed period (1h0m0s>1m0s)",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "validity larger than max due to inclusivity",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now(),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour),
 | |
| 			},
 | |
| 			expectedError: "validity period is more than the maximum allowed period (1h0m1s>1h0m0s)",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "validity backdated more than max",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 				maxBackdate: time.Hour,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now().Add(-time.Hour * 2),
 | |
| 				NotAfter:     fc.Now().Add(-time.Hour),
 | |
| 			},
 | |
| 			expectedError: "NotBefore is backdated more than the maximum allowed period (2h0m0s>1h0m0s)",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "validity is forward dated",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 				maxBackdate: time.Hour,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now().Add(time.Hour),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour * 2),
 | |
| 			},
 | |
| 			expectedError: "NotBefore is in the future",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "serial too short",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now(),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour),
 | |
| 				Serial:       []byte{0, 1, 2, 3, 4, 5, 6, 7},
 | |
| 			},
 | |
| 			expectedError: "serial must be between 9 and 19 bytes",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "serial too long",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now(),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour),
 | |
| 				Serial:       []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 			},
 | |
| 			expectedError: "serial must be between 9 and 19 bytes",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "good with poison",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:       MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId:    goodSKID,
 | |
| 				NotBefore:       fc.Now(),
 | |
| 				NotAfter:        fc.Now().Add(time.Hour),
 | |
| 				Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 				IncludeCTPoison: true,
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "good with scts",
 | |
| 			issuer: &Issuer{
 | |
| 				active: true,
 | |
| 			},
 | |
| 			profile: &Profile{
 | |
| 				maxValidity: time.Hour * 2,
 | |
| 			},
 | |
| 			request: &IssuanceRequest{
 | |
| 				PublicKey:    MarshalablePublicKey{&ecdsa.PublicKey{}},
 | |
| 				SubjectKeyId: goodSKID,
 | |
| 				NotBefore:    fc.Now(),
 | |
| 				NotAfter:     fc.Now().Add(time.Hour),
 | |
| 				Serial:       []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 				sctList:      []ct.SignedCertificateTimestamp{},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			err := tc.issuer.requestValid(fc, tc.profile, tc.request)
 | |
| 			if err != nil {
 | |
| 				if tc.expectedError == "" {
 | |
| 					t.Errorf("failed with unexpected error: %s", err)
 | |
| 				} else if tc.expectedError != err.Error() {
 | |
| 					t.Errorf("failed with unexpected error, wanted: %q, got: %q", tc.expectedError, err.Error())
 | |
| 				}
 | |
| 				return
 | |
| 			} else if tc.expectedError != "" {
 | |
| 				t.Errorf("didn't fail, expected %q", tc.expectedError)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGenerateTemplate(t *testing.T) {
 | |
| 	issuer := &Issuer{
 | |
| 		ocspURL:    "http://ocsp",
 | |
| 		issuerURL:  "http://issuer",
 | |
| 		crlURLBase: "http://crl/",
 | |
| 		sigAlg:     x509.SHA256WithRSA,
 | |
| 	}
 | |
| 
 | |
| 	actual := issuer.generateTemplate()
 | |
| 
 | |
| 	expected := &x509.Certificate{
 | |
| 		BasicConstraintsValid: true,
 | |
| 		SignatureAlgorithm:    x509.SHA256WithRSA,
 | |
| 		IssuingCertificateURL: []string{"http://issuer"},
 | |
| 		OCSPServer:            []string{"http://ocsp"},
 | |
| 		CRLDistributionPoints: nil,
 | |
| 		Policies:              []x509.OID{domainValidatedOID},
 | |
| 	}
 | |
| 
 | |
| 	test.AssertDeepEquals(t, actual, expected)
 | |
| }
 | |
| 
 | |
| func TestIssue(t *testing.T) {
 | |
| 	for _, tc := range []struct {
 | |
| 		name         string
 | |
| 		generateFunc func() (crypto.Signer, error)
 | |
| 		ku           x509.KeyUsage
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "RSA",
 | |
| 			generateFunc: func() (crypto.Signer, error) {
 | |
| 				return rsa.GenerateKey(rand.Reader, 2048)
 | |
| 			},
 | |
| 			ku: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ECDSA",
 | |
| 			generateFunc: func() (crypto.Signer, error) {
 | |
| 				return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 			},
 | |
| 			ku: x509.KeyUsageDigitalSignature,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			fc := clock.NewFake()
 | |
| 			fc.Set(time.Now())
 | |
| 			signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 			test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 			pk, err := tc.generateFunc()
 | |
| 			test.AssertNotError(t, err, "failed to generate test key")
 | |
| 			lintCertBytes, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 				PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 				SubjectKeyId:    goodSKID,
 | |
| 				Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 				DNSNames:        []string{"example.com"},
 | |
| 				NotBefore:       fc.Now(),
 | |
| 				NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 				IncludeCTPoison: true,
 | |
| 			})
 | |
| 			test.AssertNotError(t, err, "Prepare failed")
 | |
| 			_, err = x509.ParseCertificate(lintCertBytes)
 | |
| 			test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 			certBytes, err := signer.Issue(issuanceToken)
 | |
| 			test.AssertNotError(t, err, "Issue failed")
 | |
| 			cert, err := x509.ParseCertificate(certBytes)
 | |
| 			test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 			err = cert.CheckSignatureFrom(issuerCert.Certificate)
 | |
| 			test.AssertNotError(t, err, "signature validation failed")
 | |
| 			test.AssertDeepEquals(t, cert.DNSNames, []string{"example.com"})
 | |
| 			test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
 | |
| 			test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
 | |
| 			test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Poison
 | |
| 			test.AssertEquals(t, cert.KeyUsage, tc.ku)
 | |
| 			if len(cert.CRLDistributionPoints) > 0 {
 | |
| 				t.Errorf("want CRLDistributionPoints=[], got %v", cert.CRLDistributionPoints)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIssueWithCRLDP(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	issuerConfig := defaultIssuerConfig()
 | |
| 	issuerConfig.CRLURLBase = "http://crls.example.net/"
 | |
| 	issuerConfig.CRLShards = 999
 | |
| 	signer, err := newIssuer(issuerConfig, issuerCert, issuerSigner, fc)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("newIssuer: %s", err)
 | |
| 	}
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("ecdsa.GenerateKey: %s", err)
 | |
| 	}
 | |
| 	profile := defaultProfile()
 | |
| 	profile.includeCRLDistributionPoints = true
 | |
| 	_, issuanceToken, err := signer.Prepare(profile, &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("signer.Prepare: %s", err)
 | |
| 	}
 | |
| 	certBytes, err := signer.Issue(issuanceToken)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("signer.Issue: %s", err)
 | |
| 	}
 | |
| 	cert, err := x509.ParseCertificate(certBytes)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("x509.ParseCertificate: %s", err)
 | |
| 	}
 | |
| 	// Because CRL shard is calculated deterministically from serial, we know which shard will be chosen.
 | |
| 	expectedCRLDP := []string{"http://crls.example.net/919.crl"}
 | |
| 	if !reflect.DeepEqual(cert.CRLDistributionPoints, expectedCRLDP) {
 | |
| 		t.Errorf("CRLDP=%+v, want %+v", cert.CRLDistributionPoints, expectedCRLDP)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIssueCommonName(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	prof := defaultProfileConfig()
 | |
| 	prof.IgnoredLints = append(prof.IgnoredLints, "w_subject_common_name_included")
 | |
| 	cnProfile, err := NewProfile(prof)
 | |
| 	test.AssertNotError(t, err, "NewProfile failed")
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	ir := &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com", "www.example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	}
 | |
| 
 | |
| 	// In the default profile, the common name is allowed if requested.
 | |
| 	ir.CommonName = "example.com"
 | |
| 	_, issuanceToken, err := signer.Prepare(cnProfile, ir)
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err := signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err := x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 	test.AssertEquals(t, cert.Subject.CommonName, "example.com")
 | |
| 
 | |
| 	// But not including the common name should be acceptable as well.
 | |
| 	ir.CommonName = ""
 | |
| 	_, issuanceToken, err = signer.Prepare(cnProfile, ir)
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err = signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err = x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 	test.AssertEquals(t, cert.Subject.CommonName, "")
 | |
| 
 | |
| 	// And the common name should be omitted if the profile is so configured.
 | |
| 	ir.CommonName = "example.com"
 | |
| 	cnProfile.omitCommonName = true
 | |
| 	_, issuanceToken, err = signer.Prepare(cnProfile, ir)
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err = signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err = x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 	test.AssertEquals(t, cert.Subject.CommonName, "")
 | |
| }
 | |
| 
 | |
| func TestIssueOmissions(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	pc := defaultProfileConfig()
 | |
| 	pc.OmitCommonName = true
 | |
| 	pc.OmitKeyEncipherment = true
 | |
| 	pc.OmitClientAuth = true
 | |
| 	pc.OmitSKID = true
 | |
| 	pc.IgnoredLints = []string{
 | |
| 		// Reduce the lint ignores to just the minimal (SCT-related) set.
 | |
| 		"w_ct_sct_policy_count_unsatisfied",
 | |
| 		"e_scts_from_same_operator",
 | |
| 		// Ignore the warning about *not* including the SubjectKeyIdentifier extension:
 | |
| 		// zlint has both lints (one enforcing RFC5280, the other the BRs).
 | |
| 		"w_ext_subject_key_identifier_missing_sub_cert",
 | |
| 	}
 | |
| 	prof, err := NewProfile(pc)
 | |
| 	test.AssertNotError(t, err, "building test profile")
 | |
| 
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 
 | |
| 	pk, err := rsa.GenerateKey(rand.Reader, 2048)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := signer.Prepare(prof, &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		CommonName:      "example.com",
 | |
| 		IncludeCTPoison: true,
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err := signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err := x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 
 | |
| 	test.AssertEquals(t, cert.Subject.CommonName, "")
 | |
| 	test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature)
 | |
| 	test.AssertDeepEquals(t, cert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
 | |
| 	test.AssertEquals(t, len(cert.SubjectKeyId), 0)
 | |
| }
 | |
| 
 | |
| func TestIssueCTPoison(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		IncludeCTPoison: true,
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err := signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err := x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 	err = cert.CheckSignatureFrom(issuerCert.Certificate)
 | |
| 	test.AssertNotError(t, err, "signature validation failed")
 | |
| 	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
 | |
| 	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
 | |
| 	test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, CT Poison
 | |
| 	test.AssertDeepEquals(t, cert.Extensions[8], ctPoisonExt)
 | |
| }
 | |
| 
 | |
| func mustDecodeB64(b string) []byte {
 | |
| 	out, err := base64.StdEncoding.DecodeString(b)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func TestIssueSCTList(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 | |
| 	test.AssertNotError(t, err, "failed to load log list")
 | |
| 
 | |
| 	pc := defaultProfileConfig()
 | |
| 	pc.IgnoredLints = []string{
 | |
| 		// Only ignore the SKID lint, i.e., don't ignore the "missing SCT" lints.
 | |
| 		"w_ext_subject_key_identifier_not_recommended_subscriber",
 | |
| 	}
 | |
| 	enforceSCTsProfile, err := NewProfile(pc)
 | |
| 	test.AssertNotError(t, err, "NewProfile failed")
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := signer.Prepare(enforceSCTsProfile, &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	precertBytes, err := signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	precert, err := x509.ParseCertificate(precertBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 
 | |
| 	sctList := []ct.SignedCertificateTimestamp{
 | |
| 		{
 | |
| 			SCTVersion: ct.V1,
 | |
| 			LogID:      ct.LogID{KeyID: *(*[32]byte)(mustDecodeB64("OJiMlNA1mMOTLd/pI7q68npCDrlsQeFaqAwasPwEvQM="))},
 | |
| 		},
 | |
| 		{
 | |
| 			SCTVersion: ct.V1,
 | |
| 			LogID:      ct.LogID{KeyID: *(*[32]byte)(mustDecodeB64("UtToynGEyMkkXDMQei8Ll54oMwWHI0IieDEKs12/Td4="))},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	request2, err := RequestFromPrecert(precert, sctList)
 | |
| 	test.AssertNotError(t, err, "generating request from precert")
 | |
| 
 | |
| 	_, issuanceToken2, err := signer.Prepare(enforceSCTsProfile, request2)
 | |
| 	test.AssertNotError(t, err, "preparing final cert issuance")
 | |
| 
 | |
| 	finalCertBytes, err := signer.Issue(issuanceToken2)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 
 | |
| 	finalCert, err := x509.ParseCertificate(finalCertBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 
 | |
| 	err = finalCert.CheckSignatureFrom(issuerCert.Certificate)
 | |
| 	test.AssertNotError(t, err, "signature validation failed")
 | |
| 	test.AssertByteEquals(t, finalCert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
 | |
| 	test.AssertDeepEquals(t, finalCert.PublicKey, pk.Public())
 | |
| 	test.AssertEquals(t, len(finalCert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, SCT list
 | |
| 	test.AssertDeepEquals(t, finalCert.Extensions[8], pkix.Extension{
 | |
| 		Id: sctListOID,
 | |
| 		Value: []byte{
 | |
| 			4, 100, 0, 98, 0, 47, 0, 56, 152, 140, 148, 208, 53, 152, 195, 147, 45,
 | |
| 			223, 233, 35, 186, 186, 242, 122, 66, 14, 185, 108, 65, 225, 90, 168, 12,
 | |
| 			26, 176, 252, 4, 189, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47,
 | |
| 			0, 82, 212, 232, 202, 113, 132, 200, 201, 36, 92, 51, 16, 122, 47, 11,
 | |
| 			151, 158, 40, 51, 5, 135, 35, 66, 34, 120, 49, 10, 179, 93, 191, 77, 222,
 | |
| 			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestIssueMustStaple(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:         MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:      goodSKID,
 | |
| 		Serial:            []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:          []string{"example.com"},
 | |
| 		IncludeMustStaple: true,
 | |
| 		NotBefore:         fc.Now(),
 | |
| 		NotAfter:          fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison:   true,
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "Prepare failed")
 | |
| 	certBytes, err := signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "Issue failed")
 | |
| 	cert, err := x509.ParseCertificate(certBytes)
 | |
| 	test.AssertNotError(t, err, "failed to parse certificate")
 | |
| 	err = cert.CheckSignatureFrom(issuerCert.Certificate)
 | |
| 	test.AssertNotError(t, err, "signature validation failed")
 | |
| 	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
 | |
| 	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
 | |
| 	test.AssertEquals(t, len(cert.Extensions), 10) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Must-Staple, Poison
 | |
| 	test.AssertDeepEquals(t, cert.Extensions[9], mustStapleExt)
 | |
| }
 | |
| 
 | |
| func TestIssueBadLint(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	pc := defaultProfileConfig()
 | |
| 	pc.IgnoredLints = []string{}
 | |
| 	noSkipLintsProfile, err := NewProfile(pc)
 | |
| 	test.AssertNotError(t, err, "NewProfile failed")
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, _, err = signer.Prepare(noSkipLintsProfile, &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example-com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	test.AssertError(t, err, "Prepare didn't fail")
 | |
| 	test.AssertErrorIs(t, err, linter.ErrLinting)
 | |
| 	test.AssertContains(t, err.Error(), "tbsCertificate linting failed: failed lint(s)")
 | |
| }
 | |
| 
 | |
| func TestIssuanceToken(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 
 | |
| 	_, err = signer.Issue(&issuanceToken{})
 | |
| 	test.AssertError(t, err, "expected issuance with a zero token to fail")
 | |
| 
 | |
| 	_, err = signer.Issue(nil)
 | |
| 	test.AssertError(t, err, "expected issuance with a nil token to fail")
 | |
| 
 | |
| 	pk, err := rsa.GenerateKey(rand.Reader, 2048)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "expected Prepare to succeed")
 | |
| 	_, err = signer.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "expected first issuance to succeed")
 | |
| 
 | |
| 	_, err = signer.Issue(issuanceToken)
 | |
| 	test.AssertError(t, err, "expected second issuance with the same issuance token to fail")
 | |
| 	test.AssertContains(t, err.Error(), "issuance token already redeemed")
 | |
| 
 | |
| 	_, issuanceToken, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "expected Prepare to succeed")
 | |
| 
 | |
| 	signer2, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 
 | |
| 	_, err = signer2.Issue(issuanceToken)
 | |
| 	test.AssertError(t, err, "expected redeeming an issuance token with the wrong issuer to fail")
 | |
| 	test.AssertContains(t, err.Error(), "wrong issuer")
 | |
| }
 | |
| 
 | |
| func TestInvalidProfile(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 
 | |
| 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 | |
| 	test.AssertNotError(t, err, "failed to load log list")
 | |
| 
 | |
| 	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 		precertDER:      []byte{6, 6, 6},
 | |
| 	})
 | |
| 	test.AssertError(t, err, "Invalid IssuanceRequest")
 | |
| 
 | |
| 	_, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 | |
| 		PublicKey:    MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId: goodSKID,
 | |
| 		Serial:       []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		DNSNames:     []string{"example.com"},
 | |
| 		NotBefore:    fc.Now(),
 | |
| 		NotAfter:     fc.Now().Add(time.Hour - time.Second),
 | |
| 		sctList: []ct.SignedCertificateTimestamp{
 | |
| 			{
 | |
| 				SCTVersion: ct.V1,
 | |
| 				LogID:      ct.LogID{KeyID: *(*[32]byte)(mustDecodeB64("OJiMlNA1mMOTLd/pI7q68npCDrlsQeFaqAwasPwEvQM="))},
 | |
| 			},
 | |
| 		},
 | |
| 		precertDER: []byte{},
 | |
| 	})
 | |
| 	test.AssertError(t, err, "Invalid IssuanceRequest")
 | |
| }
 | |
| 
 | |
| // Generate a precert from one profile and a final cert from another, and verify
 | |
| // that the final cert errors out when linted because the lint cert doesn't
 | |
| // corresponding with the precert.
 | |
| func TestMismatchedProfiles(t *testing.T) {
 | |
| 	fc := clock.NewFake()
 | |
| 	fc.Set(time.Now())
 | |
| 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 | |
| 	test.AssertNotError(t, err, "failed to load log list")
 | |
| 
 | |
| 	issuer1, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 
 | |
| 	pc := defaultProfileConfig()
 | |
| 	pc.IgnoredLints = append(pc.IgnoredLints, "w_subject_common_name_included")
 | |
| 	cnProfile, err := NewProfile(pc)
 | |
| 	test.AssertNotError(t, err, "NewProfile failed")
 | |
| 
 | |
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 	test.AssertNotError(t, err, "failed to generate test key")
 | |
| 	_, issuanceToken, err := issuer1.Prepare(cnProfile, &IssuanceRequest{
 | |
| 		PublicKey:       MarshalablePublicKey{pk.Public()},
 | |
| 		SubjectKeyId:    goodSKID,
 | |
| 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
 | |
| 		CommonName:      "example.com",
 | |
| 		DNSNames:        []string{"example.com"},
 | |
| 		NotBefore:       fc.Now(),
 | |
| 		NotAfter:        fc.Now().Add(time.Hour - time.Second),
 | |
| 		IncludeCTPoison: true,
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "making IssuanceRequest")
 | |
| 
 | |
| 	precertDER, err := issuer1.Issue(issuanceToken)
 | |
| 	test.AssertNotError(t, err, "signing precert")
 | |
| 
 | |
| 	// Create a new profile that differs slightly (no common name)
 | |
| 	pc = defaultProfileConfig()
 | |
| 	pc.OmitCommonName = false
 | |
| 	test.AssertNotError(t, err, "building test lint registry")
 | |
| 	noCNProfile, err := NewProfile(pc)
 | |
| 	test.AssertNotError(t, err, "NewProfile failed")
 | |
| 
 | |
| 	issuer2, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 | |
| 	test.AssertNotError(t, err, "NewIssuer failed")
 | |
| 
 | |
| 	sctList := []ct.SignedCertificateTimestamp{
 | |
| 		{
 | |
| 			SCTVersion: ct.V1,
 | |
| 			LogID:      ct.LogID{KeyID: *(*[32]byte)(mustDecodeB64("OJiMlNA1mMOTLd/pI7q68npCDrlsQeFaqAwasPwEvQM="))},
 | |
| 		},
 | |
| 		{
 | |
| 			SCTVersion: ct.V1,
 | |
| 			LogID:      ct.LogID{KeyID: *(*[32]byte)(mustDecodeB64("UtToynGEyMkkXDMQei8Ll54oMwWHI0IieDEKs12/Td4="))},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	precert, err := x509.ParseCertificate(precertDER)
 | |
| 	test.AssertNotError(t, err, "parsing precert")
 | |
| 
 | |
| 	request2, err := RequestFromPrecert(precert, sctList)
 | |
| 	test.AssertNotError(t, err, "RequestFromPrecert")
 | |
| 	request2.CommonName = ""
 | |
| 
 | |
| 	_, _, err = issuer2.Prepare(noCNProfile, request2)
 | |
| 	test.AssertError(t, err, "preparing final cert issuance")
 | |
| 	test.AssertContains(t, err.Error(), "precert does not correspond to linted final cert")
 | |
| }
 | |
| 
 | |
| func TestProfileHash(t *testing.T) {
 | |
| 	// A profile _with_ IncludeCRLDistributionPoints.
 | |
| 	// Hash calculated over the ASN.1 encoding of the `ProfileConfigNew`.
 | |
| 	profile := ProfileConfig{
 | |
| 		IncludeCRLDistributionPoints: true,
 | |
| 		AllowMustStaple:              true,
 | |
| 		OmitCommonName:               true,
 | |
| 		OmitKeyEncipherment:          false,
 | |
| 		OmitClientAuth:               false,
 | |
| 		OmitSKID:                     true,
 | |
| 		MaxValidityPeriod:            config.Duration{Duration: time.Hour},
 | |
| 		MaxValidityBackdate:          config.Duration{Duration: time.Second},
 | |
| 		LintConfig:                   "example/config.toml",
 | |
| 		IgnoredLints:                 []string{"one", "two"},
 | |
| 	}
 | |
| 	hash, err := profile.hash()
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("hashing %+v: %s", profile, err)
 | |
| 	}
 | |
| 	expectedHash := "d2a6c9f0aa37d2ac0b15476cb6e0ae9b98ba59b1321d8d6da26efc620581c53d"
 | |
| 	if expectedHash != fmt.Sprintf("%x", hash) {
 | |
| 		t.Errorf("%+v.Hash()=%x, want %s", profile, hash, expectedHash)
 | |
| 	}
 | |
| }
 |