mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
f2614f2107
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
518 lines
15 KiB
Go
518 lines
15 KiB
Go
// 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"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io/ioutil"
|
|
"math/big"
|
|
|
|
"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"
|
|
}
|
|
}
|
|
|
|
// EncodeCertificatesPEM encodes a number of x509 certficates 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 certficates 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)
|
|
}
|
|
|
|
// CheckSignature verifies a signature made by the key on a CSR, such
|
|
// as on the CSR itself.
|
|
func CheckSignature(csr *x509.CertificateRequest, algo x509.SignatureAlgorithm, signed, signature []byte) error {
|
|
var hashType crypto.Hash
|
|
|
|
switch algo {
|
|
case x509.SHA1WithRSA, x509.ECDSAWithSHA1:
|
|
hashType = crypto.SHA1
|
|
case x509.SHA256WithRSA, x509.ECDSAWithSHA256:
|
|
hashType = crypto.SHA256
|
|
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
|
|
hashType = crypto.SHA384
|
|
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
|
|
hashType = crypto.SHA512
|
|
default:
|
|
return x509.ErrUnsupportedAlgorithm
|
|
}
|
|
|
|
if !hashType.Available() {
|
|
return x509.ErrUnsupportedAlgorithm
|
|
}
|
|
h := hashType.New()
|
|
|
|
h.Write(signed)
|
|
digest := h.Sum(nil)
|
|
|
|
switch pub := csr.PublicKey.(type) {
|
|
case *rsa.PublicKey:
|
|
return rsa.VerifyPKCS1v15(pub, hashType, digest, signature)
|
|
case *ecdsa.PublicKey:
|
|
ecdsaSig := new(struct{ R, S *big.Int })
|
|
if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil {
|
|
return err
|
|
}
|
|
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
|
|
return errors.New("x509: ECDSA signature contained zero or negative values")
|
|
}
|
|
if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) {
|
|
return errors.New("x509: ECDSA verification failure")
|
|
}
|
|
return nil
|
|
}
|
|
return x509.ErrUnsupportedAlgorithm
|
|
}
|
|
|
|
// 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 = CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
|
|
if err != nil {
|
|
return nil, rest, err
|
|
}
|
|
|
|
return csr, rest, nil
|
|
}
|
|
|
|
// ParseCSRPEM parses a PEM-encoded certificiate 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))
|
|
der := block.Bytes
|
|
csrObject, err := x509.ParseCertificateRequest(der)
|
|
|
|
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.Critical("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,
|
|
}
|
|
}
|