github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cert/cert.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cert
     5  
     6  import (
     7  	"crypto/rand"
     8  	"crypto/rsa"
     9  	"crypto/sha1"
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"crypto/x509/pkix"
    13  	"encoding/pem"
    14  	"fmt"
    15  	"math/big"
    16  	"net"
    17  	"time"
    18  
    19  	"github.com/juju/errors"
    20  )
    21  
    22  var KeyBits = 2048
    23  
    24  // ParseCert parses the given PEM-formatted X509 certificate.
    25  func ParseCert(certPEM string) (*x509.Certificate, error) {
    26  	certPEMData := []byte(certPEM)
    27  	for len(certPEMData) > 0 {
    28  		var certBlock *pem.Block
    29  		certBlock, certPEMData = pem.Decode(certPEMData)
    30  		if certBlock == nil {
    31  			break
    32  		}
    33  		if certBlock.Type == "CERTIFICATE" {
    34  			cert, err := x509.ParseCertificate(certBlock.Bytes)
    35  			return cert, err
    36  		}
    37  	}
    38  	return nil, errors.New("no certificates found")
    39  }
    40  
    41  // ParseCertAndKey parses the given PEM-formatted X509 certificate
    42  // and RSA private key.
    43  func ParseCertAndKey(certPEM, keyPEM string) (*x509.Certificate, *rsa.PrivateKey, error) {
    44  	tlsCert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
    45  	if err != nil {
    46  		return nil, nil, err
    47  	}
    48  
    49  	cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
    50  	if err != nil {
    51  		return nil, nil, err
    52  	}
    53  
    54  	key, ok := tlsCert.PrivateKey.(*rsa.PrivateKey)
    55  	if !ok {
    56  		return nil, nil, fmt.Errorf("private key with unexpected type %T", key)
    57  	}
    58  	return cert, key, nil
    59  }
    60  
    61  // Verify verifies that the given server certificate is valid with
    62  // respect to the given CA certificate at the given time.
    63  func Verify(srvCertPEM, caCertPEM string, when time.Time) error {
    64  	caCert, err := ParseCert(caCertPEM)
    65  	if err != nil {
    66  		return errors.Annotate(err, "cannot parse CA certificate")
    67  	}
    68  	srvCert, err := ParseCert(srvCertPEM)
    69  	if err != nil {
    70  		return errors.Annotate(err, "cannot parse server certificate")
    71  	}
    72  	pool := x509.NewCertPool()
    73  	pool.AddCert(caCert)
    74  	opts := x509.VerifyOptions{
    75  		DNSName:     "anyServer",
    76  		Roots:       pool,
    77  		CurrentTime: when,
    78  	}
    79  	_, err = srvCert.Verify(opts)
    80  	return err
    81  }
    82  
    83  // NewCA generates a CA certificate/key pair suitable for signing server
    84  // keys for an environment with the given name.
    85  func NewCA(envName, UUID string, expiry time.Time) (certPEM, keyPEM string, err error) {
    86  	key, err := rsa.GenerateKey(rand.Reader, KeyBits)
    87  	if err != nil {
    88  		return "", "", err
    89  	}
    90  	// TODO(perrito666) 2016-05-02 lp:1558657
    91  	now := time.Now()
    92  
    93  	serialNumber, err := newSerialNumber()
    94  	if err != nil {
    95  		return "", "", errors.Trace(err)
    96  	}
    97  
    98  	template := &x509.Certificate{
    99  		SerialNumber: serialNumber,
   100  		Subject: pkix.Name{
   101  			CommonName:   fmt.Sprintf("juju-generated CA for model %q", envName),
   102  			Organization: []string{"juju"},
   103  			SerialNumber: UUID,
   104  		},
   105  		NotBefore:             now.UTC().AddDate(0, 0, -7),
   106  		NotAfter:              expiry.UTC(),
   107  		SubjectKeyId:          bigIntHash(key.N),
   108  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   109  		IsCA:                  true,
   110  		MaxPathLen:            0, // Disallow delegation for now.
   111  		BasicConstraintsValid: true,
   112  	}
   113  	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
   114  	if err != nil {
   115  		return "", "", fmt.Errorf("cannot create certificate: %v", err)
   116  	}
   117  	certPEMData := pem.EncodeToMemory(&pem.Block{
   118  		Type:  "CERTIFICATE",
   119  		Bytes: certDER,
   120  	})
   121  	keyPEMData := pem.EncodeToMemory(&pem.Block{
   122  		Type:  "RSA PRIVATE KEY",
   123  		Bytes: x509.MarshalPKCS1PrivateKey(key),
   124  	})
   125  	return string(certPEMData), string(keyPEMData), nil
   126  }
   127  
   128  // newSerialNumber returns a new random serial number suitable
   129  // for use in a certificate.
   130  func newSerialNumber() (*big.Int, error) {
   131  	// A serial number can be up to 20 octets in size.
   132  	// https://tools.ietf.org/html/rfc5280#section-4.1.2.2
   133  	n, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20))
   134  	if err != nil {
   135  		return nil, errors.Annotatef(err, "failed to generate serial number")
   136  	}
   137  	return n, nil
   138  }
   139  
   140  // NewDefaultServer generates a certificate/key pair suitable for use by a server, with an
   141  // expiry time of 10 years.
   142  func NewDefaultServer(caCertPEM, caKeyPEM string, hostnames []string) (certPEM, keyPEM string, err error) {
   143  	// TODO(perrito666) 2016-05-02 lp:1558657
   144  	expiry := time.Now().UTC().AddDate(10, 0, 0)
   145  	return newLeaf(caCertPEM, caKeyPEM, expiry, hostnames, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
   146  }
   147  
   148  // NewServer generates a certificate/key pair suitable for use by a server.
   149  func NewServer(caCertPEM, caKeyPEM string, expiry time.Time, hostnames []string) (certPEM, keyPEM string, err error) {
   150  	return newLeaf(caCertPEM, caKeyPEM, expiry, hostnames, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
   151  }
   152  
   153  // newLeaf generates a certificate/key pair suitable for use by a leaf node.
   154  func newLeaf(caCertPEM, caKeyPEM string, expiry time.Time, hostnames []string, extKeyUsage []x509.ExtKeyUsage) (certPEM, keyPEM string, err error) {
   155  	tlsCert, err := tls.X509KeyPair([]byte(caCertPEM), []byte(caKeyPEM))
   156  	if err != nil {
   157  		return "", "", errors.Trace(err)
   158  	}
   159  	if len(tlsCert.Certificate) != 1 {
   160  		return "", "", fmt.Errorf("more than one certificate for CA")
   161  	}
   162  	caCert, err := x509.ParseCertificate(tlsCert.Certificate[0])
   163  	if err != nil {
   164  		return "", "", errors.Trace(err)
   165  	}
   166  	if !caCert.BasicConstraintsValid || !caCert.IsCA {
   167  		return "", "", errors.Errorf("CA certificate is not a valid CA")
   168  	}
   169  	caKey, ok := tlsCert.PrivateKey.(*rsa.PrivateKey)
   170  	if !ok {
   171  		return "", "", errors.Errorf("CA private key has unexpected type %T", tlsCert.PrivateKey)
   172  	}
   173  	key, err := rsa.GenerateKey(rand.Reader, KeyBits)
   174  	if err != nil {
   175  		return "", "", errors.Errorf("cannot generate key: %v", err)
   176  	}
   177  
   178  	serialNumber, err := newSerialNumber()
   179  	if err != nil {
   180  		return "", "", errors.Trace(err)
   181  	}
   182  	// TODO(perrito666) 2016-05-02 lp:1558657
   183  	now := time.Now()
   184  	template := &x509.Certificate{
   185  		SerialNumber: serialNumber,
   186  		Subject: pkix.Name{
   187  			// This won't match host names with dots. The hostname
   188  			// is hardcoded when connecting to avoid the issue.
   189  			CommonName:   "*",
   190  			Organization: []string{"juju"},
   191  		},
   192  		NotBefore: now.UTC().AddDate(0, 0, -7),
   193  		NotAfter:  expiry.UTC(),
   194  
   195  		SubjectKeyId: bigIntHash(key.N),
   196  		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement,
   197  		ExtKeyUsage:  extKeyUsage,
   198  	}
   199  	for _, hostname := range hostnames {
   200  		if ip := net.ParseIP(hostname); ip != nil {
   201  			template.IPAddresses = append(template.IPAddresses, ip)
   202  		} else {
   203  			template.DNSNames = append(template.DNSNames, hostname)
   204  		}
   205  	}
   206  	certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
   207  	if err != nil {
   208  		return "", "", err
   209  	}
   210  	certPEMData := pem.EncodeToMemory(&pem.Block{
   211  		Type:  "CERTIFICATE",
   212  		Bytes: certDER,
   213  	})
   214  	keyPEMData := pem.EncodeToMemory(&pem.Block{
   215  		Type:  "RSA PRIVATE KEY",
   216  		Bytes: x509.MarshalPKCS1PrivateKey(key),
   217  	})
   218  	return string(certPEMData), string(keyPEMData), nil
   219  }
   220  
   221  func bigIntHash(n *big.Int) []byte {
   222  	h := sha1.New()
   223  	h.Write(n.Bytes())
   224  	return h.Sum(nil)
   225  }