// Copyright 2016 Google Inc. 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" "strconv" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/asn1" "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.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 { 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, byte := range data { if ii%count == 0 { if ii > 0 { buf.WriteString("\n") } buf.WriteString(prefix) } buf.WriteString(fmt.Sprintf("%02x:", byte)) } } 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 } return fmt.Sprintf("%v", oid), -1 } func publicKeyToString(algo 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 { 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(fmt.Sprintf("Certificate:\n")) result.WriteString(fmt.Sprintf(" Data:\n")) result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", cert.Version, cert.Version-1)) result.WriteString(fmt.Sprintf(" Serial Number: %d (%#[1]x)\n", cert.SerialNumber)) result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm)) result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(cert.Issuer))) result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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) showCTPoison(&result, cert) showCTSCT(&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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf("\n")) } } func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions) if count > 0 { result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" X509v3 Name Constraints:")) showCritical(result, critical) if len(cert.PermittedDNSDomains) > 0 { result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" X509v3 CRL Distribution Points:")) showCritical(result, critical) result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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 showCTPoison(result *bytes.Buffer, cert *x509.Certificate) { count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions) if count > 0 { result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" Signature:\n")) appendHexData(result, sct.Signature.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.OIDExtensionCTPoison) || oid.Equal(x509.OIDExtensionCTSCT) { 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 _, err := tls.Unmarshal(sctData.Val, &sct); err != nil { return nil, fmt.Errorf("error parsing SCT: %s", err) } return &sct, 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) }