Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
17 KiB
590 lines
17 KiB
// Package helpers implements utility functionality common to many |
|
// CFSSL packages. |
|
package helpers |
|
|
|
import ( |
|
"bytes" |
|
"crypto" |
|
"crypto/ecdsa" |
|
"crypto/elliptic" |
|
"crypto/rsa" |
|
"crypto/tls" |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/asn1" |
|
"encoding/pem" |
|
"errors" |
|
"fmt" |
|
"io/ioutil" |
|
"os" |
|
|
|
"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" |
|
|
|
"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" |
|
"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 |
|
|
|
// 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, 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 := ioutil.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) { |
|
keyDER, _ := pem.Decode(in) |
|
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 |
|
} |
|
|
|
// 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 { |
|
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 ioutil.ReadFile(valFile) |
|
case 2: |
|
switch splitVal[0] { |
|
case "env": |
|
return []byte(os.Getenv(splitVal[1])), nil |
|
case "file": |
|
return ioutil.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], ", ")) |
|
} |
|
}
|
|
|