github.com/grailbio/base@v0.0.11/security/tls/certificateauthority/tls.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package certificateauthority implements an x509 certificate authority.
     6  package certificateauthority
     7  
     8  import (
     9  	"crypto/rand"
    10  	"crypto/rsa"
    11  	"crypto/x509"
    12  	"crypto/x509/pkix"
    13  	"encoding/pem"
    14  	"errors"
    15  	"math/big"
    16  	"net"
    17  	"time"
    18  
    19  	"github.com/grailbio/base/security/keycrypt"
    20  )
    21  
    22  // CertificateAuthority is a x509 certificate authority.
    23  type CertificateAuthority struct {
    24  	// The amount of allowable clock drift between the systems between
    25  	// which certificates are exchanged.
    26  	DriftMargin time.Duration
    27  	// The keycrypt secret that contains the PEM-encoded signing
    28  	// certificate and public key.
    29  	Signer keycrypt.Secret
    30  	// The x509 certificate. Populated by Init().
    31  	Cert *x509.Certificate
    32  
    33  	key *rsa.PrivateKey
    34  }
    35  
    36  // Init initializes the certificate authority. Init extracts the the
    37  // authority certificate and private key from ca.Signer.
    38  func (ca *CertificateAuthority) Init() error {
    39  	pemBlock, err := ca.Signer.Get()
    40  	if err != nil {
    41  		return err
    42  	}
    43  	for {
    44  		var derBlock *pem.Block
    45  		derBlock, pemBlock = pem.Decode(pemBlock)
    46  		if derBlock == nil {
    47  			break
    48  		}
    49  		switch derBlock.Type {
    50  		case "CERTIFICATE":
    51  			ca.Cert, err = x509.ParseCertificate(derBlock.Bytes)
    52  			if err != nil {
    53  				return err
    54  			}
    55  		case "RSA PRIVATE KEY":
    56  			ca.key, err = x509.ParsePKCS1PrivateKey(derBlock.Bytes)
    57  			if err != nil {
    58  				return err
    59  			}
    60  		}
    61  	}
    62  	if ca.Cert == nil || ca.key == nil {
    63  		return errors.New("incomplete certificate")
    64  	}
    65  	return nil
    66  }
    67  
    68  // Issue a new certificate with both client and server authentication
    69  // key usage extensions.
    70  func (ca CertificateAuthority) Issue(commonName string, ttl time.Duration, ips []net.IP, dnss []string) ([]byte, *rsa.PrivateKey, error) {
    71  	return ca.IssueWithKeyUsage(commonName, ttl, ips, dnss, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
    72  }
    73  
    74  // IssueWithKeyUsage a new certificate with the indicated key usage extensions.
    75  func (ca CertificateAuthority) IssueWithKeyUsage(commonName string, ttl time.Duration, ips []net.IP, dnss []string, keyUsage []x509.ExtKeyUsage) ([]byte, *rsa.PrivateKey, error) {
    76  	maxSerial := new(big.Int).Lsh(big.NewInt(1), 128)
    77  	serial, err := rand.Int(rand.Reader, maxSerial)
    78  	if err != nil {
    79  		return nil, nil, err
    80  	}
    81  	now := time.Now().Add(-ca.DriftMargin)
    82  	template := x509.Certificate{
    83  		SerialNumber: serial,
    84  		Subject: pkix.Name{
    85  			CommonName: commonName,
    86  		},
    87  		NotBefore:             now,
    88  		NotAfter:              now.Add(ca.DriftMargin + ttl),
    89  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
    90  		ExtKeyUsage:           keyUsage,
    91  		BasicConstraintsValid: true,
    92  	}
    93  	template.IPAddresses = append(template.IPAddresses, ips...)
    94  	template.DNSNames = append(template.DNSNames, dnss...)
    95  	key, err := rsa.GenerateKey(rand.Reader, 2048)
    96  	if err != nil {
    97  		return nil, nil, err
    98  	}
    99  	cert, err := x509.CreateCertificate(rand.Reader, &template, ca.Cert, &key.PublicKey, ca.key)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	return cert, key, nil
   104  }