istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/util/verify_cert.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"crypto/ecdsa"
    19  	"crypto/rsa"
    20  	"crypto/x509"
    21  	"fmt"
    22  	"os"
    23  	"reflect"
    24  	"sort"
    25  	"strings"
    26  	"time"
    27  )
    28  
    29  // VerifyFields contains the certificate fields to verify in the test.
    30  type VerifyFields struct {
    31  	NotBefore   time.Time
    32  	TTL         time.Duration // NotAfter - NotBefore
    33  	ExtKeyUsage []x509.ExtKeyUsage
    34  	KeyUsage    x509.KeyUsage
    35  	IsCA        bool
    36  	Org         string
    37  	CommonName  string
    38  	Host        string
    39  }
    40  
    41  // VerifyCertificate verifies a given PEM encoded certificate by
    42  // - building one or more chains from the certificate to a root certificate;
    43  // - checking fields are set as expected.
    44  func VerifyCertificate(privPem []byte, certChainPem []byte, rootCertPem []byte, expectedFields *VerifyFields) error {
    45  	roots := x509.NewCertPool()
    46  	if rootCertPem != nil {
    47  		if ok := roots.AppendCertsFromPEM(rootCertPem); !ok {
    48  			return fmt.Errorf("failed to parse root certificate")
    49  		}
    50  	}
    51  
    52  	intermediates := x509.NewCertPool()
    53  	if ok := intermediates.AppendCertsFromPEM(certChainPem); !ok {
    54  		return fmt.Errorf("failed to parse certificate chain")
    55  	}
    56  
    57  	cert, err := ParsePemEncodedCertificate(certChainPem)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	opts := x509.VerifyOptions{
    63  		Intermediates: intermediates,
    64  		Roots:         roots,
    65  	}
    66  	host := ""
    67  	if expectedFields != nil {
    68  		host = expectedFields.Host
    69  		san := host
    70  		// uri scheme is currently not supported in go VerifyOptions. We verify
    71  		// this uri at the end as a special case.
    72  		if strings.HasPrefix(host, "spiffe") {
    73  			san = ""
    74  		}
    75  		opts.DNSName = san
    76  	}
    77  	opts.KeyUsages = append(opts.KeyUsages, x509.ExtKeyUsageAny)
    78  
    79  	if _, err = cert.Verify(opts); err != nil {
    80  		return fmt.Errorf("failed to verify certificate: " + err.Error())
    81  	}
    82  	if privPem != nil {
    83  		priv, err := ParsePemEncodedKey(privPem)
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		privRSAKey, privRSAOk := priv.(*rsa.PrivateKey)
    89  		pubRSAKey, pubRSAOk := cert.PublicKey.(*rsa.PublicKey)
    90  
    91  		privECKey, privECOk := priv.(*ecdsa.PrivateKey)
    92  		pubECKey, pubECOk := cert.PublicKey.(*ecdsa.PublicKey)
    93  
    94  		rsaMatch := privRSAOk && pubRSAOk
    95  		ecMatch := privECOk && pubECOk
    96  
    97  		if rsaMatch {
    98  			if !reflect.DeepEqual(privRSAKey.PublicKey, *pubRSAKey) {
    99  				return fmt.Errorf("the generated private RSA key and cert doesn't match")
   100  			}
   101  		} else if ecMatch {
   102  			if !reflect.DeepEqual(privECKey.PublicKey, *pubECKey) {
   103  				return fmt.Errorf("the generated private EC key and cert doesn't match")
   104  			}
   105  		} else {
   106  			return fmt.Errorf("algorithms for private key and cert do not match")
   107  		}
   108  	}
   109  	if strings.HasPrefix(host, "spiffe") {
   110  		matchHost := false
   111  		ids, err := ExtractIDs(cert.Extensions)
   112  		if err != nil {
   113  			return err
   114  		}
   115  		for _, id := range ids {
   116  			if strings.HasSuffix(id, host) {
   117  				matchHost = true
   118  				break
   119  			}
   120  		}
   121  		if !matchHost {
   122  			return fmt.Errorf("the certificate doesn't have the expected SAN for: %s", host)
   123  		}
   124  	}
   125  	if expectedFields != nil {
   126  		if nb := expectedFields.NotBefore; !nb.IsZero() && !nb.Equal(cert.NotBefore) {
   127  			return fmt.Errorf("unexpected value for 'NotBefore' field: want %v but got %v", nb, cert.NotBefore)
   128  		}
   129  
   130  		if ttl := expectedFields.TTL; ttl != 0 && ttl != (cert.NotAfter.Sub(cert.NotBefore)) {
   131  			return fmt.Errorf("unexpected value for 'NotAfter' - 'NotBefore': want %v but got %v", ttl, cert.NotAfter.Sub(cert.NotBefore))
   132  		}
   133  
   134  		if eku := sortExtKeyUsage(expectedFields.ExtKeyUsage); !reflect.DeepEqual(eku, sortExtKeyUsage(cert.ExtKeyUsage)) {
   135  			return fmt.Errorf("unexpected value for 'ExtKeyUsage' field: want %v but got %v", eku, cert.ExtKeyUsage)
   136  		}
   137  
   138  		if ku := expectedFields.KeyUsage; ku != cert.KeyUsage {
   139  			return fmt.Errorf("unexpected value for 'KeyUsage' field: want %v but got %v", ku, cert.KeyUsage)
   140  		}
   141  
   142  		if isCA := expectedFields.IsCA; isCA != cert.IsCA {
   143  			return fmt.Errorf("unexpected value for 'IsCA' field: want %t but got %t", isCA, cert.IsCA)
   144  		}
   145  
   146  		if org := expectedFields.Org; org != "" && !reflect.DeepEqual([]string{org}, cert.Issuer.Organization) {
   147  			return fmt.Errorf("unexpected value for 'Organization' field: want %v but got %v",
   148  				[]string{org}, cert.Issuer.Organization)
   149  		}
   150  
   151  		if cn := expectedFields.CommonName; cn != cert.Subject.CommonName {
   152  			return fmt.Errorf("unexpected value for 'CommonName' field: want %v but got %v",
   153  				cn, cert.Subject.CommonName)
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  func sortExtKeyUsage(extKeyUsage []x509.ExtKeyUsage) []int {
   160  	data := make([]int, len(extKeyUsage))
   161  	for i := range extKeyUsage {
   162  		data[i] = int(extKeyUsage[i])
   163  	}
   164  	sort.Ints(data)
   165  	return data
   166  }
   167  
   168  // FindRootCertFromCertificateChainBytes find the root cert from cert chain
   169  func FindRootCertFromCertificateChainBytes(certBytes []byte) ([]byte, error) {
   170  	certChain, cert, err := ParsePemEncodedCertificateChain(certBytes)
   171  	if err != nil {
   172  		return nil, fmt.Errorf("error parsing root certificate: %s", err.Error())
   173  	}
   174  	rootCert := certChain[len(certChain)-1]
   175  
   176  	if !rootCert.IsCA {
   177  		return nil, fmt.Errorf("found root cert is not a ca type cert: %v", rootCert)
   178  	}
   179  
   180  	return cert, nil
   181  }
   182  
   183  // IsCertExpired returns  whether a cert expires
   184  func IsCertExpired(filepath string) (bool, error) {
   185  	var err error
   186  	var certPEMBlock []byte
   187  	certPEMBlock, err = os.ReadFile(filepath)
   188  	if err != nil {
   189  		return true, fmt.Errorf("failed to read the cert, error is %v", err)
   190  	}
   191  	x509Cert, err := ParsePemEncodedCertificate(certPEMBlock)
   192  	if err != nil {
   193  		return true, fmt.Errorf("failed to parse the cert, err is %v", err)
   194  	}
   195  	return x509Cert.NotAfter.Before(time.Now()), nil
   196  }