diff --git a/authority/tls.go b/authority/tls.go index 883c13a9..3afc52cc 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -30,8 +30,12 @@ type SignOptions struct { } var ( + // Step extensions OIDs stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64} stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...) + // Certificate transparency extensions OIDs + ctPoisonOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3} + ctSigendCertificateTimestampOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2} ) type stepProvisionerASN1 struct { @@ -151,6 +155,25 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext http.StatusInternalServerError, errContext} } + if a.ctClient != nil { + // Submit precertificate chain and get SCTs + scts, err := a.ctClient.GetSCTs(crtBytes, issIdentity.Crt.Raw) + if err != nil { + return nil, nil, &apiError{errors.Wrap(err, "sign: error getting SCTs for certificate"), + http.StatusBadGateway, errContext} + } + + // Remove ct poison extension and add sct extension + leaf.RemoveExtension(ctPoisonOID) + leaf.AddExtension(scts.GetExtension()) + + // Recreate final certificate + if crtBytes, err = leaf.CreateCertificate(); err != nil { + return nil, nil, &apiError{errors.Wrap(err, "sign: error creating final leaf certificate"), + http.StatusInternalServerError, errContext} + } + } + serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"), @@ -163,42 +186,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext http.StatusInternalServerError, errContext} } - // Certificate transparency if a.ctClient != nil { - scts, err := a.ctClient.GetSCTs(serverCert, caCert) - if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error getting SCTs for certificate"), - http.StatusBadGateway, errContext} - } - crt := leaf.Subject() - crt.ExtraExtensions = append(crt.ExtraExtensions, scts.GetExtension()) - for i, ext := range crt.ExtraExtensions { - if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}) { - crt.ExtraExtensions = append(crt.ExtraExtensions[:i], crt.ExtraExtensions[i+1:]...) - break - } - } - - for i, ext := range crt.Extensions { - if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}) { - crt.Extensions = append(crt.Extensions[:i], crt.Extensions[i+1:]...) - break - } - } - - crtBytes, err = leaf.CreateCertificate() - if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error creating new leaf certificate"), - http.StatusInternalServerError, errContext} - } - - serverCert, err = x509.ParseCertificate(crtBytes) - if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"), - http.StatusInternalServerError, errContext} - } - - if err := a.ctClient.SubmitToLogs(serverCert, caCert); err != nil { + // Submit final certificate chain + if err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil { return nil, nil, &apiError{errors.Wrap(err, "sign: error submitting final certificate to ct logs"), http.StatusBadGateway, errContext} } @@ -264,17 +254,45 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi PolicyIdentifiers: oldCert.PolicyIdentifiers, } - leaf, err := x509util.NewLeafProfileWithTemplate(newCert, - issIdentity.Crt, issIdentity.Key) + opts := []x509util.WithOption{} + // Add CT Poison extension + if a.ctClient != nil { + opts = append(opts, x509util.WithCTPoison()) + } + + leaf, err := x509util.NewLeafProfileWithTemplate(newCert, issIdentity.Crt, issIdentity.Key, opts...) if err != nil { return nil, nil, &apiError{err, http.StatusInternalServerError, context{}} } + + // Remove previous SCTs if any + leaf.RemoveExtension(ctSigendCertificateTimestampOID) + crtBytes, err := leaf.CreateCertificate() if err != nil { return nil, nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"), http.StatusInternalServerError, context{}} } + if a.ctClient != nil { + // Submit precertificate chain and get SCTs + scts, err := a.ctClient.GetSCTs(crtBytes, issIdentity.Crt.Raw) + if err != nil { + return nil, nil, &apiError{errors.Wrap(err, "renew: error getting SCTs for certificate"), + http.StatusBadGateway, context{}} + } + + // Remove ct poison extension and add sct extension + leaf.RemoveExtension(ctPoisonOID) + leaf.AddExtension(scts.GetExtension()) + + // Recreate final certificate + if crtBytes, err = leaf.CreateCertificate(); err != nil { + return nil, nil, &apiError{errors.Wrap(err, "renew: error creating final leaf certificate"), + http.StatusInternalServerError, context{}} + } + } + serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { return nil, nil, &apiError{errors.Wrap(err, "error parsing new server certificate"), @@ -286,14 +304,30 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi http.StatusInternalServerError, context{}} } + if a.ctClient != nil { + // Submit final certificate chain + if err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil { + return nil, nil, &apiError{errors.Wrap(err, "renew: error submitting final certificate to ct logs"), + http.StatusBadGateway, context{}} + } + } + return serverCert, caCert, nil } // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { + opts := []x509util.WithOption{ + x509util.WithHosts(strings.Join(a.config.DNSNames, ",")), + } + + // Add CT Poison extension + if a.ctClient != nil { + opts = append(opts, x509util.WithCTPoison()) + } + profile, err := x509util.NewLeafProfile("Step Online CA", - a.intermediateIdentity.Crt, a.intermediateIdentity.Key, - x509util.WithHosts(strings.Join(a.config.DNSNames, ","))) + a.intermediateIdentity.Crt, a.intermediateIdentity.Key, opts...) if err != nil { return nil, err } @@ -303,6 +337,23 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { return nil, err } + if a.ctClient != nil { + // Submit precertificate chain and get SCTs + scts, err := a.ctClient.GetSCTs(crtBytes, a.intermediateIdentity.Crt.Raw) + if err != nil { + return nil, errors.Wrap(err, "error getting SCTs for certificate") + } + + // Remove ct poison extension and add sct extension + profile.RemoveExtension(ctPoisonOID) + profile.AddExtension(scts.GetExtension()) + + // Recreate final certificate + if crtBytes, err = profile.CreateCertificate(); err != nil { + return nil, errors.Wrap(err, "error creating final leaf certificate") + } + } + keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey()) if err != nil { return nil, err @@ -319,6 +370,14 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { if err != nil { return nil, err } + + if a.ctClient != nil { + // Submit final certificate chain + if err := a.ctClient.SubmitToLogs(crtBytes, intermediatePEM.Bytes); err != nil { + return nil, errors.Wrap(err, "error submitting final certificate to ct logs") + } + } + tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM)) diff --git a/ct/ct.go b/ct/ct.go index c74f9ff6..291f1a9a 100644 --- a/ct/ct.go +++ b/ct/ct.go @@ -6,7 +6,6 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/pem" - "fmt" "io/ioutil" "log" "net/http" @@ -45,8 +44,8 @@ func (c *Config) Validate() error { // Client is the interfaced used to communicate with the certificate transparency logs. type Client interface { - GetSCTs(chain ...*x509.Certificate) (*SCT, error) - SubmitToLogs(chain ...*x509.Certificate) error + GetSCTs(asn1Data ...[]byte) (*SCT, error) + SubmitToLogs(asn1Data ...[]byte) error } type logClient interface { @@ -139,14 +138,15 @@ func New(c Config) (*ClientImpl, error) { // GetSCTs submit the precertificate to the logs and returns the list of SCTs to // embed into the certificate. -func (c *ClientImpl) GetSCTs(chain ...*x509.Certificate) (*SCT, error) { - ctChain := chainFromCerts(chain) +func (c *ClientImpl) GetSCTs(asn1Data ...[]byte) (*SCT, error) { + chain := chainFromDERs(asn1Data) ctx, cancel := context.WithTimeout(context.Background(), c.timeout) defer cancel() - sct, err := c.logClient.AddPreChain(ctx, ctChain) + sct, err := c.logClient.AddPreChain(ctx, chain) if err != nil { return nil, errors.Wrapf(err, "failed to get SCT from %s", c.config.URI) } + logLeafHash(asn1Data, sct, true) return &SCT{ LogURL: c.config.URI, SCT: sct, @@ -154,32 +154,55 @@ func (c *ClientImpl) GetSCTs(chain ...*x509.Certificate) (*SCT, error) { } // SubmitToLogs submits the certificate to the certificate transparency logs. -func (c *ClientImpl) SubmitToLogs(chain ...*x509.Certificate) error { - ctChain := chainFromCerts(chain) +func (c *ClientImpl) SubmitToLogs(asn1Data ...[]byte) error { + chain := chainFromDERs(asn1Data) ctx, cancel := context.WithTimeout(context.Background(), c.timeout) defer cancel() - sct, err := c.logClient.AddChain(ctx, ctChain) + sct, err := c.logClient.AddChain(ctx, chain) if err != nil { return errors.Wrapf(err, "failed submit certificate to %s", c.config.URI) } - - // Calculate the leaf hash - leafEntry := ct.CreateX509MerkleTreeLeaf(ctChain[0], sct.Timestamp) - leafHash, err := ct.LeafHashForLeaf(leafEntry) - if err != nil { - log.Println(err) - } - // Display the SCT - fmt.Printf("LogID: %x\n", sct.LogID.KeyID[:]) - fmt.Printf("LeafHash: %x\n", leafHash) - + logLeafHash(asn1Data, sct, false) return nil } -func chainFromCerts(certs []*x509.Certificate) []ct.ASN1Cert { +func chainFromDERs(asn1Data [][]byte) []ct.ASN1Cert { var chain []ct.ASN1Cert - for _, cert := range certs { - chain = append(chain, ct.ASN1Cert{Data: cert.Raw}) + for _, der := range asn1Data { + chain = append(chain, ct.ASN1Cert{Data: der}) } return chain } + +func logLeafHash(asn1Data [][]byte, sct *ct.SignedCertificateTimestamp, isPrecert bool) { + var etype ct.LogEntryType + if isPrecert { + etype = ct.PrecertLogEntryType + } else { + etype = ct.X509LogEntryType + } + + chain := make([]*ctx509.Certificate, len(asn1Data)) + for i := range asn1Data { + cert, err := ctx509.ParseCertificate(asn1Data[i]) + if err != nil { + log.Println(err) + return + } + chain[i] = cert + } + + leafEntry, err := ct.MerkleTreeLeafFromChain(chain, etype, sct.Timestamp) + if err != nil { + log.Println(err) + return + } + + leafHash, err := ct.LeafHashForLeaf(leafEntry) + if err != nil { + log.Println(err) + return + } + + log.Printf("LogID: %x, LeafHash: %x, Timestamp: %d\n", sct.LogID.KeyID[:], leafHash, sct.Timestamp) +}