istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/util/generate_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 // Provides utility methods to generate X.509 certificates with different 16 // options. This implementation is Largely inspired from 17 // https://golang.org/src/crypto/tls/generate_cert.go. 18 19 package util 20 21 import ( 22 "crypto" 23 "crypto/ecdsa" 24 "crypto/ed25519" 25 "crypto/elliptic" 26 "crypto/rand" 27 "crypto/rsa" 28 "crypto/x509" 29 "crypto/x509/pkix" 30 "encoding/pem" 31 "errors" 32 "fmt" 33 "math/big" 34 "os" 35 "strings" 36 "time" 37 38 "istio.io/istio/pkg/log" 39 ) 40 41 // SupportedECSignatureAlgorithms are the types of EC Signature Algorithms 42 // to be used in key generation (e.g. ECDSA or ED2551) 43 type SupportedECSignatureAlgorithms string 44 45 // SupportedEllipticCurves are the types of curves 46 // to be used in key generation (e.g. P256, P384) 47 type SupportedEllipticCurves string 48 49 const ( 50 // only ECDSA is currently supported 51 EcdsaSigAlg SupportedECSignatureAlgorithms = "ECDSA" 52 53 // supported curves when using ECC 54 P256Curve SupportedEllipticCurves = "P256" 55 P384Curve SupportedEllipticCurves = "P384" 56 ) 57 58 // CertOptions contains options for generating a new certificate. 59 type CertOptions struct { 60 // Comma-separated hostnames and IPs to generate a certificate for. 61 // This can also be set to the identity running the workload, 62 // like kubernetes service account. 63 Host string 64 65 // The NotBefore field of the issued certificate. 66 NotBefore time.Time 67 68 // TTL of the certificate. NotAfter - NotBefore. 69 TTL time.Duration 70 71 // Signer certificate. 72 SignerCert *x509.Certificate 73 74 // Signer private key. 75 SignerPriv crypto.PrivateKey 76 77 // Signer private key (PEM encoded). 78 SignerPrivPem []byte 79 80 // Organization for this certificate. 81 Org string 82 83 // The size of RSA private key to be generated. 84 RSAKeySize int 85 86 // Whether this certificate is used as signing cert for CA. 87 IsCA bool 88 89 // Whether this certificate is self-signed. 90 IsSelfSigned bool 91 92 // Whether this certificate is for a client. 93 IsClient bool 94 95 // Whether this certificate is for a server. 96 IsServer bool 97 98 // Whether this certificate is for dual-use clients (SAN+CN). 99 IsDualUse bool 100 101 // If true, the private key is encoded with PKCS#8. 102 PKCS8Key bool 103 104 // The type of Elliptical Signature algorithm to use 105 // when generating private keys. Currently only ECDSA is supported. 106 // If empty, RSA is used, otherwise ECC is used. 107 ECSigAlg SupportedECSignatureAlgorithms 108 109 // The type of Elliptical Signature algorithm to use 110 // when generating private keys. Currently only ECDSA is supported. 111 // If empty, RSA is used, otherwise ECC is used. 112 ECCCurve SupportedEllipticCurves 113 114 // Subjective Alternative Name values. 115 DNSNames string 116 } 117 118 // GenCertKeyFromOptions generates a X.509 certificate and a private key with the given options. 119 func GenCertKeyFromOptions(options CertOptions) (pemCert []byte, pemKey []byte, err error) { 120 // Generate the appropriate private&public key pair based on options. 121 // The public key will be bound to the certificate generated below. The 122 // private key will be used to sign this certificate in the self-signed 123 // case, otherwise the certificate is signed by the signer private key 124 // as specified in the CertOptions. 125 if options.ECSigAlg != "" { 126 var ecPriv *ecdsa.PrivateKey 127 128 switch options.ECSigAlg { 129 case EcdsaSigAlg: 130 var curve elliptic.Curve 131 switch options.ECCCurve { 132 case P384Curve: 133 curve = elliptic.P384() 134 default: 135 curve = elliptic.P256() 136 } 137 138 ecPriv, err = ecdsa.GenerateKey(curve, rand.Reader) 139 if err != nil { 140 return nil, nil, fmt.Errorf("cert generation fails at EC key generation (%v)", err) 141 } 142 143 default: 144 return nil, nil, errors.New("cert generation fails due to unsupported EC signature algorithm") 145 } 146 return genCert(options, ecPriv, &ecPriv.PublicKey) 147 } 148 149 if options.RSAKeySize < minimumRsaKeySize { 150 return nil, nil, fmt.Errorf("requested key size does not meet the minimum required size of %d (requested: %d)", minimumRsaKeySize, options.RSAKeySize) 151 } 152 rsaPriv, err := rsa.GenerateKey(rand.Reader, options.RSAKeySize) 153 if err != nil { 154 return nil, nil, fmt.Errorf("cert generation fails at RSA key generation (%v)", err) 155 } 156 return genCert(options, rsaPriv, &rsaPriv.PublicKey) 157 } 158 159 func genCert(options CertOptions, priv any, key any) ([]byte, []byte, error) { 160 template, err := genCertTemplateFromOptions(options) 161 if err != nil { 162 return nil, nil, fmt.Errorf("cert generation fails at cert template creation (%v)", err) 163 } 164 signerCert, signerKey := template, crypto.PrivateKey(priv) 165 if !options.IsSelfSigned { 166 signerCert, signerKey = options.SignerCert, options.SignerPriv 167 } 168 certBytes, err := x509.CreateCertificate(rand.Reader, template, signerCert, key, signerKey) 169 if err != nil { 170 return nil, nil, fmt.Errorf("cert generation fails at X509 cert creation (%v)", err) 171 } 172 173 pemCert, pemKey, err := encodePem(false, certBytes, priv, options.PKCS8Key) 174 return pemCert, pemKey, err 175 } 176 177 func publicKey(priv any) any { 178 switch k := priv.(type) { 179 case *rsa.PrivateKey: 180 return &k.PublicKey 181 case *ecdsa.PrivateKey: 182 return &k.PublicKey 183 case ed25519.PrivateKey: 184 return k.Public().(ed25519.PublicKey) 185 default: 186 return nil 187 } 188 } 189 190 // GenRootCertFromExistingKey generates a X.509 certificate using existing 191 // CA private key. Only called by a self-signed Citadel. 192 func GenRootCertFromExistingKey(options CertOptions) (pemCert []byte, pemKey []byte, err error) { 193 if !options.IsSelfSigned || len(options.SignerPrivPem) == 0 { 194 return nil, nil, fmt.Errorf("skip cert " + 195 "generation. Citadel is not in self-signed mode or CA private key is not " + 196 "available") 197 } 198 199 template, err := genCertTemplateFromOptions(options) 200 if err != nil { 201 return nil, nil, fmt.Errorf("cert generation fails at cert template creation (%v)", err) 202 } 203 caPrivateKey, err := ParsePemEncodedKey(options.SignerPrivPem) 204 if err != nil { 205 return nil, nil, fmt.Errorf("unrecogniazed CA "+ 206 "private key, skip root cert rotation: %s", err.Error()) 207 } 208 certBytes, err := x509.CreateCertificate(rand.Reader, template, template, publicKey(caPrivateKey), caPrivateKey) 209 if err != nil { 210 return nil, nil, fmt.Errorf("cert generation fails at X509 cert creation (%v)", err) 211 } 212 213 pemCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) 214 return pemCert, options.SignerPrivPem, nil 215 } 216 217 // GetCertOptionsFromExistingCert parses cert and generates a CertOptions 218 // that contains information about the cert. This is the reverse operation of 219 // genCertTemplateFromOptions(), and only called by a self-signed Citadel. 220 func GetCertOptionsFromExistingCert(certBytes []byte) (opts CertOptions, err error) { 221 cert, certErr := ParsePemEncodedCertificate(certBytes) 222 if certErr != nil { 223 return opts, certErr 224 } 225 226 orgs := cert.Subject.Organization 227 if len(orgs) > 0 { 228 opts.Org = orgs[0] 229 } 230 // TODO(JimmyCYJ): parse other fields from certificate, e.g. CommonName. 231 return opts, nil 232 } 233 234 // MergeCertOptions merges deltaOpts into defaultOpts and returns the merged 235 // CertOptions. Only called by a self-signed Citadel. 236 func MergeCertOptions(defaultOpts, deltaOpts CertOptions) CertOptions { 237 if len(deltaOpts.Org) > 0 { 238 defaultOpts.Org = deltaOpts.Org 239 } 240 // TODO(JimmyCYJ): merge other fields, e.g. Host, IsDualUse, etc. 241 return defaultOpts 242 } 243 244 // GenCertFromCSR generates a X.509 certificate with the given CSR. 245 func GenCertFromCSR(csr *x509.CertificateRequest, signingCert *x509.Certificate, publicKey any, 246 signingKey crypto.PrivateKey, subjectIDs []string, ttl time.Duration, isCA bool, 247 ) (cert []byte, err error) { 248 tmpl, err := genCertTemplateFromCSR(csr, subjectIDs, ttl, isCA) 249 if err != nil { 250 return nil, err 251 } 252 return x509.CreateCertificate(rand.Reader, tmpl, signingCert, publicKey, signingKey) 253 } 254 255 // LoadSignerCredsFromFiles loads the signer cert&key from the given files. 256 // 257 // signerCertFile: cert file name 258 // signerPrivFile: private key file name 259 func LoadSignerCredsFromFiles(signerCertFile string, signerPrivFile string) (*x509.Certificate, crypto.PrivateKey, error) { 260 signerCertBytes, err := os.ReadFile(signerCertFile) 261 if err != nil { 262 return nil, nil, fmt.Errorf("certificate file reading failure (%v)", err) 263 } 264 265 signerPrivBytes, err := os.ReadFile(signerPrivFile) 266 if err != nil { 267 return nil, nil, fmt.Errorf("private key file reading failure (%v)", err) 268 } 269 270 cert, err := ParsePemEncodedCertificate(signerCertBytes) 271 if err != nil { 272 return nil, nil, fmt.Errorf("pem encoded cert parsing failure (%v)", err) 273 } 274 275 key, err := ParsePemEncodedKey(signerPrivBytes) 276 if err != nil { 277 return nil, nil, fmt.Errorf("pem encoded key parsing failure (%v)", err) 278 } 279 280 return cert, key, nil 281 } 282 283 // ClockSkewGracePeriod defines the period of time a certificate will be valid before its creation. 284 // This is meant to handle cases where we have clock skew between the CA and workloads. 285 const ClockSkewGracePeriod = time.Minute * 2 286 287 // genCertTemplateFromCSR generates a certificate template with the given CSR. 288 // The NotBefore value of the cert is set to current time. 289 func genCertTemplateFromCSR(csr *x509.CertificateRequest, subjectIDs []string, ttl time.Duration, isCA bool) ( 290 *x509.Certificate, error, 291 ) { 292 subjectIDsInString := strings.Join(subjectIDs, ",") 293 var keyUsage x509.KeyUsage 294 extKeyUsages := []x509.ExtKeyUsage{} 295 if isCA { 296 // If the cert is a CA cert, the private key is allowed to sign other certificates. 297 keyUsage = x509.KeyUsageCertSign 298 } else { 299 // Otherwise the private key is allowed for digital signature and key encipherment. 300 keyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment 301 // For now, we do not differentiate non-CA certs to be used on client auth or server auth. 302 extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) 303 } 304 305 // Build cert extensions with the subjectIDs. 306 ext, err := BuildSubjectAltNameExtension(subjectIDsInString) 307 if err != nil { 308 return nil, err 309 } 310 exts := []pkix.Extension{*ext} 311 312 subject := pkix.Name{} 313 // Dual use mode if common name in CSR is not empty. 314 // In this case, set CN as determined by DualUseCommonName(subjectIDsInString). 315 if len(csr.Subject.CommonName) != 0 { 316 if cn, err := DualUseCommonName(subjectIDsInString); err != nil { 317 // log and continue 318 log.Errorf("dual-use failed for cert template - omitting CN (%v)", err) 319 } else { 320 subject.CommonName = cn 321 } 322 } 323 324 now := time.Now() 325 326 serialNum, err := genSerialNum() 327 if err != nil { 328 return nil, err 329 } 330 // SignatureAlgorithm will use the default algorithm. 331 // See https://golang.org/src/crypto/x509/x509.go?s=5131:5158#L1965 . 332 return &x509.Certificate{ 333 SerialNumber: serialNum, 334 Subject: subject, 335 NotBefore: now.Add(-ClockSkewGracePeriod), 336 NotAfter: now.Add(ttl), 337 KeyUsage: keyUsage, 338 ExtKeyUsage: extKeyUsages, 339 IsCA: isCA, 340 BasicConstraintsValid: true, 341 ExtraExtensions: exts, 342 }, nil 343 } 344 345 // genCertTemplateFromoptions generates a certificate template with the given options. 346 func genCertTemplateFromOptions(options CertOptions) (*x509.Certificate, error) { 347 var keyUsage x509.KeyUsage 348 if options.IsCA { 349 // If the cert is a CA cert, the private key is allowed to sign other certificates. 350 keyUsage = x509.KeyUsageCertSign 351 } else { 352 // Otherwise the private key is allowed for digital signature and key encipherment. 353 keyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment 354 } 355 356 extKeyUsages := []x509.ExtKeyUsage{} 357 if options.IsServer { 358 extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth) 359 } 360 if options.IsClient { 361 extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageClientAuth) 362 } 363 364 notBefore := time.Now() 365 if !options.NotBefore.IsZero() { 366 notBefore = options.NotBefore 367 } 368 369 serialNum, err := genSerialNum() 370 if err != nil { 371 return nil, err 372 } 373 374 subject := pkix.Name{ 375 Organization: []string{options.Org}, 376 } 377 378 exts := []pkix.Extension{} 379 if h := options.Host; len(h) > 0 { 380 s, err := BuildSubjectAltNameExtension(h) 381 if err != nil { 382 return nil, err 383 } 384 if options.IsDualUse { 385 cn, err := DualUseCommonName(h) 386 if err != nil { 387 // log and continue 388 log.Errorf("dual-use failed for cert template - omitting CN (%v)", err) 389 } else { 390 subject.CommonName = cn 391 } 392 } 393 exts = []pkix.Extension{*s} 394 } 395 396 dnsNames := strings.Split(options.DNSNames, ",") 397 if len(dnsNames[0]) == 0 { 398 dnsNames = nil 399 } 400 401 return &x509.Certificate{ 402 SerialNumber: serialNum, 403 Subject: subject, 404 NotBefore: notBefore, 405 NotAfter: notBefore.Add(options.TTL), 406 KeyUsage: keyUsage, 407 ExtKeyUsage: extKeyUsages, 408 IsCA: options.IsCA, 409 BasicConstraintsValid: true, 410 ExtraExtensions: exts, 411 DNSNames: dnsNames, 412 }, nil 413 } 414 415 func genSerialNum() (*big.Int, error) { 416 serialNumLimit := new(big.Int).Lsh(big.NewInt(1), 128) 417 serialNum, err := rand.Int(rand.Reader, serialNumLimit) 418 if err != nil { 419 return nil, fmt.Errorf("serial number generation failure (%v)", err) 420 } 421 return serialNum, nil 422 } 423 424 func encodePem(isCSR bool, csrOrCert []byte, priv any, pkcs8 bool) ( 425 csrOrCertPem []byte, privPem []byte, err error, 426 ) { 427 encodeMsg := "CERTIFICATE" 428 if isCSR { 429 encodeMsg = "CERTIFICATE REQUEST" 430 } 431 csrOrCertPem = pem.EncodeToMemory(&pem.Block{Type: encodeMsg, Bytes: csrOrCert}) 432 433 var encodedKey []byte 434 if pkcs8 { 435 if encodedKey, err = x509.MarshalPKCS8PrivateKey(priv); err != nil { 436 return nil, nil, err 437 } 438 privPem = pem.EncodeToMemory(&pem.Block{Type: blockTypePKCS8PrivateKey, Bytes: encodedKey}) 439 } else { 440 switch k := priv.(type) { 441 case *rsa.PrivateKey: 442 encodedKey = x509.MarshalPKCS1PrivateKey(k) 443 privPem = pem.EncodeToMemory(&pem.Block{Type: blockTypeRSAPrivateKey, Bytes: encodedKey}) 444 case *ecdsa.PrivateKey: 445 encodedKey, err = x509.MarshalECPrivateKey(k) 446 if err != nil { 447 return nil, nil, err 448 } 449 privPem = pem.EncodeToMemory(&pem.Block{Type: blockTypeECPrivateKey, Bytes: encodedKey}) 450 } 451 } 452 err = nil 453 return 454 }