in traffic_ops/traffic_ops_golang/deliveryservice/keys.go [397:566]
func verifyCertKeyPair(pemCertificate string, pemPrivateKey string, rootCA string, allowEC bool) (string, string, bool, bool, bool, error) {
// decode, verify, and order certs for storage
cleanPemPrivateKey := ""
certs := strings.SplitAfter(pemCertificate, PemCertEndMarker)
if len(certs) <= 1 {
return "", "", false, false, false, errors.New("no certificate chain to verify")
}
// decode and verify the server certificate
block, _ := pem.Decode([]byte(certs[0]))
if block == nil {
return "", "", false, false, false, errors.New("could not decode pem-encoded server certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", "", false, false, false, errors.New("could not parse the server certificate: " + err.Error())
}
// Common x509 certificate validation
err = commonX509CertificateValidation(cert)
if err != nil {
return "", "", false, false, false, err
}
switch cert.PublicKeyAlgorithm {
case x509.RSA:
var rsaPrivateKey *rsa.PrivateKey
// RSA is both a digital signature and encryption algorithm, hence the key encipherment
// usage must be indicated in the certificate.
// The keyUsage and extended Key Usage does not exist in version 1 of the x509 specificication.
if cert.Version > 1 && !(cert.KeyUsage&x509.KeyUsageKeyEncipherment > 0) {
return "", "", false, false, false, errors.New("cert/key (rsa) validation: no keyEncipherment keyUsage extension present in x509v3 server certificate")
}
// Extract the RSA public key from the x509 certificate
certPublicKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok || certPublicKey == nil {
return "", "", false, false, false, errors.New("cert/key (rsa) validation error: could not extract public RSA key from certificate")
}
// Attempt to decode the RSA private key
rsaPrivateKey, cleanPemPrivateKey, err = decodeRSAPrivateKey(pemPrivateKey)
if err != nil {
return "", "", false, false, false, err
}
// Check RSA private key modulus against the x509 RSA public key modulus
if rsaPrivateKey != nil && certPublicKey != nil && !bytes.Equal(rsaPrivateKey.N.Bytes(), certPublicKey.N.Bytes()) {
return "", "", false, false, false, errors.New("cert/key (rsa) mismatch error: RSA public N modulus value mismatch")
}
case x509.ECDSA:
var ecdsaPrivateKey *ecdsa.PrivateKey
// Only permit ECDSA support for DNS* DSTypes until the Traffic Router can support it
if !allowEC {
return "", "", false, false, false, errors.New("cert/key validation error: ECDSA public key algorithm unsupported for non-DNS delivery service type")
}
// DSA and ECDSA is not an encryption algorithm and only a signing algorithm, hence the
// certificate only needs to have the DigitalSignature KeyUsage indicated.
if cert.Version > 1 && !(cert.KeyUsage&x509.KeyUsageDigitalSignature > 0) {
return "", "", false, false, false, errors.New("cert/key (ecdsa) validation error: no digitalSignature keyUsage extension present in x509v3 server certificate")
}
// Attempt to decode the ECDSA private key
ecdsaPrivateKey, cleanPemPrivateKey, err = decodeECDSAPrivateKey(pemPrivateKey)
if err != nil {
return "", "", false, false, false, err
}
// Extract the ECDSA public key from the x509 certificate
certPublicKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok || certPublicKey == nil {
return "", "", false, false, false, errors.New("cert/key (ecdsa) validation error: could not get extract public ECDSA key from certificate")
}
// Compare the ECDSA curve name contained within the x509.PublicKey against the curve name indicated in the private key
if certPublicKey.Params().Name != ecdsaPrivateKey.Params().Name {
return "", "", false, false, false, errors.New("cert/key (ecdsa) mismatch error: ECDSA curve name in cert does not match curve name in private key")
}
// Verify that ECDSA public value X matches in both the cert.PublicKey and the private key.
if !bytes.Equal(certPublicKey.X.Bytes(), ecdsaPrivateKey.X.Bytes()) {
return "", "", false, false, false, errors.New("cert/key (ecdsa) mismatch error: ECDSA public X value mismatch")
}
// Verify that ECDSA public value Y matches in both the cert.PublicKey and the private key.
if !bytes.Equal(certPublicKey.Y.Bytes(), ecdsaPrivateKey.Y.Bytes()) {
return "", "", false, false, false, errors.New("cert/key (ecdsa) mismatch error: ECDSA public Y value mismatch")
}
case x509.DSA:
return "", "", false, false, false, errors.New("cert/key validation error: DSA public key algorithm unsupported")
case x509.UnknownPublicKeyAlgorithm:
fallthrough
default:
return "", "", false, false, false, errors.New("cert/key validation error: Unknown public key algorithm")
}
bundle := ""
parsedCerts := []*x509.Certificate{}
for i := 0; i < len(certs)-1; i++ {
bundle += certs[i]
blk, _ := pem.Decode([]byte(certs[i]))
c, err := x509.ParseCertificate(blk.Bytes)
if err != nil {
return "", "", false, false, false, errors.New("unable to parse intermediate certificate: " + err.Error())
}
parsedCerts = append(parsedCerts, c)
}
intermediatePool := x509.NewCertPool()
if !intermediatePool.AppendCertsFromPEM([]byte(bundle)) {
return "", "", false, false, false, errors.New("certificate CA bundle is empty")
}
opts := x509.VerifyOptions{
Intermediates: intermediatePool,
}
if rootCA != "" {
// verify the certificate chain.
rootPool := x509.NewCertPool()
if !rootPool.AppendCertsFromPEM([]byte(rootCA)) {
return "", "", false, false, false, errors.New("unable to parse root CA certificate")
}
opts.Roots = rootPool
}
for i := 0; i < len(parsedCerts)-1; i++ {
current := parsedCerts[i]
next := parsedCerts[i+1]
if current.Issuer.String() != next.Subject.String() {
return "", "", false, false, true, nil
}
}
chain, err := cert.Verify(opts)
if err != nil {
if _, ok := err.(x509.UnknownAuthorityError); ok {
return pemCertificate, cleanPemPrivateKey, true, false, false, nil
}
return "", "", false, false, false, errors.New("could not verify the certificate chain: " + err.Error())
}
if len(chain) < 1 {
return "", "", false, false, false, errors.New("can't find valid chain for cert in file in request")
}
pemEncodedChain := ""
for _, link := range chain[0] {
// Include all certificates in the chain, since verification was successful.
block := &pem.Block{Type: "CERTIFICATE", Bytes: link.Raw}
pemEncodedChain += string(pem.EncodeToMemory(block))
}
pemCertificate = strings.TrimSpace(pemCertificate)
pemEncodedChain = strings.TrimSpace(pemEncodedChain)
if len(pemEncodedChain) < 1 {
return "", "", false, false, false, errors.New("invalid empty certificate chain in request")
}
if pemEncodedChain != pemCertificate {
return pemCertificate, cleanPemPrivateKey, false, true, false, nil
}
return pemCertificate, cleanPemPrivateKey, false, false, false, nil
}