// Copyright 2017 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "errors" "fmt" "time" "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/certificate-transparency-go/x509" ct "github.com/google/certificate-transparency-go" ) // ChainGenerator encapsulates objects that can generate certificate chains for testing. type ChainGenerator interface { // CertChain generates a certificate chain. CertChain() ([]ct.ASN1Cert, error) // PreCertChain generates a precertificate chain, and also returns the leaf TBS data PreCertChain() ([]ct.ASN1Cert, []byte, error) } // GeneratorFactory is a method that builds a Log-specific ChainGenerator. type GeneratorFactory func(c *configpb.LogConfig) (ChainGenerator, error) // SyntheticChainGenerator builds synthetic certificate chains based on // a template chain and intermediate CA private key. type SyntheticChainGenerator struct { chain []ct.ASN1Cert leafCert *x509.Certificate caCert *x509.Certificate // Signer which matches the caCert signer crypto.Signer notAfter time.Time } // NewSyntheticChainGenerator returns a ChainGenerator that mints synthetic certificates based on the // given template chain. The provided signer should match the public key of the first issuer cert. func NewSyntheticChainGenerator(chain []ct.ASN1Cert, signer crypto.Signer, notAfter time.Time) (ChainGenerator, error) { if len(chain) < 2 { return nil, fmt.Errorf("chain too short (%d)", len(chain)) } leaf, err := x509.ParseCertificate(chain[0].Data) if err != nil { return nil, fmt.Errorf("failed to parse leaf cert: %v", err) } issuer, err := x509.ParseCertificate(chain[1].Data) if err != nil { return nil, fmt.Errorf("failed to parse issuer cert: %v", err) } if notAfter.IsZero() { notAfter = time.Now().Add(24 * time.Hour) } return &SyntheticChainGenerator{ chain: chain, leafCert: leaf, caCert: issuer, signer: signer, notAfter: notAfter, }, nil } // CertChain builds a new synthetic chain with a fresh leaf cert, changing SubjectKeyId and re-signing. func (g *SyntheticChainGenerator) CertChain() ([]ct.ASN1Cert, error) { cert := *g.leafCert cert.NotAfter = g.notAfter chain := make([]ct.ASN1Cert, len(g.chain)) copy(chain[1:], g.chain[1:]) // Randomize the subject key ID. randData := make([]byte, 128) if _, err := rand.Read(randData); err != nil { return nil, fmt.Errorf("failed to read random data: %v", err) } cert.SubjectKeyId = randData // Create a fresh certificate, signed by the intermediate CA, for the leaf. var err error chain[0].Data, err = x509.CreateCertificate(rand.Reader, &cert, g.caCert, cert.PublicKey, g.signer) if err != nil { return nil, fmt.Errorf("failed to create certificate: %v", err) } return chain, nil } // PreCertChain builds a new synthetic precert chain; also returns the leaf TBS data. func (g *SyntheticChainGenerator) PreCertChain() ([]ct.ASN1Cert, []byte, error) { prechain := make([]ct.ASN1Cert, len(g.chain)) copy(prechain[1:], g.chain[1:]) cert, err := x509.ParseCertificate(g.chain[0].Data) if err != nil { return nil, nil, fmt.Errorf("failed to parse certificate to build precert from: %v", err) } cert.NotAfter = g.notAfter prechain[0].Data, err = buildNewPrecertData(cert, g.caCert, g.signer) if err != nil { return nil, nil, fmt.Errorf("failed to create certificate: %v", err) } // For later verification, build the leaf TBS data that is included in the log. tbs, err := buildLeafTBS(prechain[0].Data, nil) if err != nil { return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err) } return prechain, tbs, nil } // buildLeafTBS builds the raw pre-cert data (a DER-encoded TBSCertificate) that is included // in the log. func buildLeafTBS(precertData []byte, preIssuer *x509.Certificate) ([]byte, error) { reparsed, err := x509.ParseCertificate(precertData) if err != nil { return nil, fmt.Errorf("failed to re-parse created precertificate: %v", err) } return x509.BuildPrecertTBS(reparsed.RawTBSCertificate, preIssuer) } // makePreIssuerPrecertChain builds a precert chain where the pre-cert is signed by a new // pre-issuer intermediate. func makePreIssuerPrecertChain(chain []ct.ASN1Cert, issuer *x509.Certificate, signer crypto.Signer) ([]ct.ASN1Cert, []byte, error) { prechain := make([]ct.ASN1Cert, len(chain)+1) copy(prechain[2:], chain[1:]) // Create a new private key and intermediate CA cert to go with it. preSigner, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, fmt.Errorf("failed to create pre-issuer private key: %v", err) } preIssuerTemplate := *issuer preIssuerTemplate.RawSubject = nil preIssuerTemplate.Subject.CommonName += "PrecertIssuer" preIssuerTemplate.PublicKeyAlgorithm = x509.ECDSA preIssuerTemplate.PublicKey = preSigner.Public() preIssuerTemplate.ExtKeyUsage = append(preIssuerTemplate.ExtKeyUsage, x509.ExtKeyUsageCertificateTransparency) // Set a new subject-key-id for the intermediate (to ensure it's different from the true // issuer's subject-key-id). randData := make([]byte, 128) if _, err := rand.Read(randData); err != nil { return nil, nil, fmt.Errorf("failed to read random data: %v", err) } preIssuerTemplate.SubjectKeyId = randData prechain[1].Data, err = x509.CreateCertificate(rand.Reader, &preIssuerTemplate, issuer, preIssuerTemplate.PublicKey, signer) if err != nil { return nil, nil, fmt.Errorf("failed to create pre-issuer certificate: %v", err) } // Parse the pre-issuer back to a fully-populated x509.Certificate. preIssuer, err := x509.ParseCertificate(prechain[1].Data) if err != nil { return nil, nil, fmt.Errorf("failed to re-parse generated pre-issuer: %v", err) } cert, err := x509.ParseCertificate(chain[0].Data) if err != nil { return nil, nil, fmt.Errorf("failed to parse certificate to build precert from: %v", err) } prechain[0].Data, err = buildNewPrecertData(cert, preIssuer, preSigner) if err != nil { return nil, nil, fmt.Errorf("failed to create certificate: %v", err) } if err := verifyChain(prechain); err != nil { return nil, nil, fmt.Errorf("failed to verify just-created prechain: %v", err) } // The leaf data has the poison removed and the issuance information changed. tbs, err := buildLeafTBS(prechain[0].Data, preIssuer) if err != nil { return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err) } return prechain, tbs, nil } // verifyChain checks that a chain of certificates validates locally. func verifyChain(rawChain []ct.ASN1Cert) error { chain := make([]*x509.Certificate, 0, len(rawChain)) for i, c := range rawChain { cert, err := x509.ParseCertificate(c.Data) if err != nil { return fmt.Errorf("failed to parse rawChain[%d]: %v", i, err) } chain = append(chain, cert) } // First verify signatures cert-by-cert. for i := 1; i < len(chain); i++ { issuer := chain[i] cert := chain[i-1] if err := cert.CheckSignatureFrom(issuer); err != nil { return fmt.Errorf("failed to check signature on rawChain[%d] using rawChain[%d]: %v", i-1, i, err) } } // Now verify the chain as a whole intermediatePool := x509.NewCertPool() for i := 1; i < len(chain); i++ { // Don't check path-len constraints chain[i].MaxPathLen = -1 intermediatePool.AddCert(chain[i]) } rootPool := x509.NewCertPool() rootPool.AddCert(chain[len(chain)-1]) opts := x509.VerifyOptions{ Roots: rootPool, Intermediates: intermediatePool, DisableTimeChecks: true, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } chain[0].UnhandledCriticalExtensions = nil chains, err := chain[0].Verify(opts) if err != nil { return fmt.Errorf("chain[0].Verify(%+v) failed: %v", opts, err) } if len(chains) == 0 { return errors.New("no path to root found when trying to validate chains") } return nil } // SyntheticGeneratorFactory returns a function that creates per-Log ChainGenerator instances // that create synthetic certificates (details of which are specified by the arguments). func SyntheticGeneratorFactory(testDir, leafNotAfter string) (GeneratorFactory, error) { leafChain, err := GetChain(testDir, "leaf01.chain") if err != nil { return nil, fmt.Errorf("failed to load certificate: %v", err) } signer, err := MakeSigner(testDir) if err != nil { return nil, fmt.Errorf("failed to retrieve signer for re-signing: %v", err) } var notAfterOverride time.Time if leafNotAfter != "" { notAfterOverride, err = time.Parse(time.RFC3339, leafNotAfter) if err != nil { return nil, fmt.Errorf("failed to parse leaf notAfter: %v", err) } } // Build a synthetic generator for each target log. return func(c *configpb.LogConfig) (ChainGenerator, error) { notAfter := notAfterOverride if notAfter.IsZero() { var err error notAfter, err = NotAfterForLog(c) if err != nil { return nil, fmt.Errorf("failed to determine notAfter for %s: %v", c.Prefix, err) } } return NewSyntheticChainGenerator(leafChain, signer, notAfter) }, nil }