github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/certs.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package security
    12  
    13  import (
    14  	"context"
    15  	"crypto"
    16  	"crypto/rand"
    17  	"crypto/rsa"
    18  	"crypto/tls"
    19  	"crypto/x509"
    20  	"encoding/pem"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/cockroachdb/cockroach/pkg/util/envutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/log"
    28  	"github.com/cockroachdb/errors"
    29  )
    30  
    31  const (
    32  	keyFileMode  = 0600
    33  	certFileMode = 0644
    34  )
    35  
    36  // loadCACertAndKey loads the certificate and key files,parses them,
    37  // and returns the x509 certificate and private key.
    38  func loadCACertAndKey(sslCA, sslCAKey string) (*x509.Certificate, crypto.PrivateKey, error) {
    39  	// LoadX509KeyPair does a bunch of validation, including len(Certificates) != 0.
    40  	caCert, err := tls.LoadX509KeyPair(sslCA, sslCAKey)
    41  	if err != nil {
    42  		return nil, nil, errors.Errorf("error loading CA certificate %s and key %s: %s",
    43  			sslCA, sslCAKey, err)
    44  	}
    45  
    46  	// Extract x509 certificate from tls cert.
    47  	x509Cert, err := x509.ParseCertificate(caCert.Certificate[0])
    48  	if err != nil {
    49  		return nil, nil, errors.Errorf("error parsing CA certificate %s: %s", sslCA, err)
    50  	}
    51  	return x509Cert, caCert.PrivateKey, nil
    52  }
    53  
    54  func writeCertificateToFile(certFilePath string, certificate []byte, overwrite bool) error {
    55  	certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: certificate}
    56  
    57  	return WritePEMToFile(certFilePath, certFileMode, overwrite, certBlock)
    58  }
    59  
    60  func writeKeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error {
    61  	keyBlock, err := PrivateKeyToPEM(key)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	return WritePEMToFile(keyFilePath, keyFileMode, overwrite, keyBlock)
    67  }
    68  
    69  func writePKCS8KeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error {
    70  	keyBytes, err := PrivateKeyToPKCS8(key)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	return SafeWriteToFile(keyFilePath, keyFileMode, overwrite, keyBytes)
    76  }
    77  
    78  // CreateCAPair creates a general CA certificate and associated key.
    79  func CreateCAPair(
    80  	certsDir, caKeyPath string,
    81  	keySize int,
    82  	lifetime time.Duration,
    83  	allowKeyReuse bool,
    84  	overwrite bool,
    85  ) error {
    86  	return createCACertAndKey(certsDir, caKeyPath, CAPem, keySize, lifetime, allowKeyReuse, overwrite)
    87  }
    88  
    89  // CreateClientCAPair creates a client CA certificate and associated key.
    90  func CreateClientCAPair(
    91  	certsDir, caKeyPath string,
    92  	keySize int,
    93  	lifetime time.Duration,
    94  	allowKeyReuse bool,
    95  	overwrite bool,
    96  ) error {
    97  	return createCACertAndKey(certsDir, caKeyPath, ClientCAPem, keySize, lifetime, allowKeyReuse, overwrite)
    98  }
    99  
   100  // CreateUICAPair creates a UI CA certificate and associated key.
   101  func CreateUICAPair(
   102  	certsDir, caKeyPath string,
   103  	keySize int,
   104  	lifetime time.Duration,
   105  	allowKeyReuse bool,
   106  	overwrite bool,
   107  ) error {
   108  	return createCACertAndKey(certsDir, caKeyPath, UICAPem, keySize, lifetime, allowKeyReuse, overwrite)
   109  }
   110  
   111  // createCACertAndKey creates a CA key and a CA certificate.
   112  // If the certs directory does not exist, it is created.
   113  // If the key does not exist, it is created.
   114  // The certificate is written to the certs directory. If the file already exists,
   115  // we append the original certificates to the new certificate.
   116  //
   117  // The filename of the certificate file must be specified.
   118  // It should be one of:
   119  // - ca.crt: the general CA certificate
   120  // - ca-client.crt: the CA certificate to verify client certificates
   121  func createCACertAndKey(
   122  	certsDir, caKeyPath string,
   123  	caType PemUsage,
   124  	keySize int,
   125  	lifetime time.Duration,
   126  	allowKeyReuse bool,
   127  	overwrite bool,
   128  ) error {
   129  	if len(caKeyPath) == 0 {
   130  		return errors.New("the path to the CA key is required")
   131  	}
   132  	if len(certsDir) == 0 {
   133  		return errors.New("the path to the certs directory is required")
   134  	}
   135  	if caType != CAPem && caType != ClientCAPem && caType != UICAPem {
   136  		return fmt.Errorf("caType argument to createCACertAndKey must be one of CAPem (%d), ClientCAPem (%d), or UICAPem (%d), got: %d",
   137  			CAPem, ClientCAPem, UICAPem, caType)
   138  	}
   139  
   140  	// The certificate manager expands the env for the certs directory.
   141  	// For consistency, we need to do this for the key as well.
   142  	caKeyPath = os.ExpandEnv(caKeyPath)
   143  
   144  	// Create a certificate manager with "create dir if not exist".
   145  	cm, err := NewCertificateManagerFirstRun(certsDir)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	var key crypto.PrivateKey
   151  	if _, err := os.Stat(caKeyPath); err != nil {
   152  		if !os.IsNotExist(err) {
   153  			return errors.Errorf("could not stat CA key file %s: %v", caKeyPath, err)
   154  		}
   155  
   156  		// The key does not exist: create it.
   157  		key, err = rsa.GenerateKey(rand.Reader, keySize)
   158  		if err != nil {
   159  			return errors.Errorf("could not generate new CA key: %v", err)
   160  		}
   161  
   162  		// overwrite is not technically needed here, but use it in case something else created it.
   163  		if err := writeKeyToFile(caKeyPath, key, overwrite); err != nil {
   164  			return errors.Errorf("could not write CA key to file %s: %v", caKeyPath, err)
   165  		}
   166  
   167  		log.Infof(context.Background(), "Generated CA key %s", caKeyPath)
   168  	} else {
   169  		if !allowKeyReuse {
   170  			return errors.Errorf("CA key %s exists, but key reuse is disabled", caKeyPath)
   171  		}
   172  		// The key exists, parse it.
   173  		contents, err := ioutil.ReadFile(caKeyPath)
   174  		if err != nil {
   175  			return errors.Errorf("could not read CA key file %s: %v", caKeyPath, err)
   176  		}
   177  
   178  		key, err = PEMToPrivateKey(contents)
   179  		if err != nil {
   180  			return errors.Errorf("could not parse CA key file %s: %v", caKeyPath, err)
   181  		}
   182  
   183  		log.Infof(context.Background(), "Using CA key from file %s", caKeyPath)
   184  	}
   185  
   186  	// Generate certificate.
   187  	certContents, err := GenerateCA(key.(crypto.Signer), lifetime)
   188  	if err != nil {
   189  		return errors.Errorf("could not generate CA certificate: %v", err)
   190  	}
   191  
   192  	var certPath string
   193  	// We've already checked the caType value at the beginning of this function.
   194  	switch caType {
   195  	case CAPem:
   196  		certPath = cm.CACertPath()
   197  	case ClientCAPem:
   198  		certPath = cm.ClientCACertPath()
   199  	case UICAPem:
   200  		certPath = cm.UICACertPath()
   201  	}
   202  
   203  	var existingCertificates []*pem.Block
   204  	if _, err := os.Stat(certPath); err == nil {
   205  		// The cert file already exists, load certificates.
   206  		contents, err := ioutil.ReadFile(certPath)
   207  		if err != nil {
   208  			return errors.Errorf("could not read existing CA cert file %s: %v", certPath, err)
   209  		}
   210  
   211  		existingCertificates, err = PEMToCertificates(contents)
   212  		if err != nil {
   213  			return errors.Errorf("could not parse existing CA cert file %s: %v", certPath, err)
   214  		}
   215  		log.Infof(context.Background(), "Found %d certificates in %s",
   216  			len(existingCertificates), certPath)
   217  	} else if !os.IsNotExist(err) {
   218  		return errors.Errorf("could not stat CA cert file %s: %v", certPath, err)
   219  	}
   220  
   221  	// Always place the new certificate first.
   222  	certificates := []*pem.Block{{Type: "CERTIFICATE", Bytes: certContents}}
   223  	certificates = append(certificates, existingCertificates...)
   224  
   225  	if err := WritePEMToFile(certPath, certFileMode, overwrite, certificates...); err != nil {
   226  		return errors.Errorf("could not write CA certificate file %s: %v", certPath, err)
   227  	}
   228  
   229  	log.Infof(context.Background(), "Wrote %d certificates to %s", len(certificates), certPath)
   230  
   231  	return nil
   232  }
   233  
   234  // CreateNodePair creates a node key and certificate.
   235  // The CA cert and key must load properly. If multiple certificates
   236  // exist in the CA cert, the first one is used.
   237  func CreateNodePair(
   238  	certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, hosts []string,
   239  ) error {
   240  	if len(caKeyPath) == 0 {
   241  		return errors.New("the path to the CA key is required")
   242  	}
   243  	if len(certsDir) == 0 {
   244  		return errors.New("the path to the certs directory is required")
   245  	}
   246  
   247  	// The certificate manager expands the env for the certs directory.
   248  	// For consistency, we need to do this for the key as well.
   249  	caKeyPath = os.ExpandEnv(caKeyPath)
   250  
   251  	// Create a certificate manager with "create dir if not exist".
   252  	cm, err := NewCertificateManagerFirstRun(certsDir)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	// Load the CA pair.
   258  	caCert, caPrivateKey, err := loadCACertAndKey(cm.CACertPath(), caKeyPath)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	// Generate certificates and keys.
   264  	nodeKey, err := rsa.GenerateKey(rand.Reader, keySize)
   265  	if err != nil {
   266  		return errors.Errorf("could not generate new node key: %v", err)
   267  	}
   268  
   269  	// Allow control of the principal to place in the cert via an env var. This
   270  	// is intended for testing purposes only.
   271  	nodeUser := envutil.EnvOrDefaultString("COCKROACH_CERT_NODE_USER", NodeUser)
   272  	nodeCert, err := GenerateServerCert(caCert, caPrivateKey,
   273  		nodeKey.Public(), lifetime, nodeUser, hosts)
   274  	if err != nil {
   275  		return errors.Errorf("error creating node server certificate and key: %s", err)
   276  	}
   277  
   278  	certPath := cm.NodeCertPath()
   279  	if err := writeCertificateToFile(certPath, nodeCert, overwrite); err != nil {
   280  		return errors.Errorf("error writing node server certificate to %s: %v", certPath, err)
   281  	}
   282  	log.Infof(context.Background(), "Generated node certificate: %s", certPath)
   283  
   284  	keyPath := cm.NodeKeyPath()
   285  	if err := writeKeyToFile(keyPath, nodeKey, overwrite); err != nil {
   286  		return errors.Errorf("error writing node server key to %s: %v", keyPath, err)
   287  	}
   288  	log.Infof(context.Background(), "Generated node key: %s", keyPath)
   289  
   290  	return nil
   291  }
   292  
   293  // CreateUIPair creates a UI certificate and key using the UI CA.
   294  // The CA cert and key must load properly. If multiple certificates
   295  // exist in the CA cert, the first one is used.
   296  func CreateUIPair(
   297  	certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, hosts []string,
   298  ) error {
   299  	if len(caKeyPath) == 0 {
   300  		return errors.New("the path to the CA key is required")
   301  	}
   302  	if len(certsDir) == 0 {
   303  		return errors.New("the path to the certs directory is required")
   304  	}
   305  
   306  	// The certificate manager expands the env for the certs directory.
   307  	// For consistency, we need to do this for the key as well.
   308  	caKeyPath = os.ExpandEnv(caKeyPath)
   309  
   310  	// Create a certificate manager with "create dir if not exist".
   311  	cm, err := NewCertificateManagerFirstRun(certsDir)
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	// Load the CA pair.
   317  	caCert, caPrivateKey, err := loadCACertAndKey(cm.UICACertPath(), caKeyPath)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	// Generate certificates and keys.
   323  	uiKey, err := rsa.GenerateKey(rand.Reader, keySize)
   324  	if err != nil {
   325  		return errors.Errorf("could not generate new UI key: %v", err)
   326  	}
   327  
   328  	uiCert, err := GenerateUIServerCert(caCert, caPrivateKey, uiKey.Public(), lifetime, hosts)
   329  	if err != nil {
   330  		return errors.Errorf("error creating UI server certificate and key: %s", err)
   331  	}
   332  
   333  	certPath := cm.UICertPath()
   334  	if err := writeCertificateToFile(certPath, uiCert, overwrite); err != nil {
   335  		return errors.Errorf("error writing UI server certificate to %s: %v", certPath, err)
   336  	}
   337  	log.Infof(context.Background(), "Generated UI certificate: %s", certPath)
   338  
   339  	keyPath := cm.UIKeyPath()
   340  	if err := writeKeyToFile(keyPath, uiKey, overwrite); err != nil {
   341  		return errors.Errorf("error writing UI server key to %s: %v", keyPath, err)
   342  	}
   343  	log.Infof(context.Background(), "Generated UI key: %s", keyPath)
   344  
   345  	return nil
   346  }
   347  
   348  // CreateClientPair creates a node key and certificate.
   349  // The CA cert and key must load properly. If multiple certificates
   350  // exist in the CA cert, the first one is used.
   351  // If a client CA exists, this is used instead.
   352  // If wantPKCS8Key is true, the private key in PKCS#8 encoding is written as well.
   353  func CreateClientPair(
   354  	certsDir, caKeyPath string,
   355  	keySize int,
   356  	lifetime time.Duration,
   357  	overwrite bool,
   358  	user string,
   359  	wantPKCS8Key bool,
   360  ) error {
   361  	if len(caKeyPath) == 0 {
   362  		return errors.New("the path to the CA key is required")
   363  	}
   364  	if len(certsDir) == 0 {
   365  		return errors.New("the path to the certs directory is required")
   366  	}
   367  
   368  	// The certificate manager expands the env for the certs directory.
   369  	// For consistency, we need to do this for the key as well.
   370  	caKeyPath = os.ExpandEnv(caKeyPath)
   371  
   372  	// Create a certificate manager with "create dir if not exist".
   373  	cm, err := NewCertificateManagerFirstRun(certsDir)
   374  	if err != nil {
   375  		return err
   376  	}
   377  
   378  	var caCertPath string
   379  	// Check to see if we are using a client CA.
   380  	// We only check for its presence, not whether it has errors.
   381  	if cm.ClientCACert() != nil {
   382  		caCertPath = cm.ClientCACertPath()
   383  	} else {
   384  		caCertPath = cm.CACertPath()
   385  	}
   386  
   387  	// Load the CA pair.
   388  	caCert, caPrivateKey, err := loadCACertAndKey(caCertPath, caKeyPath)
   389  	if err != nil {
   390  		return err
   391  	}
   392  
   393  	// Generate certificates and keys.
   394  	clientKey, err := rsa.GenerateKey(rand.Reader, keySize)
   395  	if err != nil {
   396  		return errors.Errorf("could not generate new client key: %v", err)
   397  	}
   398  
   399  	clientCert, err := GenerateClientCert(caCert, caPrivateKey, clientKey.Public(), lifetime, user)
   400  	if err != nil {
   401  		return errors.Errorf("error creating client certificate and key: %s", err)
   402  	}
   403  
   404  	certPath := cm.ClientCertPath(user)
   405  	if err := writeCertificateToFile(certPath, clientCert, overwrite); err != nil {
   406  		return errors.Errorf("error writing client certificate to %s: %v", certPath, err)
   407  	}
   408  	log.Infof(context.Background(), "Generated client certificate: %s", certPath)
   409  
   410  	keyPath := cm.ClientKeyPath(user)
   411  	if err := writeKeyToFile(keyPath, clientKey, overwrite); err != nil {
   412  		return errors.Errorf("error writing client key to %s: %v", keyPath, err)
   413  	}
   414  	log.Infof(context.Background(), "Generated client key: %s", keyPath)
   415  
   416  	if wantPKCS8Key {
   417  		pkcs8KeyPath := keyPath + ".pk8"
   418  		if err := writePKCS8KeyToFile(pkcs8KeyPath, clientKey, overwrite); err != nil {
   419  			return errors.Errorf("error writing client PKCS8 key to %s: %v", pkcs8KeyPath, err)
   420  		}
   421  		log.Infof(context.Background(), "Generated PKCS8 client key: %s", pkcs8KeyPath)
   422  	}
   423  
   424  	return nil
   425  }
   426  
   427  // PEMContentsToX509 takes raw pem-encoded contents and attempts to parse into
   428  // x509.Certificate objects.
   429  func PEMContentsToX509(contents []byte) ([]*x509.Certificate, error) {
   430  	derCerts, err := PEMToCertificates(contents)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  
   435  	certs := make([]*x509.Certificate, len(derCerts))
   436  	for i, c := range derCerts {
   437  		x509Cert, err := x509.ParseCertificate(c.Bytes)
   438  		if err != nil {
   439  			return nil, err
   440  		}
   441  
   442  		certs[i] = x509Cert
   443  	}
   444  
   445  	return certs, nil
   446  }