123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 |
- // Package helpers implements utility functionality common to many
- // CFSSL packages.
- package helpers
- import (
- "bytes"
- "crypto"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rsa"
- "crypto/tls"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/asn1"
- "encoding/pem"
- "errors"
- "fmt"
- "os"
- "strings"
- "time"
- "github.com/cloudflare/cfssl/crypto/pkcs7"
- cferr "github.com/cloudflare/cfssl/errors"
- "github.com/cloudflare/cfssl/helpers/derhelpers"
- "github.com/cloudflare/cfssl/log"
- 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, RSA or Ed25519 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()
- } else if _, ok := key.(ed25519.PublicKey); ok {
- return ed25519.PublicKeySize
- }
- 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
- }
- return MonthsValid(c) <= maxMonths
- }
- // 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"
- case x509.PureEd25519:
- return "Ed25519"
- default:
- return "Unknown Signature"
- }
- }
- // HashAlgoString returns the hash algorithm name contained 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"
- case x509.PureEd25519:
- return "Ed25519"
- 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, cferr.New(cferr.CertificateError, cferr.ParseFailed)
- } else if cert == nil {
- break
- }
- certs = append(certs, cert...)
- }
- if len(certsPEM) > 0 {
- return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
- }
- 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, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
- }
- } else {
- key = pkcs12data.(crypto.Signer)
- }
- } else {
- if pkcs7data.ContentInfo != "SignedData" {
- return nil, nil, cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, errors.New("can only extract certificates from signed data content info"))
- }
- certs = pkcs7data.Content.SignedData.Certificates
- }
- if certs == nil {
- return nil, key, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
- }
- 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, cferr.Wrap(cferr.CertificateError, cferr.VerifyFailed, 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 {
- // Log the actual parsing error but throw a default parse error message.
- log.Debugf("Certificate parsing error: %v", err)
- return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed)
- } else if cert == nil {
- return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
- } else if len(rest) > 0 {
- return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PEM file should contain only one object"))
- } else if len(cert) > 1 {
- return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, 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 derhelpers.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, cferr.New(cferr.PrivateKeyError, cferr.Encrypted)
- }
- }
- return keyDER.Bytes, nil
- }
- return nil, cferr.New(cferr.PrivateKeyError, cferr.DecodeFailed)
- }
- // 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, cferr.New(cferr.CSRError, cferr.BadRequest)
- }
- 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, cferr.New(cferr.CSRError, cferr.DecodeFailed)
- }
- csrObject, err := x509.ParseCertificateRequest(block.Bytes)
- if err != nil {
- return nil, err
- }
- return csrObject, nil
- }
- // ParseCSRDER parses a PEM-encoded certificate signing request.
- // It does not check the signature. This is useful for dumping data from a CSR
- // locally.
- func ParseCSRDER(csrDER []byte) (*x509.CertificateRequest, error) {
- csrObject, err := x509.ParseCertificateRequest(csrDER)
- if err != nil {
- return nil, err
- }
- err = csrObject.CheckSignature()
- 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
- }
- case ed25519.PublicKey:
- return x509.PureEd25519
- 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 {
- log.Criticalf("Unable to read client certificate from file: %s or key from file: %s", certFile, keyFile)
- return nil, err
- }
- log.Debug("Client certificate loaded ")
- 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, cferr.Wrap(cferr.CTError, cferr.Unknown, 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, cferr.Wrap(cferr.CTError, cferr.Unknown, errors.New("serialized SCT 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, cferr.Wrap(cferr.CTError, cferr.Unknown, 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], ", "))
- }
- }
|