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 }