github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/x509.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  	"crypto"
    15  	"crypto/rand"
    16  	"crypto/x509"
    17  	"crypto/x509/pkix"
    18  	"math/big"
    19  	"net"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  // Utility to generate x509 certificates, both CA and not.
    27  // This is mostly based on http://golang.org/src/crypto/tls/generate_cert.go
    28  // Most fields and settings are hard-coded. TODO(marc): allow customization.
    29  
    30  const (
    31  	// Make certs valid a day before to handle clock issues, specifically
    32  	// boot2docker: https://github.com/boot2docker/boot2docker/issues/69
    33  	validFrom     = -time.Hour * 24
    34  	maxPathLength = 1
    35  	caCommonName  = "Cockroach CA"
    36  )
    37  
    38  // newTemplate returns a partially-filled template.
    39  // It should be further populated based on whether the cert is for a CA or node.
    40  func newTemplate(commonName string, lifetime time.Duration) (*x509.Certificate, error) {
    41  	// Generate a random serial number.
    42  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    43  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	now := timeutil.Now()
    49  	notBefore := now.Add(validFrom)
    50  	notAfter := now.Add(lifetime)
    51  
    52  	cert := &x509.Certificate{
    53  		SerialNumber: serialNumber,
    54  		Subject: pkix.Name{
    55  			Organization: []string{"Cockroach"},
    56  			CommonName:   commonName,
    57  		},
    58  		NotBefore: notBefore,
    59  		NotAfter:  notAfter,
    60  
    61  		KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
    62  	}
    63  
    64  	return cert, nil
    65  }
    66  
    67  // GenerateCA generates a CA certificate and signs it using the signer (a private key).
    68  // It returns the DER-encoded certificate.
    69  func GenerateCA(signer crypto.Signer, lifetime time.Duration) ([]byte, error) {
    70  	template, err := newTemplate(caCommonName, lifetime)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// Set CA-specific fields.
    76  	template.BasicConstraintsValid = true
    77  	template.IsCA = true
    78  	template.MaxPathLen = maxPathLength
    79  	template.KeyUsage |= x509.KeyUsageCertSign
    80  	template.KeyUsage |= x509.KeyUsageContentCommitment
    81  
    82  	certBytes, err := x509.CreateCertificate(
    83  		rand.Reader,
    84  		template,
    85  		template,
    86  		signer.Public(),
    87  		signer)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return certBytes, nil
    93  }
    94  
    95  func checkLifetimeAgainstCA(cert, ca *x509.Certificate) error {
    96  	if ca.NotAfter.After(cert.NotAfter) || ca.NotAfter.Equal(cert.NotAfter) {
    97  		return nil
    98  	}
    99  
   100  	now := timeutil.Now()
   101  	// Truncate the lifetime to round hours, the maximum "pretty" duration.
   102  	niceCALifetime := ca.NotAfter.Sub(now).Hours()
   103  	niceCertLifetime := cert.NotAfter.Sub(now).Hours()
   104  	return errors.Errorf("CA lifetime is %fh, shorter than the requested %fh. "+
   105  		"Renew CA certificate, or rerun with --lifetime=%dh for a shorter duration.",
   106  		niceCALifetime, niceCertLifetime, int64(niceCALifetime))
   107  }
   108  
   109  // GenerateServerCert generates a server certificate and returns the cert bytes.
   110  // Takes in the CA cert and private key, the node public key, the certificate lifetime,
   111  // and the list of hosts/ip addresses this certificate applies to.
   112  func GenerateServerCert(
   113  	caCert *x509.Certificate,
   114  	caPrivateKey crypto.PrivateKey,
   115  	nodePublicKey crypto.PublicKey,
   116  	lifetime time.Duration,
   117  	user string,
   118  	hosts []string,
   119  ) ([]byte, error) {
   120  	// Create template for user.
   121  	template, err := newTemplate(user, lifetime)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// Don't issue certificates that outlast the CA cert.
   127  	if err := checkLifetimeAgainstCA(template, caCert); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Both server and client authentication are allowed (for inter-node RPC).
   132  	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
   133  	for _, h := range hosts {
   134  		if ip := net.ParseIP(h); ip != nil {
   135  			template.IPAddresses = append(template.IPAddresses, ip)
   136  		} else {
   137  			template.DNSNames = append(template.DNSNames, h)
   138  		}
   139  	}
   140  
   141  	certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, nodePublicKey, caPrivateKey)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return certBytes, nil
   147  }
   148  
   149  // GenerateUIServerCert generates a server certificate for the Admin UI and returns the cert bytes.
   150  // Takes in the CA cert and private key, the UI cert public key, the certificate lifetime,
   151  // and the list of hosts/ip addresses this certificate applies to.
   152  func GenerateUIServerCert(
   153  	caCert *x509.Certificate,
   154  	caPrivateKey crypto.PrivateKey,
   155  	certPublicKey crypto.PublicKey,
   156  	lifetime time.Duration,
   157  	hosts []string,
   158  ) ([]byte, error) {
   159  	// Use the first host as the CN. We still place all in the alternative subject name.
   160  	template, err := newTemplate(hosts[0], lifetime)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	// Don't issue certificates that outlast the CA cert.
   166  	if err := checkLifetimeAgainstCA(template, caCert); err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	// Only server authentication is allowed.
   171  	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
   172  	for _, h := range hosts {
   173  		if ip := net.ParseIP(h); ip != nil {
   174  			template.IPAddresses = append(template.IPAddresses, ip)
   175  		} else {
   176  			template.DNSNames = append(template.DNSNames, h)
   177  		}
   178  	}
   179  
   180  	certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, certPublicKey, caPrivateKey)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	return certBytes, nil
   186  }
   187  
   188  // GenerateClientCert generates a client certificate and returns the cert bytes.
   189  // Takes in the CA cert and private key, the client public key, the certificate lifetime,
   190  // and the username.
   191  func GenerateClientCert(
   192  	caCert *x509.Certificate,
   193  	caPrivateKey crypto.PrivateKey,
   194  	clientPublicKey crypto.PublicKey,
   195  	lifetime time.Duration,
   196  	user string,
   197  ) ([]byte, error) {
   198  
   199  	// TODO(marc): should we add extra checks?
   200  	if len(user) == 0 {
   201  		return nil, errors.Errorf("user cannot be empty")
   202  	}
   203  
   204  	// Create template for "user".
   205  	template, err := newTemplate(user, lifetime)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	// Don't issue certificates that outlast the CA cert.
   211  	if err := checkLifetimeAgainstCA(template, caCert); err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	// Set client-specific fields.
   216  	// Client authentication only.
   217  	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
   218  
   219  	certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, clientPublicKey, caPrivateKey)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	return certBytes, nil
   225  }