package certlib // Originally from CFSSL, mostly written by me originally, and licensed under: /* Copyright (c) 2014 CloudFlare Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // I've modified it for use in my own code e.g. by removing the CFSSL errors // and replacing them with sane ones. import ( "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" "os" "strings" "time" "git.wntrmute.dev/kyle/goutils/certlib/certerr" "git.wntrmute.dev/kyle/goutils/certlib/pkcs7" ct "github.com/google/certificate-transparency-go" cttls "github.com/google/certificate-transparency-go/tls" ctx509 "github.com/google/certificate-transparency-go/x509" "golang.org/x/crypto/ocsp" "golang.org/x/crypto/pkcs12" ) // OneYear is a time.Duration representing a year's worth of seconds. const OneYear = 8760 * time.Hour // OneDay is a time.Duration representing a day's worth of seconds. const OneDay = 24 * time.Hour // DelegationUsage is the OID for the DelegationUseage extensions var DelegationUsage = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 44} // DelegationExtension var DelegationExtension = pkix.Extension{ Id: DelegationUsage, Critical: false, Value: []byte{0x05, 0x00}, // ASN.1 NULL } // InclusiveDate returns the time.Time representation of a date - 1 // nanosecond. This allows time.After to be used inclusively. func InclusiveDate(year int, month time.Month, day int) time.Time { return time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(-1 * time.Nanosecond) } // Jul2012 is the July 2012 CAB Forum deadline for when CAs must stop // issuing certificates valid for more than 5 years. var Jul2012 = InclusiveDate(2012, time.July, 01) // Apr2015 is the April 2015 CAB Forum deadline for when CAs must stop // issuing certificates valid for more than 39 months. var Apr2015 = InclusiveDate(2015, time.April, 01) // KeyLength returns the bit size of ECDSA or RSA PublicKey func KeyLength(key interface{}) int { if key == nil { return 0 } if ecdsaKey, ok := key.(*ecdsa.PublicKey); ok { return ecdsaKey.Curve.Params().BitSize } else if rsaKey, ok := key.(*rsa.PublicKey); ok { return rsaKey.N.BitLen() } return 0 } // ExpiryTime returns the time when the certificate chain is expired. func ExpiryTime(chain []*x509.Certificate) (notAfter time.Time) { if len(chain) == 0 { return } notAfter = chain[0].NotAfter for _, cert := range chain { if notAfter.After(cert.NotAfter) { notAfter = cert.NotAfter } } return } // MonthsValid returns the number of months for which a certificate is valid. func MonthsValid(c *x509.Certificate) int { issued := c.NotBefore expiry := c.NotAfter years := (expiry.Year() - issued.Year()) months := years*12 + int(expiry.Month()) - int(issued.Month()) // Round up if valid for less than a full month if expiry.Day() > issued.Day() { months++ } return months } // ValidExpiry determines if a certificate is valid for an acceptable // length of time per the CA/Browser Forum baseline requirements. // See https://cabforum.org/wp-content/uploads/CAB-Forum-BR-1.3.0.pdf func ValidExpiry(c *x509.Certificate) bool { issued := c.NotBefore var maxMonths int switch { case issued.After(Apr2015): maxMonths = 39 case issued.After(Jul2012): maxMonths = 60 case issued.Before(Jul2012): maxMonths = 120 } if MonthsValid(c) > maxMonths { return false } return true } // SignatureString returns the TLS signature string corresponding to // an X509 signature algorithm. func SignatureString(alg x509.SignatureAlgorithm) string { switch alg { case x509.MD2WithRSA: return "MD2WithRSA" case x509.MD5WithRSA: return "MD5WithRSA" case x509.SHA1WithRSA: return "SHA1WithRSA" case x509.SHA256WithRSA: return "SHA256WithRSA" case x509.SHA384WithRSA: return "SHA384WithRSA" case x509.SHA512WithRSA: return "SHA512WithRSA" case x509.DSAWithSHA1: return "DSAWithSHA1" case x509.DSAWithSHA256: return "DSAWithSHA256" case x509.ECDSAWithSHA1: return "ECDSAWithSHA1" case x509.ECDSAWithSHA256: return "ECDSAWithSHA256" case x509.ECDSAWithSHA384: return "ECDSAWithSHA384" case x509.ECDSAWithSHA512: return "ECDSAWithSHA512" default: return "Unknown Signature" } } // HashAlgoString returns the hash algorithm name contains in the signature // method. func HashAlgoString(alg x509.SignatureAlgorithm) string { switch alg { case x509.MD2WithRSA: return "MD2" case x509.MD5WithRSA: return "MD5" case x509.SHA1WithRSA: return "SHA1" case x509.SHA256WithRSA: return "SHA256" case x509.SHA384WithRSA: return "SHA384" case x509.SHA512WithRSA: return "SHA512" case x509.DSAWithSHA1: return "SHA1" case x509.DSAWithSHA256: return "SHA256" case x509.ECDSAWithSHA1: return "SHA1" case x509.ECDSAWithSHA256: return "SHA256" case x509.ECDSAWithSHA384: return "SHA384" case x509.ECDSAWithSHA512: return "SHA512" default: return "Unknown Hash Algorithm" } } // StringTLSVersion returns underlying enum values from human names for TLS // versions, defaults to current golang default of TLS 1.0 func StringTLSVersion(version string) uint16 { switch version { case "1.2": return tls.VersionTLS12 case "1.1": return tls.VersionTLS11 default: return tls.VersionTLS10 } } // EncodeCertificatesPEM encodes a number of x509 certificates to PEM func EncodeCertificatesPEM(certs []*x509.Certificate) []byte { var buffer bytes.Buffer for _, cert := range certs { pem.Encode(&buffer, &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }) } return buffer.Bytes() } // EncodeCertificatePEM encodes a single x509 certificates to PEM func EncodeCertificatePEM(cert *x509.Certificate) []byte { return EncodeCertificatesPEM([]*x509.Certificate{cert}) } // ParseCertificatesPEM parses a sequence of PEM-encoded certificate and returns them, // can handle PEM encoded PKCS #7 structures. func ParseCertificatesPEM(certsPEM []byte) ([]*x509.Certificate, error) { var certs []*x509.Certificate var err error certsPEM = bytes.TrimSpace(certsPEM) for len(certsPEM) > 0 { var cert []*x509.Certificate cert, certsPEM, err = ParseOneCertificateFromPEM(certsPEM) if err != nil { return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err) } else if cert == nil { break } certs = append(certs, cert...) } if len(certsPEM) > 0 { return nil, certerr.DecodeError(certerr.ErrorSourceCertificate, errors.New("trailing data at end of certificate")) } return certs, nil } // ParseCertificatesDER parses a DER encoding of a certificate object and possibly private key, // either PKCS #7, PKCS #12, or raw x509. func ParseCertificatesDER(certsDER []byte, password string) (certs []*x509.Certificate, key crypto.Signer, err error) { certsDER = bytes.TrimSpace(certsDER) pkcs7data, err := pkcs7.ParsePKCS7(certsDER) if err != nil { var pkcs12data interface{} certs = make([]*x509.Certificate, 1) pkcs12data, certs[0], err = pkcs12.Decode(certsDER, password) if err != nil { certs, err = x509.ParseCertificates(certsDER) if err != nil { return nil, nil, certerr.DecodeError(certerr.ErrorSourceCertificate, err) } } else { key = pkcs12data.(crypto.Signer) } } else { if pkcs7data.ContentInfo != "SignedData" { return nil, nil, certerr.DecodeError(certerr.ErrorSourceCertificate, errors.New("can only extract certificates from signed data content info")) } certs = pkcs7data.Content.SignedData.Certificates } if certs == nil { return nil, key, certerr.DecodeError(certerr.ErrorSourceCertificate, errors.New("no certificates decoded")) } return certs, key, nil } // ParseSelfSignedCertificatePEM parses a PEM-encoded certificate and check if it is self-signed. func ParseSelfSignedCertificatePEM(certPEM []byte) (*x509.Certificate, error) { cert, err := ParseCertificatePEM(certPEM) if err != nil { return nil, err } if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { return nil, certerr.VerifyError(certerr.ErrorSourceCertificate, err) } return cert, nil } // ParseCertificatePEM parses and returns a PEM-encoded certificate, // can handle PEM encoded PKCS #7 structures. func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) { certPEM = bytes.TrimSpace(certPEM) cert, rest, err := ParseOneCertificateFromPEM(certPEM) if err != nil { return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err) } else if cert == nil { return nil, certerr.DecodeError(certerr.ErrorSourceCertificate, errors.New("no certificate decoded")) } else if len(rest) > 0 { return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("the PEM file should contain only one object")) } else if len(cert) > 1 { return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("the PKCS7 object in the PEM file should contain only one certificate")) } return cert[0], nil } // ParseOneCertificateFromPEM attempts to parse one PEM encoded certificate object, // either a raw x509 certificate or a PKCS #7 structure possibly containing // multiple certificates, from the top of certsPEM, which itself may // contain multiple PEM encoded certificate objects. func ParseOneCertificateFromPEM(certsPEM []byte) ([]*x509.Certificate, []byte, error) { block, rest := pem.Decode(certsPEM) if block == nil { return nil, rest, nil } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { pkcs7data, err := pkcs7.ParsePKCS7(block.Bytes) if err != nil { return nil, rest, err } if pkcs7data.ContentInfo != "SignedData" { return nil, rest, errors.New("only PKCS #7 Signed Data Content Info supported for certificate parsing") } certs := pkcs7data.Content.SignedData.Certificates if certs == nil { return nil, rest, errors.New("PKCS #7 structure contains no certificates") } return certs, rest, nil } var certs = []*x509.Certificate{cert} return certs, rest, nil } // LoadPEMCertPool loads a pool of PEM certificates from file. func LoadPEMCertPool(certsFile string) (*x509.CertPool, error) { if certsFile == "" { return nil, nil } pemCerts, err := os.ReadFile(certsFile) if err != nil { return nil, err } return PEMToCertPool(pemCerts) } // PEMToCertPool concerts PEM certificates to a CertPool. func PEMToCertPool(pemCerts []byte) (*x509.CertPool, error) { if len(pemCerts) == 0 { return nil, nil } certPool := x509.NewCertPool() if !certPool.AppendCertsFromPEM(pemCerts) { return nil, errors.New("failed to load cert pool") } return certPool, nil } // ParsePrivateKeyPEM parses and returns a PEM-encoded private // key. The private key may be either an unencrypted PKCS#8, PKCS#1, // or elliptic private key. func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) { return ParsePrivateKeyPEMWithPassword(keyPEM, nil) } // ParsePrivateKeyPEMWithPassword parses and returns a PEM-encoded private // key. The private key may be a potentially encrypted PKCS#8, PKCS#1, // or elliptic private key. func ParsePrivateKeyPEMWithPassword(keyPEM []byte, password []byte) (key crypto.Signer, err error) { keyDER, err := GetKeyDERFromPEM(keyPEM, password) if err != nil { return nil, err } return ParsePrivateKeyDER(keyDER) } // GetKeyDERFromPEM parses a PEM-encoded private key and returns DER-format key bytes. func GetKeyDERFromPEM(in []byte, password []byte) ([]byte, error) { // Ignore any EC PARAMETERS blocks when looking for a key (openssl includes // them by default). var keyDER *pem.Block for { keyDER, in = pem.Decode(in) if keyDER == nil || keyDER.Type != "EC PARAMETERS" { break } } if keyDER != nil { if procType, ok := keyDER.Headers["Proc-Type"]; ok { if strings.Contains(procType, "ENCRYPTED") { if password != nil { return x509.DecryptPEMBlock(keyDER, password) } return nil, certerr.DecodeError(certerr.ErrorSourcePrivateKey, certerr.ErrEncryptedPrivateKey) } } return keyDER.Bytes, nil } return nil, certerr.DecodeError(certerr.ErrorSourcePrivateKey, errors.New("failed to decode private key")) } // ParseCSR parses a PEM- or DER-encoded PKCS #10 certificate signing request. func ParseCSR(in []byte) (csr *x509.CertificateRequest, rest []byte, err error) { in = bytes.TrimSpace(in) p, rest := pem.Decode(in) if p != nil { if p.Type != "NEW CERTIFICATE REQUEST" && p.Type != "CERTIFICATE REQUEST" { return nil, rest, certerr.ParsingError(certerr.ErrorSourceCSR, certerr.ErrInvalidPEMType(p.Type, "NEW CERTIFICATE REQUEST", "CERTIFICATE REQUEST")) } csr, err = x509.ParseCertificateRequest(p.Bytes) } else { csr, err = x509.ParseCertificateRequest(in) } if err != nil { return nil, rest, err } err = csr.CheckSignature() if err != nil { return nil, rest, err } return csr, rest, nil } // ParseCSRPEM parses a PEM-encoded certificate signing request. // It does not check the signature. This is useful for dumping data from a CSR // locally. func ParseCSRPEM(csrPEM []byte) (*x509.CertificateRequest, error) { block, _ := pem.Decode([]byte(csrPEM)) if block == nil { return nil, certerr.DecodeError(certerr.ErrorSourceCSR, errors.New("PEM block is empty")) } csrObject, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return nil, err } return csrObject, nil } // SignerAlgo returns an X.509 signature algorithm from a crypto.Signer. func SignerAlgo(priv crypto.Signer) x509.SignatureAlgorithm { switch pub := priv.Public().(type) { case *rsa.PublicKey: bitLength := pub.N.BitLen() switch { case bitLength >= 4096: return x509.SHA512WithRSA case bitLength >= 3072: return x509.SHA384WithRSA case bitLength >= 2048: return x509.SHA256WithRSA default: return x509.SHA1WithRSA } case *ecdsa.PublicKey: switch pub.Curve { case elliptic.P521(): return x509.ECDSAWithSHA512 case elliptic.P384(): return x509.ECDSAWithSHA384 case elliptic.P256(): return x509.ECDSAWithSHA256 default: return x509.ECDSAWithSHA1 } default: return x509.UnknownSignatureAlgorithm } } // LoadClientCertificate load key/certificate from pem files func LoadClientCertificate(certFile string, keyFile string) (*tls.Certificate, error) { if certFile != "" && keyFile != "" { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, certerr.LoadingError(certerr.ErrorSourceKeypair, err) } return &cert, nil } return nil, nil } // CreateTLSConfig creates a tls.Config object from certs and roots func CreateTLSConfig(remoteCAs *x509.CertPool, cert *tls.Certificate) *tls.Config { var certs []tls.Certificate if cert != nil { certs = []tls.Certificate{*cert} } return &tls.Config{ Certificates: certs, RootCAs: remoteCAs, } } // SerializeSCTList serializes a list of SCTs. func SerializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) { list := ctx509.SignedCertificateTimestampList{} for _, sct := range sctList { sctBytes, err := cttls.Marshal(sct) if err != nil { return nil, err } list.SCTList = append(list.SCTList, ctx509.SerializedSCT{Val: sctBytes}) } return cttls.Marshal(list) } // DeserializeSCTList deserializes a list of SCTs. func DeserializeSCTList(serializedSCTList []byte) ([]ct.SignedCertificateTimestamp, error) { var sctList ctx509.SignedCertificateTimestampList rest, err := cttls.Unmarshal(serializedSCTList, &sctList) if err != nil { return nil, err } if len(rest) != 0 { return nil, certerr.ParsingError(certerr.ErrorSourceSCTList, errors.New("serialized SCT list contained trailing garbage")) } list := make([]ct.SignedCertificateTimestamp, len(sctList.SCTList)) for i, serializedSCT := range sctList.SCTList { var sct ct.SignedCertificateTimestamp rest, err := cttls.Unmarshal(serializedSCT.Val, &sct) if err != nil { return nil, err } if len(rest) != 0 { return nil, certerr.ParsingError(certerr.ErrorSourceSCTList, errors.New("serialized SCT list contained trailing garbage")) } list[i] = sct } return list, nil } // SCTListFromOCSPResponse extracts the SCTList from an ocsp.Response, // returning an empty list if the SCT extension was not found or could not be // unmarshalled. func SCTListFromOCSPResponse(response *ocsp.Response) ([]ct.SignedCertificateTimestamp, error) { // This loop finds the SCTListExtension in the OCSP response. var SCTListExtension, ext pkix.Extension for _, ext = range response.Extensions { // sctExtOid is the ObjectIdentifier of a Signed Certificate Timestamp. sctExtOid := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5} if ext.Id.Equal(sctExtOid) { SCTListExtension = ext break } } // This code block extracts the sctList from the SCT extension. var sctList []ct.SignedCertificateTimestamp var err error if numBytes := len(SCTListExtension.Value); numBytes != 0 { var serializedSCTList []byte rest := make([]byte, numBytes) copy(rest, SCTListExtension.Value) for len(rest) != 0 { rest, err = asn1.Unmarshal(rest, &serializedSCTList) if err != nil { return nil, certerr.ParsingError(certerr.ErrorSourceSCTList, err) } } sctList, err = DeserializeSCTList(serializedSCTList) } return sctList, err } // ReadBytes reads a []byte either from a file or an environment variable. // If valFile has a prefix of 'env:', the []byte is read from the environment // using the subsequent name. If the prefix is 'file:' the []byte is read from // the subsequent file. If no prefix is provided, valFile is assumed to be a // file path. func ReadBytes(valFile string) ([]byte, error) { switch splitVal := strings.SplitN(valFile, ":", 2); len(splitVal) { case 1: return os.ReadFile(valFile) case 2: switch splitVal[0] { case "env": return []byte(os.Getenv(splitVal[1])), nil case "file": return os.ReadFile(splitVal[1]) default: return nil, fmt.Errorf("unknown prefix: %s", splitVal[0]) } default: return nil, fmt.Errorf("multiple prefixes: %s", strings.Join(splitVal[:len(splitVal)-1], ", ")) } }