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  }