From a86b5261cbbc37a15ff822fd5eba6bc2cfa5b017 Mon Sep 17 00:00:00 2001 From: Matthew McPherrin Date: Fri, 23 Jun 2023 13:29:18 -0400 Subject: [PATCH] WIP concept for ct validation in RA --- ctpolicy/ctpolicy.go | 42 +- ra/ra.go | 5 + .../gossip/minimal/x509ext/x509ext.go | 92 ++ .../x509util/files.go | 116 +++ .../x509util/fuzz.go | 26 + .../x509util/pem_cert_pool.go | 120 +++ .../x509util/revoked.go | 169 ++++ .../x509util/x509util.go | 900 ++++++++++++++++++ vendor/modules.txt | 2 + 9 files changed, 1471 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go create mode 100644 vendor/github.com/google/certificate-transparency-go/x509util/files.go create mode 100644 vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go create mode 100644 vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go create mode 100644 vendor/github.com/google/certificate-transparency-go/x509util/revoked.go create mode 100644 vendor/github.com/google/certificate-transparency-go/x509util/x509util.go diff --git a/ctpolicy/ctpolicy.go b/ctpolicy/ctpolicy.go index 5f9e0b55fd2..0e8bda1a612 100644 --- a/ctpolicy/ctpolicy.go +++ b/ctpolicy/ctpolicy.go @@ -6,12 +6,16 @@ import ( "strings" "time" + ct "github.com/google/certificate-transparency-go" + ctx509 "github.com/google/certificate-transparency-go/x509" + "github.com/google/certificate-transparency-go/x509util" + "github.com/prometheus/client_golang/prometheus" + "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/ctpolicy/loglist" berrors "github.com/letsencrypt/boulder/errors" blog "github.com/letsencrypt/boulder/log" pubpb "github.com/letsencrypt/boulder/publisher/proto" - "github.com/prometheus/client_golang/prometheus" ) const ( @@ -26,6 +30,8 @@ type CTPolicy struct { sctLogs loglist.List infoLogs loglist.List finalLogs loglist.List + logVerifiers map[[32]byte]ct.SignatureVerifier + issuers map[string]*ctx509.Certificate stagger time.Duration log blog.Logger winnerCounter *prometheus.CounterVec @@ -93,6 +99,7 @@ func New(pub pubpb.PublisherClient, sctLogs loglist.List, infoLogs loglist.List, winnerCounter: winnerCounter, operatorGroupsGauge: operatorGroupsGauge, shardExpiryGauge: shardExpiryGauge, + /* TODO: issuers and validators */ } } @@ -240,3 +247,36 @@ func (ctp *CTPolicy) submitPrecertInformational(cert core.CertDER, expiration ti func (ctp *CTPolicy) SubmitFinalCert(cert core.CertDER, expiration time.Time) { ctp.submitAllBestEffort(cert, false, expiration) } + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +// ValidateFinalCert checks that this final cert has valid SCTs embedded. +func (ctp *CTPolicy) ValidateFinalCert(certDER core.CertDER) error { + leaf := must(ctx509.ParseCertificate(certDER)) + issuer := ctp.issuers[leaf.Issuer.CommonName] + chain := []*ctx509.Certificate{leaf, issuer} + + for _, sctData := range leaf.SCTList.SCTList { + sct, err := x509util.ExtractSCT(&sctData) + if err != nil { + return err + } + + merkleLeaf, err := ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp) + if err != nil { + return err + } + + err = ctp.logVerifiers[sct.LogID.KeyID].VerifySCTSignature(*sct, ct.LogEntry{Leaf: *merkleLeaf}) + if err != nil { + return err + } + } + + return nil +} diff --git a/ra/ra.go b/ra/ra.go index 69ebd78c278..b2d2f063761 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -1306,6 +1306,11 @@ func (ra *RegistrationAuthorityImpl) issueCertificateInner( // Asynchronously submit the final certificate to any configured logs go ra.ctpolicy.SubmitFinalCert(cert.Der, parsedCertificate.NotAfter) + err = ra.ctpolicy.ValidateFinalCert(cert.Der) + if err != nil { + return nil, wrapError(err, "bad CT, or my bad code?") + } + // TODO(#6587): Make this error case Very Alarming err = ra.matchesCSR(parsedCertificate, csr) if err != nil { diff --git a/vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go b/vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go new file mode 100644 index 00000000000..540e717d74e --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go @@ -0,0 +1,92 @@ +// Copyright 2018 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 x509ext holds extensions types and values for minimal gossip. +package x509ext + +import ( + "errors" + "fmt" + + "github.com/google/certificate-transparency-go/asn1" + "github.com/google/certificate-transparency-go/tls" + "github.com/google/certificate-transparency-go/x509" + + ct "github.com/google/certificate-transparency-go" +) + +// OIDExtensionCTSTH is the OID value for an X.509 extension that holds +// a log STH value. +// TODO(drysdale): get an official OID value +var OIDExtensionCTSTH = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5} + +// OIDExtKeyUsageCTMinimalGossip is the OID value for an extended key usage +// (EKU) that indicates a leaf certificate is used for the validation of STH +// values from public CT logs. +// TODO(drysdale): get an official OID value +var OIDExtKeyUsageCTMinimalGossip = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 6} + +// LogSTHInfo is the structure that gets TLS-encoded into the X.509 extension +// identified by OIDExtensionCTSTH. +type LogSTHInfo struct { + LogURL []byte `tls:"maxlen:255"` + Version tls.Enum `tls:"maxval:255"` + TreeSize uint64 + Timestamp uint64 + SHA256RootHash ct.SHA256Hash + TreeHeadSignature ct.DigitallySigned +} + +// LogSTHInfoFromCert retrieves the STH information embedded in a certificate. +func LogSTHInfoFromCert(cert *x509.Certificate) (*LogSTHInfo, error) { + for _, ext := range cert.Extensions { + if ext.Id.Equal(OIDExtensionCTSTH) { + var sthInfo LogSTHInfo + rest, err := tls.Unmarshal(ext.Value, &sthInfo) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal STH: %v", err) + } else if len(rest) > 0 { + return nil, fmt.Errorf("trailing data (%d bytes) after STH", len(rest)) + } + return &sthInfo, nil + } + } + return nil, errors.New("no STH extension found") +} + +// HasSTHInfo indicates whether a certificate has embedded STH information. +func HasSTHInfo(cert *x509.Certificate) bool { + for _, ext := range cert.Extensions { + if ext.Id.Equal(OIDExtensionCTSTH) { + return true + } + } + return false +} + +// STHFromCert retrieves the STH embedded in a certificate; note the returned STH +// does not have the LogID field filled in. +func STHFromCert(cert *x509.Certificate) (*ct.SignedTreeHead, error) { + sthInfo, err := LogSTHInfoFromCert(cert) + if err != nil { + return nil, err + } + return &ct.SignedTreeHead{ + Version: ct.Version(sthInfo.Version), + TreeSize: sthInfo.TreeSize, + Timestamp: sthInfo.Timestamp, + SHA256RootHash: sthInfo.SHA256RootHash, + TreeHeadSignature: sthInfo.TreeHeadSignature, + }, nil +} diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/files.go b/vendor/github.com/google/certificate-transparency-go/x509util/files.go new file mode 100644 index 00000000000..823ac7375a9 --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/x509util/files.go @@ -0,0 +1,116 @@ +// Copyright 2016 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 x509util + +import ( + "encoding/pem" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + + "github.com/google/certificate-transparency-go/x509" +) + +// ReadPossiblePEMFile loads data from a file which may be in DER format +// or may be in PEM format (with the given blockname). +func ReadPossiblePEMFile(filename, blockname string) ([][]byte, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("%s: failed to read data: %v", filename, err) + } + return dePEM(data, blockname), nil +} + +// ReadPossiblePEMURL attempts to determine if the given target is a local file or a +// URL, and return the file contents regardless. It also copes with either PEM or DER +// format data. +func ReadPossiblePEMURL(target, blockname string) ([][]byte, error) { + if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") { + // Assume it's a filename + return ReadPossiblePEMFile(target, blockname) + } + + rsp, err := http.Get(target) + if err != nil { + return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) + } + data, err := io.ReadAll(rsp.Body) + if err != nil { + return nil, fmt.Errorf("failed to io.ReadAll(%q): %v", target, err) + } + return dePEM(data, blockname), nil +} + +func dePEM(data []byte, blockname string) [][]byte { + var results [][]byte + if strings.Contains(string(data), "BEGIN "+blockname) { + rest := data + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type == blockname { + results = append(results, block.Bytes) + } + } + } else { + results = append(results, data) + } + return results +} + +// ReadFileOrURL returns the data from a target which may be either a filename +// or an HTTP(S) URL. +func ReadFileOrURL(target string, client *http.Client) ([]byte, error) { + u, err := url.Parse(target) + if err != nil || (u.Scheme != "http" && u.Scheme != "https") { + return os.ReadFile(target) + } + + rsp, err := client.Get(u.String()) + if err != nil { + return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) + } + return io.ReadAll(rsp.Body) +} + +// GetIssuer attempts to retrieve the issuer for a certificate, by examining +// the cert's Authority Information Access extension (if present) for the +// issuer's URL and retrieving from there. +func GetIssuer(cert *x509.Certificate, client *http.Client) (*x509.Certificate, error) { + if len(cert.IssuingCertificateURL) == 0 { + return nil, nil + } + issuerURL := cert.IssuingCertificateURL[0] + rsp, err := client.Get(issuerURL) + if err != nil || rsp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get issuer from %q: %v", issuerURL, err) + } + defer rsp.Body.Close() + body, err := io.ReadAll(rsp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read issuer from %q: %v", issuerURL, err) + } + issuers, err := x509.ParseCertificates(body) + if err != nil { + return nil, fmt.Errorf("failed to parse issuer cert: %v", err) + } + return issuers[0], nil +} diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go b/vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go new file mode 100644 index 00000000000..ccda196e2ea --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go @@ -0,0 +1,26 @@ +// 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 x509util + +import "github.com/google/certificate-transparency-go/x509" + +// Fuzz is a go-fuzz (https://github.com/dvyukov/go-fuzz) entrypoint +// for fuzzing the parsing of X509 certificates. +func Fuzz(data []byte) int { + if _, err := x509.ParseCertificate(data); err == nil { + return 1 + } + return 0 +} diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go b/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go new file mode 100644 index 00000000000..e419659fa9a --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go @@ -0,0 +1,120 @@ +// Copyright 2016 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 x509util + +import ( + "crypto/sha256" + "encoding/pem" + "errors" + "fmt" + "os" + + "github.com/google/certificate-transparency-go/x509" + "k8s.io/klog/v2" +) + +// String for certificate blocks in BEGIN / END PEM headers +const pemCertificateBlockType string = "CERTIFICATE" + +// PEMCertPool is a wrapper / extension to x509.CertPool. It allows us to access the +// raw certs, which we need to serve get-roots request and has stricter handling on loading +// certs into the pool. CertPool ignores errors if at least one cert loads correctly but +// PEMCertPool requires all certs to load. +type PEMCertPool struct { + // maps from sha-256 to certificate, used for dup detection + fingerprintToCertMap map[[sha256.Size]byte]x509.Certificate + rawCerts []*x509.Certificate + certPool *x509.CertPool +} + +// NewPEMCertPool creates a new, empty, instance of PEMCertPool. +func NewPEMCertPool() *PEMCertPool { + return &PEMCertPool{fingerprintToCertMap: make(map[[sha256.Size]byte]x509.Certificate), certPool: x509.NewCertPool()} +} + +// AddCert adds a certificate to a pool. Uses fingerprint to weed out duplicates. +// cert must not be nil. +func (p *PEMCertPool) AddCert(cert *x509.Certificate) { + fingerprint := sha256.Sum256(cert.Raw) + _, ok := p.fingerprintToCertMap[fingerprint] + + if !ok { + p.fingerprintToCertMap[fingerprint] = *cert + p.certPool.AddCert(cert) + p.rawCerts = append(p.rawCerts, cert) + } +} + +// Included indicates whether the given cert is included in the pool. +func (p *PEMCertPool) Included(cert *x509.Certificate) bool { + fingerprint := sha256.Sum256(cert.Raw) + _, ok := p.fingerprintToCertMap[fingerprint] + return ok +} + +// AppendCertsFromPEM adds certs to the pool from a byte slice assumed to contain PEM encoded data. +// Skips over non certificate blocks in the data. Returns true if all certificates in the +// data were parsed and added to the pool successfully and at least one certificate was found. +func (p *PEMCertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) { + for len(pemCerts) > 0 { + var block *pem.Block + block, pemCerts = pem.Decode(pemCerts) + if block == nil { + break + } + if block.Type != pemCertificateBlockType || len(block.Headers) != 0 { + continue + } + + cert, err := x509.ParseCertificate(block.Bytes) + if x509.IsFatal(err) { + klog.Warningf("error parsing PEM certificate: %v", err) + return false + } + + p.AddCert(cert) + ok = true + } + + return +} + +// AppendCertsFromPEMFile adds certs from a file that contains concatenated PEM data. +func (p *PEMCertPool) AppendCertsFromPEMFile(pemFile string) error { + pemData, err := os.ReadFile(pemFile) + if err != nil { + return fmt.Errorf("failed to load PEM certs file: %v", err) + } + + if !p.AppendCertsFromPEM(pemData) { + return errors.New("failed to parse PEM certs file") + } + return nil +} + +// Subjects returns a list of the DER-encoded subjects of all of the certificates in the pool. +func (p *PEMCertPool) Subjects() (res [][]byte) { + return p.certPool.Subjects() +} + +// CertPool returns the underlying CertPool. +func (p *PEMCertPool) CertPool() *x509.CertPool { + return p.certPool +} + +// RawCertificates returns a list of the raw bytes of certificates that are in this pool +func (p *PEMCertPool) RawCertificates() []*x509.Certificate { + return p.rawCerts +} diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/revoked.go b/vendor/github.com/google/certificate-transparency-go/x509util/revoked.go new file mode 100644 index 00000000000..e0927292630 --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/x509util/revoked.go @@ -0,0 +1,169 @@ +// 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 x509util + +import ( + "bytes" + "encoding/hex" + "fmt" + "strconv" + + "github.com/google/certificate-transparency-go/x509" + "github.com/google/certificate-transparency-go/x509/pkix" +) + +// RevocationReasonToString generates a string describing a revocation reason code. +func RevocationReasonToString(reason x509.RevocationReasonCode) string { + switch reason { + case x509.Unspecified: + return "Unspecified" + case x509.KeyCompromise: + return "Key Compromise" + case x509.CACompromise: + return "CA Compromise" + case x509.AffiliationChanged: + return "Affiliation Changed" + case x509.Superseded: + return "Superseded" + case x509.CessationOfOperation: + return "Cessation Of Operation" + case x509.CertificateHold: + return "Certificate Hold" + case x509.RemoveFromCRL: + return "Remove From CRL" + case x509.PrivilegeWithdrawn: + return "Privilege Withdrawn" + case x509.AACompromise: + return "AA Compromise" + default: + return strconv.Itoa(int(reason)) + } +} + +// CRLToString generates a string describing the given certificate revocation list. +// The output roughly resembles that from openssl crl -text. +func CRLToString(crl *x509.CertificateList) string { + var result bytes.Buffer + var showCritical = func(critical bool) { + if critical { + result.WriteString(" critical") + } + result.WriteString("\n") + } + result.WriteString("Certificate Revocation List (CRL):\n") + result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", crl.TBSCertList.Version+1, crl.TBSCertList.Version)) + result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.TBSCertList.Signature))) + var issuer pkix.Name + issuer.FillFromRDNSequence(&crl.TBSCertList.Issuer) + result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(issuer))) + result.WriteString(fmt.Sprintf(" Last Update: %v\n", crl.TBSCertList.ThisUpdate)) + result.WriteString(fmt.Sprintf(" Next Update: %v\n", crl.TBSCertList.NextUpdate)) + + if len(crl.TBSCertList.Extensions) > 0 { + result.WriteString(" CRL extensions:\n") + } + + count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 Authority Key Identifier:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(crl.TBSCertList.AuthorityKeyID))) + } + count, critical = OIDInExtensions(x509.OIDExtensionIssuerAltName, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 Issuer Alt Name:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuerAltNames))) + } + count, critical = OIDInExtensions(x509.OIDExtensionCRLNumber, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 CRLNumber:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.CRLNumber)) + } + count, critical = OIDInExtensions(x509.OIDExtensionDeltaCRLIndicator, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 Delta CRL Indicator:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.BaseCRLNumber)) + } + count, critical = OIDInExtensions(x509.OIDExtensionIssuingDistributionPoint, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 Issuing Distribution Point:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuingDPFullNames))) + } + count, critical = OIDInExtensions(x509.OIDExtensionFreshestCRL, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" X509v3 Freshest CRL:") + showCritical(critical) + result.WriteString(" Full Name:\n") + var buf bytes.Buffer + for _, pt := range crl.TBSCertList.FreshestCRLDistributionPoint { + commaAppend(&buf, "URI:"+pt) + } + result.WriteString(fmt.Sprintf(" %v\n", buf.String())) + } + count, critical = OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, crl.TBSCertList.Extensions) + if count > 0 { + result.WriteString(" Authority Information Access:") + showCritical(critical) + var issuerBuf bytes.Buffer + for _, issuer := range crl.TBSCertList.IssuingCertificateURL { + commaAppend(&issuerBuf, "URI:"+issuer) + } + if issuerBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String())) + } + var ocspBuf bytes.Buffer + for _, ocsp := range crl.TBSCertList.OCSPServer { + commaAppend(&ocspBuf, "URI:"+ocsp) + } + if ocspBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String())) + } + // TODO(drysdale): Display other GeneralName types + } + + result.WriteString("\n") + result.WriteString("Revoked Certificates:\n") + for _, c := range crl.TBSCertList.RevokedCertificates { + result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", c.SerialNumber.Text(10), c.SerialNumber.Text(16))) + result.WriteString(fmt.Sprintf(" Revocation Date : %v\n", c.RevocationTime)) + count, critical = OIDInExtensions(x509.OIDExtensionCRLReasons, c.Extensions) + if count > 0 { + result.WriteString(" X509v3 CRL Reason Code:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %s\n", RevocationReasonToString(c.RevocationReason))) + } + count, critical = OIDInExtensions(x509.OIDExtensionInvalidityDate, c.Extensions) + if count > 0 { + result.WriteString(" Invalidity Date:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %s\n", c.InvalidityDate)) + } + count, critical = OIDInExtensions(x509.OIDExtensionCertificateIssuer, c.Extensions) + if count > 0 { + result.WriteString(" Issuer:") + showCritical(critical) + result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&c.Issuer))) + } + } + result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.SignatureAlgorithm))) + appendHexData(&result, crl.SignatureValue.Bytes, 18, " ") + result.WriteString("\n") + + return result.String() +} diff --git a/vendor/github.com/google/certificate-transparency-go/x509util/x509util.go b/vendor/github.com/google/certificate-transparency-go/x509util/x509util.go new file mode 100644 index 00000000000..d3c20e1aa9e --- /dev/null +++ b/vendor/github.com/google/certificate-transparency-go/x509util/x509util.go @@ -0,0 +1,900 @@ +// Copyright 2016 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 x509util includes utility code for working with X.509 +// certificates from the x509 package. +package x509util + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "net" + "strconv" + + ct "github.com/google/certificate-transparency-go" + "github.com/google/certificate-transparency-go/asn1" + "github.com/google/certificate-transparency-go/gossip/minimal/x509ext" + "github.com/google/certificate-transparency-go/tls" + "github.com/google/certificate-transparency-go/x509" + "github.com/google/certificate-transparency-go/x509/pkix" +) + +// OIDForStandardExtension indicates whether oid identifies a standard extension. +// Standard extensions are listed in RFC 5280 (and other RFCs). +func OIDForStandardExtension(oid asn1.ObjectIdentifier) bool { + if oid.Equal(x509.OIDExtensionSubjectKeyId) || + oid.Equal(x509.OIDExtensionKeyUsage) || + oid.Equal(x509.OIDExtensionExtendedKeyUsage) || + oid.Equal(x509.OIDExtensionAuthorityKeyId) || + oid.Equal(x509.OIDExtensionBasicConstraints) || + oid.Equal(x509.OIDExtensionSubjectAltName) || + oid.Equal(x509.OIDExtensionCertificatePolicies) || + oid.Equal(x509.OIDExtensionNameConstraints) || + oid.Equal(x509.OIDExtensionCRLDistributionPoints) || + oid.Equal(x509.OIDExtensionIssuerAltName) || + oid.Equal(x509.OIDExtensionSubjectDirectoryAttributes) || + oid.Equal(x509.OIDExtensionInhibitAnyPolicy) || + oid.Equal(x509.OIDExtensionPolicyConstraints) || + oid.Equal(x509.OIDExtensionPolicyMappings) || + oid.Equal(x509.OIDExtensionFreshestCRL) || + oid.Equal(x509.OIDExtensionSubjectInfoAccess) || + oid.Equal(x509.OIDExtensionAuthorityInfoAccess) || + oid.Equal(x509.OIDExtensionIPPrefixList) || + oid.Equal(x509.OIDExtensionASList) || + oid.Equal(x509.OIDExtensionCTPoison) || + oid.Equal(x509.OIDExtensionCTSCT) { + return true + } + return false +} + +// OIDInExtensions checks whether the extension identified by oid is present in extensions +// and returns how many times it occurs together with an indication of whether any of them +// are marked critical. +func OIDInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) (int, bool) { + count := 0 + critical := false + for _, ext := range extensions { + if ext.Id.Equal(oid) { + count++ + if ext.Critical { + critical = true + } + } + } + return count, critical +} + +// String formatting for various X.509/ASN.1 types +func bitStringToString(b asn1.BitString) string { // nolint:deadcode,unused + result := hex.EncodeToString(b.Bytes) + bitsLeft := b.BitLength % 8 + if bitsLeft != 0 { + result += " (" + strconv.Itoa(8-bitsLeft) + " unused bits)" + } + return result +} + +func publicKeyAlgorithmToString(algo x509.PublicKeyAlgorithm) string { + // Use OpenSSL-compatible strings for the algorithms. + switch algo { + case x509.RSA: + return "rsaEncryption" + case x509.DSA: + return "dsaEncryption" + case x509.ECDSA: + return "id-ecPublicKey" + default: + return strconv.Itoa(int(algo)) + } +} + +// appendHexData adds a hex dump of binary data to buf, with line breaks +// after each set of count bytes, and with each new line prefixed with the +// given prefix. +func appendHexData(buf *bytes.Buffer, data []byte, count int, prefix string) { + for ii, b := range data { + if ii%count == 0 { + if ii > 0 { + buf.WriteString("\n") + } + buf.WriteString(prefix) + } + buf.WriteString(fmt.Sprintf("%02x:", b)) + } +} + +func curveOIDToString(oid asn1.ObjectIdentifier) (t string, bitlen int) { + switch { + case oid.Equal(x509.OIDNamedCurveP224): + return "secp224r1", 224 + case oid.Equal(x509.OIDNamedCurveP256): + return "prime256v1", 256 + case oid.Equal(x509.OIDNamedCurveP384): + return "secp384r1", 384 + case oid.Equal(x509.OIDNamedCurveP521): + return "secp521r1", 521 + case oid.Equal(x509.OIDNamedCurveP192): + return "secp192r1", 192 + } + return fmt.Sprintf("%v", oid), -1 +} + +func publicKeyToString(_ x509.PublicKeyAlgorithm, pub interface{}) string { + var buf bytes.Buffer + switch pub := pub.(type) { + case *rsa.PublicKey: + bitlen := pub.N.BitLen() + buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen)) + buf.WriteString(" Modulus:\n") + data := pub.N.Bytes() + appendHexData(&buf, data, 15, " ") + buf.WriteString("\n") + buf.WriteString(fmt.Sprintf(" Exponent: %d (0x%x)", pub.E, pub.E)) + case *dsa.PublicKey: + buf.WriteString(" pub:\n") + appendHexData(&buf, pub.Y.Bytes(), 15, " ") + buf.WriteString("\n") + buf.WriteString(" P:\n") + appendHexData(&buf, pub.P.Bytes(), 15, " ") + buf.WriteString("\n") + buf.WriteString(" Q:\n") + appendHexData(&buf, pub.Q.Bytes(), 15, " ") + buf.WriteString("\n") + buf.WriteString(" G:\n") + appendHexData(&buf, pub.G.Bytes(), 15, " ") + case *ecdsa.PublicKey: + data := elliptic.Marshal(pub.Curve, pub.X, pub.Y) + oid, ok := x509.OIDFromNamedCurve(pub.Curve) + if !ok { + return " " + } + oidname, bitlen := curveOIDToString(oid) + buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen)) + buf.WriteString(" pub:\n") + appendHexData(&buf, data, 15, " ") + buf.WriteString("\n") + buf.WriteString(fmt.Sprintf(" ASN1 OID: %s", oidname)) + default: + buf.WriteString(fmt.Sprintf("%v", pub)) + } + return buf.String() +} + +func commaAppend(buf *bytes.Buffer, s string) { + if buf.Len() > 0 { + buf.WriteString(", ") + } + buf.WriteString(s) +} + +func keyUsageToString(k x509.KeyUsage) string { + var buf bytes.Buffer + if k&x509.KeyUsageDigitalSignature != 0 { + commaAppend(&buf, "Digital Signature") + } + if k&x509.KeyUsageContentCommitment != 0 { + commaAppend(&buf, "Content Commitment") + } + if k&x509.KeyUsageKeyEncipherment != 0 { + commaAppend(&buf, "Key Encipherment") + } + if k&x509.KeyUsageDataEncipherment != 0 { + commaAppend(&buf, "Data Encipherment") + } + if k&x509.KeyUsageKeyAgreement != 0 { + commaAppend(&buf, "Key Agreement") + } + if k&x509.KeyUsageCertSign != 0 { + commaAppend(&buf, "Certificate Signing") + } + if k&x509.KeyUsageCRLSign != 0 { + commaAppend(&buf, "CRL Signing") + } + if k&x509.KeyUsageEncipherOnly != 0 { + commaAppend(&buf, "Encipher Only") + } + if k&x509.KeyUsageDecipherOnly != 0 { + commaAppend(&buf, "Decipher Only") + } + return buf.String() +} + +func extKeyUsageToString(u x509.ExtKeyUsage) string { + switch u { + case x509.ExtKeyUsageAny: + return "Any" + case x509.ExtKeyUsageServerAuth: + return "TLS Web server authentication" + case x509.ExtKeyUsageClientAuth: + return "TLS Web client authentication" + case x509.ExtKeyUsageCodeSigning: + return "Signing of executable code" + case x509.ExtKeyUsageEmailProtection: + return "Email protection" + case x509.ExtKeyUsageIPSECEndSystem: + return "IPSEC end system" + case x509.ExtKeyUsageIPSECTunnel: + return "IPSEC tunnel" + case x509.ExtKeyUsageIPSECUser: + return "IPSEC user" + case x509.ExtKeyUsageTimeStamping: + return "Time stamping" + case x509.ExtKeyUsageOCSPSigning: + return "OCSP signing" + case x509.ExtKeyUsageMicrosoftServerGatedCrypto: + return "Microsoft server gated cryptography" + case x509.ExtKeyUsageNetscapeServerGatedCrypto: + return "Netscape server gated cryptography" + case x509.ExtKeyUsageCertificateTransparency: + return "Certificate transparency" + default: + return "Unknown" + } +} + +func attributeOIDToString(oid asn1.ObjectIdentifier) string { // nolint:deadcode,unused + switch { + case oid.Equal(pkix.OIDCountry): + return "Country" + case oid.Equal(pkix.OIDOrganization): + return "Organization" + case oid.Equal(pkix.OIDOrganizationalUnit): + return "OrganizationalUnit" + case oid.Equal(pkix.OIDCommonName): + return "CommonName" + case oid.Equal(pkix.OIDSerialNumber): + return "SerialNumber" + case oid.Equal(pkix.OIDLocality): + return "Locality" + case oid.Equal(pkix.OIDProvince): + return "Province" + case oid.Equal(pkix.OIDStreetAddress): + return "StreetAddress" + case oid.Equal(pkix.OIDPostalCode): + return "PostalCode" + case oid.Equal(pkix.OIDPseudonym): + return "Pseudonym" + case oid.Equal(pkix.OIDTitle): + return "Title" + case oid.Equal(pkix.OIDDnQualifier): + return "DnQualifier" + case oid.Equal(pkix.OIDName): + return "Name" + case oid.Equal(pkix.OIDSurname): + return "Surname" + case oid.Equal(pkix.OIDGivenName): + return "GivenName" + case oid.Equal(pkix.OIDInitials): + return "Initials" + case oid.Equal(pkix.OIDGenerationQualifier): + return "GenerationQualifier" + default: + return oid.String() + } +} + +// NameToString creates a string description of a pkix.Name object. +func NameToString(name pkix.Name) string { + var result bytes.Buffer + addSingle := func(prefix, item string) { + if len(item) == 0 { + return + } + commaAppend(&result, prefix) + result.WriteString(item) + } + addList := func(prefix string, items []string) { + for _, item := range items { + addSingle(prefix, item) + } + } + addList("C=", name.Country) + addList("O=", name.Organization) + addList("OU=", name.OrganizationalUnit) + addList("L=", name.Locality) + addList("ST=", name.Province) + addList("streetAddress=", name.StreetAddress) + addList("postalCode=", name.PostalCode) + addSingle("serialNumber=", name.SerialNumber) + addSingle("CN=", name.CommonName) + for _, atv := range name.Names { + value, ok := atv.Value.(string) + if !ok { + continue + } + t := atv.Type + // All of the defined attribute OIDs are of the form 2.5.4.N, and OIDAttribute is + // the 2.5.4 prefix ('id-at' in RFC 5280). + if len(t) == 4 && t[0] == pkix.OIDAttribute[0] && t[1] == pkix.OIDAttribute[1] && t[2] == pkix.OIDAttribute[2] { + // OID is 'id-at N', so check the final value to figure out which attribute. + switch t[3] { + case pkix.OIDCommonName[3], pkix.OIDSerialNumber[3], pkix.OIDCountry[3], pkix.OIDLocality[3], pkix.OIDProvince[3], + pkix.OIDStreetAddress[3], pkix.OIDOrganization[3], pkix.OIDOrganizationalUnit[3], pkix.OIDPostalCode[3]: + continue // covered by explicit fields + case pkix.OIDPseudonym[3]: + addSingle("pseudonym=", value) + continue + case pkix.OIDTitle[3]: + addSingle("title=", value) + continue + case pkix.OIDDnQualifier[3]: + addSingle("dnQualifier=", value) + continue + case pkix.OIDName[3]: + addSingle("name=", value) + continue + case pkix.OIDSurname[3]: + addSingle("surname=", value) + continue + case pkix.OIDGivenName[3]: + addSingle("givenName=", value) + continue + case pkix.OIDInitials[3]: + addSingle("initials=", value) + continue + case pkix.OIDGenerationQualifier[3]: + addSingle("generationQualifier=", value) + continue + } + } + addSingle(t.String()+"=", value) + } + return result.String() +} + +// OtherNameToString creates a string description of an x509.OtherName object. +func OtherNameToString(other x509.OtherName) string { + return fmt.Sprintf("%v=%v", other.TypeID, hex.EncodeToString(other.Value.Bytes)) +} + +// GeneralNamesToString creates a string description of an x509.GeneralNames object. +func GeneralNamesToString(gname *x509.GeneralNames) string { + var buf bytes.Buffer + for _, name := range gname.DNSNames { + commaAppend(&buf, "DNS:"+name) + } + for _, email := range gname.EmailAddresses { + commaAppend(&buf, "email:"+email) + } + for _, name := range gname.DirectoryNames { + commaAppend(&buf, "DirName:"+NameToString(name)) + } + for _, uri := range gname.URIs { + commaAppend(&buf, "URI:"+uri) + } + for _, ip := range gname.IPNets { + if ip.Mask == nil { + commaAppend(&buf, "IP Address:"+ip.IP.String()) + } else { + commaAppend(&buf, "IP Address:"+ip.IP.String()+"/"+ip.Mask.String()) + } + } + for _, id := range gname.RegisteredIDs { + commaAppend(&buf, "Registered ID:"+id.String()) + } + for _, other := range gname.OtherNames { + commaAppend(&buf, "othername:"+OtherNameToString(other)) + } + return buf.String() +} + +// CertificateToString generates a string describing the given certificate. +// The output roughly resembles that from openssl x509 -text. +func CertificateToString(cert *x509.Certificate) string { + var result bytes.Buffer + result.WriteString("Certificate:\n") + result.WriteString(" Data:\n") + result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", cert.Version, cert.Version-1)) + result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", cert.SerialNumber.Text(10), cert.SerialNumber.Text(16))) + result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm)) + result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(cert.Issuer))) + result.WriteString(" Validity:\n") + result.WriteString(fmt.Sprintf(" Not Before: %v\n", cert.NotBefore)) + result.WriteString(fmt.Sprintf(" Not After : %v\n", cert.NotAfter)) + result.WriteString(fmt.Sprintf(" Subject: %v\n", NameToString(cert.Subject))) + result.WriteString(" Subject Public Key Info:\n") + result.WriteString(fmt.Sprintf(" Public Key Algorithm: %v\n", publicKeyAlgorithmToString(cert.PublicKeyAlgorithm))) + result.WriteString(fmt.Sprintf("%v\n", publicKeyToString(cert.PublicKeyAlgorithm, cert.PublicKey))) + + if len(cert.Extensions) > 0 { + result.WriteString(" X509v3 extensions:\n") + } + // First display the extensions that are already cracked out + showAuthKeyID(&result, cert) + showSubjectKeyID(&result, cert) + showKeyUsage(&result, cert) + showExtendedKeyUsage(&result, cert) + showBasicConstraints(&result, cert) + showSubjectAltName(&result, cert) + showNameConstraints(&result, cert) + showCertPolicies(&result, cert) + showCRLDPs(&result, cert) + showAuthInfoAccess(&result, cert) + showSubjectInfoAccess(&result, cert) + showRPKIAddressRanges(&result, cert) + showRPKIASIdentifiers(&result, cert) + showCTPoison(&result, cert) + showCTSCT(&result, cert) + showCTLogSTHInfo(&result, cert) + + showUnhandledExtensions(&result, cert) + showSignature(&result, cert) + + return result.String() +} + +func showCritical(result *bytes.Buffer, critical bool) { + if critical { + result.WriteString(" critical") + } + result.WriteString("\n") +} + +func showAuthKeyID(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Authority Key Identifier:") + showCritical(result, critical) + result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.AuthorityKeyId))) + } +} + +func showSubjectKeyID(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionSubjectKeyId, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Subject Key Identifier:") + showCritical(result, critical) + result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.SubjectKeyId))) + } +} + +func showKeyUsage(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionKeyUsage, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Key Usage:") + showCritical(result, critical) + result.WriteString(fmt.Sprintf(" %v\n", keyUsageToString(cert.KeyUsage))) + } +} + +func showExtendedKeyUsage(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionExtendedKeyUsage, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Extended Key Usage:") + showCritical(result, critical) + var usages bytes.Buffer + for _, usage := range cert.ExtKeyUsage { + commaAppend(&usages, extKeyUsageToString(usage)) + } + for _, oid := range cert.UnknownExtKeyUsage { + commaAppend(&usages, oid.String()) + } + result.WriteString(fmt.Sprintf(" %v\n", usages.String())) + } +} + +func showBasicConstraints(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionBasicConstraints, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Basic Constraints:") + showCritical(result, critical) + result.WriteString(fmt.Sprintf(" CA:%t", cert.IsCA)) + if cert.MaxPathLen > 0 || cert.MaxPathLenZero { + result.WriteString(fmt.Sprintf(", pathlen:%d", cert.MaxPathLen)) + } + result.WriteString("\n") + } +} + +func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Subject Alternative Name:") + showCritical(result, critical) + var buf bytes.Buffer + for _, name := range cert.DNSNames { + commaAppend(&buf, "DNS:"+name) + } + for _, email := range cert.EmailAddresses { + commaAppend(&buf, "email:"+email) + } + for _, ip := range cert.IPAddresses { + commaAppend(&buf, "IP Address:"+ip.String()) + } + + result.WriteString(fmt.Sprintf(" %v\n", buf.String())) + // TODO(drysdale): include other name forms + } +} + +func showNameConstraints(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionNameConstraints, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Name Constraints:") + showCritical(result, critical) + if len(cert.PermittedDNSDomains) > 0 { + result.WriteString(" Permitted:\n") + var buf bytes.Buffer + for _, name := range cert.PermittedDNSDomains { + commaAppend(&buf, "DNS:"+name) + } + result.WriteString(fmt.Sprintf(" %v\n", buf.String())) + } + // TODO(drysdale): include other name forms + } + +} + +func showCertPolicies(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionCertificatePolicies, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 Certificate Policies:") + showCritical(result, critical) + for _, oid := range cert.PolicyIdentifiers { + result.WriteString(fmt.Sprintf(" Policy: %v\n", oid.String())) + // TODO(drysdale): Display any qualifiers associated with the policy + } + } + +} + +func showCRLDPs(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionCRLDistributionPoints, cert.Extensions) + if count > 0 { + result.WriteString(" X509v3 CRL Distribution Points:") + showCritical(result, critical) + result.WriteString(" Full Name:\n") + var buf bytes.Buffer + for _, pt := range cert.CRLDistributionPoints { + commaAppend(&buf, "URI:"+pt) + } + result.WriteString(fmt.Sprintf(" %v\n", buf.String())) + // TODO(drysdale): Display other GeneralNames types, plus issuer/reasons/relative-name + } + +} + +func showAuthInfoAccess(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, cert.Extensions) + if count > 0 { + result.WriteString(" Authority Information Access:") + showCritical(result, critical) + var issuerBuf bytes.Buffer + for _, issuer := range cert.IssuingCertificateURL { + commaAppend(&issuerBuf, "URI:"+issuer) + } + if issuerBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String())) + } + var ocspBuf bytes.Buffer + for _, ocsp := range cert.OCSPServer { + commaAppend(&ocspBuf, "URI:"+ocsp) + } + if ocspBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String())) + } + // TODO(drysdale): Display other GeneralNames types + } +} + +func showSubjectInfoAccess(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionSubjectInfoAccess, cert.Extensions) + if count > 0 { + result.WriteString(" Subject Information Access:") + showCritical(result, critical) + var tsBuf bytes.Buffer + for _, ts := range cert.SubjectTimestamps { + commaAppend(&tsBuf, "URI:"+ts) + } + if tsBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" AD Time Stamping - %v\n", tsBuf.String())) + } + var repoBuf bytes.Buffer + for _, repo := range cert.SubjectCARepositories { + commaAppend(&repoBuf, "URI:"+repo) + } + if repoBuf.Len() > 0 { + result.WriteString(fmt.Sprintf(" CA repository - %v\n", repoBuf.String())) + } + } +} + +func showAddressRange(prefix x509.IPAddressPrefix, afi uint16) string { + switch afi { + case x509.IPv4AddressFamilyIndicator, x509.IPv6AddressFamilyIndicator: + size := 4 + if afi == x509.IPv6AddressFamilyIndicator { + size = 16 + } + ip := make([]byte, size) + copy(ip, prefix.Bytes) + addr := net.IPNet{IP: ip, Mask: net.CIDRMask(prefix.BitLength, 8*size)} + return addr.String() + default: + return fmt.Sprintf("%x/%d", prefix.Bytes, prefix.BitLength) + } + +} + +func showRPKIAddressRanges(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionIPPrefixList, cert.Extensions) + if count > 0 { + result.WriteString(" sbgp-ipAddrBlock:") + showCritical(result, critical) + for _, blocks := range cert.RPKIAddressRanges { + afi := blocks.AFI + switch afi { + case x509.IPv4AddressFamilyIndicator: + result.WriteString(" IPv4") + case x509.IPv6AddressFamilyIndicator: + result.WriteString(" IPv6") + default: + result.WriteString(fmt.Sprintf(" %d", afi)) + } + if blocks.SAFI != 0 { + result.WriteString(fmt.Sprintf(" SAFI=%d", blocks.SAFI)) + } + result.WriteString(":") + if blocks.InheritFromIssuer { + result.WriteString(" inherit\n") + continue + } + result.WriteString("\n") + for _, prefix := range blocks.AddressPrefixes { + result.WriteString(fmt.Sprintf(" %s\n", showAddressRange(prefix, afi))) + } + for _, ipRange := range blocks.AddressRanges { + result.WriteString(fmt.Sprintf(" [%s, %s]\n", showAddressRange(ipRange.Min, afi), showAddressRange(ipRange.Max, afi))) + } + } + } +} + +func showASIDs(result *bytes.Buffer, asids *x509.ASIdentifiers, label string) { + if asids == nil { + return + } + result.WriteString(fmt.Sprintf(" %s:\n", label)) + if asids.InheritFromIssuer { + result.WriteString(" inherit\n") + return + } + for _, id := range asids.ASIDs { + result.WriteString(fmt.Sprintf(" %d\n", id)) + } + for _, idRange := range asids.ASIDRanges { + result.WriteString(fmt.Sprintf(" %d-%d\n", idRange.Min, idRange.Max)) + } +} + +func showRPKIASIdentifiers(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionASList, cert.Extensions) + if count > 0 { + result.WriteString(" sbgp-autonomousSysNum:") + showCritical(result, critical) + showASIDs(result, cert.RPKIASNumbers, "Autonomous System Numbers") + showASIDs(result, cert.RPKIRoutingDomainIDs, "Routing Domain Identifiers") + } +} +func showCTPoison(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions) + if count > 0 { + result.WriteString(" RFC6962 Pre-Certificate Poison:") + showCritical(result, critical) + result.WriteString(" .....\n") + } +} + +func showCTSCT(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509.OIDExtensionCTSCT, cert.Extensions) + if count > 0 { + result.WriteString(" RFC6962 Certificate Transparency SCT:") + showCritical(result, critical) + for i, sctData := range cert.SCTList.SCTList { + result.WriteString(fmt.Sprintf(" SCT [%d]:\n", i)) + var sct ct.SignedCertificateTimestamp + _, err := tls.Unmarshal(sctData.Val, &sct) + if err != nil { + appendHexData(result, sctData.Val, 16, " ") + result.WriteString("\n") + continue + } + result.WriteString(fmt.Sprintf(" Version: %d\n", sct.SCTVersion)) + result.WriteString(fmt.Sprintf(" LogID: %s\n", base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:]))) + result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sct.Timestamp)) + result.WriteString(fmt.Sprintf(" Signature: %s\n", sct.Signature.Algorithm)) + result.WriteString(" Signature:\n") + appendHexData(result, sct.Signature.Signature, 16, " ") + result.WriteString("\n") + } + } +} + +func showCTLogSTHInfo(result *bytes.Buffer, cert *x509.Certificate) { + count, critical := OIDInExtensions(x509ext.OIDExtensionCTSTH, cert.Extensions) + if count > 0 { + result.WriteString(" Certificate Transparency STH:") + showCritical(result, critical) + sthInfo, err := x509ext.LogSTHInfoFromCert(cert) + if err != nil { + result.WriteString(" Failed to decode STH:\n") + return + } + result.WriteString(fmt.Sprintf(" LogURL: %s\n", string(sthInfo.LogURL))) + result.WriteString(fmt.Sprintf(" Version: %d\n", sthInfo.Version)) + result.WriteString(fmt.Sprintf(" TreeSize: %d\n", sthInfo.TreeSize)) + result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sthInfo.Timestamp)) + result.WriteString(" RootHash:\n") + appendHexData(result, sthInfo.SHA256RootHash[:], 16, " ") + result.WriteString("\n") + result.WriteString(fmt.Sprintf(" TreeHeadSignature: %s\n", sthInfo.TreeHeadSignature.Algorithm)) + appendHexData(result, sthInfo.TreeHeadSignature.Signature, 16, " ") + result.WriteString("\n") + } +} + +func showUnhandledExtensions(result *bytes.Buffer, cert *x509.Certificate) { + for _, ext := range cert.Extensions { + // Skip extensions that are already cracked out + if oidAlreadyPrinted(ext.Id) { + continue + } + result.WriteString(fmt.Sprintf(" %v:", ext.Id)) + showCritical(result, ext.Critical) + appendHexData(result, ext.Value, 16, " ") + result.WriteString("\n") + } +} + +func showSignature(result *bytes.Buffer, cert *x509.Certificate) { + result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm)) + appendHexData(result, cert.Signature, 18, " ") + result.WriteString("\n") +} + +// TODO(drysdale): remove this once all standard OIDs are parsed and printed. +func oidAlreadyPrinted(oid asn1.ObjectIdentifier) bool { + if oid.Equal(x509.OIDExtensionSubjectKeyId) || + oid.Equal(x509.OIDExtensionKeyUsage) || + oid.Equal(x509.OIDExtensionExtendedKeyUsage) || + oid.Equal(x509.OIDExtensionAuthorityKeyId) || + oid.Equal(x509.OIDExtensionBasicConstraints) || + oid.Equal(x509.OIDExtensionSubjectAltName) || + oid.Equal(x509.OIDExtensionCertificatePolicies) || + oid.Equal(x509.OIDExtensionNameConstraints) || + oid.Equal(x509.OIDExtensionCRLDistributionPoints) || + oid.Equal(x509.OIDExtensionAuthorityInfoAccess) || + oid.Equal(x509.OIDExtensionSubjectInfoAccess) || + oid.Equal(x509.OIDExtensionIPPrefixList) || + oid.Equal(x509.OIDExtensionASList) || + oid.Equal(x509.OIDExtensionCTPoison) || + oid.Equal(x509.OIDExtensionCTSCT) || + oid.Equal(x509ext.OIDExtensionCTSTH) { + return true + } + return false +} + +// CertificateFromPEM takes a certificate in PEM format and returns the +// corresponding x509.Certificate object. +func CertificateFromPEM(pemBytes []byte) (*x509.Certificate, error) { + block, rest := pem.Decode(pemBytes) + if len(rest) != 0 { + return nil, errors.New("trailing data found after PEM block") + } + if block == nil { + return nil, errors.New("PEM block is nil") + } + if block.Type != "CERTIFICATE" { + return nil, errors.New("PEM block is not a CERTIFICATE") + } + return x509.ParseCertificate(block.Bytes) +} + +// CertificatesFromPEM parses one or more certificates from the given PEM data. +// The PEM certificates must be concatenated. This function can be used for +// parsing PEM-formatted certificate chains, but does not verify that the +// resulting chain is a valid certificate chain. +func CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) { + var chain []*x509.Certificate + for { + var block *pem.Block + block, pemBytes = pem.Decode(pemBytes) + if block == nil { + return chain, nil + } + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("PEM block is not a CERTIFICATE") + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.New("failed to parse certificate") + } + chain = append(chain, cert) + } +} + +// ParseSCTsFromSCTList parses each of the SCTs contained within an SCT list. +func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.SignedCertificateTimestamp, error) { + var scts []*ct.SignedCertificateTimestamp + for i, data := range sctList.SCTList { + sct, err := ExtractSCT(&data) + if err != nil { + return nil, fmt.Errorf("error extracting SCT number %d: %s", i, err) + } + scts = append(scts, sct) + } + return scts, nil +} + +// ExtractSCT deserializes an SCT from a TLS-encoded SCT. +func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, error) { + if sctData == nil { + return nil, errors.New("SCT is nil") + } + var sct ct.SignedCertificateTimestamp + if rest, err := tls.Unmarshal(sctData.Val, &sct); err != nil { + return nil, fmt.Errorf("error parsing SCT: %s", err) + } else if len(rest) > 0 { + return nil, fmt.Errorf("extra data (%d bytes) after serialized SCT", len(rest)) + } + return &sct, nil +} + +// MarshalSCTsIntoSCTList serializes SCTs into SCT list. +func MarshalSCTsIntoSCTList(scts []*ct.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) { + var sctList x509.SignedCertificateTimestampList + sctList.SCTList = []x509.SerializedSCT{} + for i, sct := range scts { + if sct == nil { + return nil, fmt.Errorf("SCT number %d is nil", i) + } + encd, err := tls.Marshal(*sct) + if err != nil { + return nil, fmt.Errorf("error serializing SCT number %d: %s", i, err) + } + sctData := x509.SerializedSCT{Val: encd} + sctList.SCTList = append(sctList.SCTList, sctData) + } + return &sctList, nil +} + +var pemCertificatePrefix = []byte("-----BEGIN CERTIFICATE") + +// ParseSCTsFromCertificate parses any SCTs that are embedded in the +// certificate provided. The certificate bytes provided can be either DER or +// PEM, provided the PEM data starts with the PEM block marker (i.e. has no +// leading text). +func ParseSCTsFromCertificate(certBytes []byte) ([]*ct.SignedCertificateTimestamp, error) { + var cert *x509.Certificate + var err error + if bytes.HasPrefix(certBytes, pemCertificatePrefix) { + cert, err = CertificateFromPEM(certBytes) + } else { + cert, err = x509.ParseCertificate(certBytes) + } + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %s", err) + } + return ParseSCTsFromSCTList(&cert.SCTList) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 13ae0e1a7f8..5a2734a4831 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -182,10 +182,12 @@ github.com/google/certificate-transparency-go github.com/google/certificate-transparency-go/asn1 github.com/google/certificate-transparency-go/client github.com/google/certificate-transparency-go/client/configpb +github.com/google/certificate-transparency-go/gossip/minimal/x509ext github.com/google/certificate-transparency-go/jsonclient github.com/google/certificate-transparency-go/tls github.com/google/certificate-transparency-go/x509 github.com/google/certificate-transparency-go/x509/pkix +github.com/google/certificate-transparency-go/x509util # github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 ## explicit github.com/grpc-ecosystem/go-grpc-prometheus