github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 = 1024
    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 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  	now := time.Now()
    91  	template := &x509.Certificate{
    92  		SerialNumber: new(big.Int),
    93  		Subject: pkix.Name{
    94  			CommonName:   fmt.Sprintf("juju-generated CA for environment %q", envName),
    95  			Organization: []string{"juju"},
    96  		},
    97  		NotBefore:             now.UTC().AddDate(0, 0, -7),
    98  		NotAfter:              expiry.UTC(),
    99  		SubjectKeyId:          bigIntHash(key.N),
   100  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   101  		IsCA:                  true,
   102  		MaxPathLen:            0, // Disallow delegation for now.
   103  		BasicConstraintsValid: true,
   104  	}
   105  	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
   106  	if err != nil {
   107  		return "", "", fmt.Errorf("canot create certificate: %v", err)
   108  	}
   109  	certPEMData := pem.EncodeToMemory(&pem.Block{
   110  		Type:  "CERTIFICATE",
   111  		Bytes: certDER,
   112  	})
   113  	keyPEMData := pem.EncodeToMemory(&pem.Block{
   114  		Type:  "RSA PRIVATE KEY",
   115  		Bytes: x509.MarshalPKCS1PrivateKey(key),
   116  	})
   117  	return string(certPEMData), string(keyPEMData), nil
   118  }
   119  
   120  // NewServer generates a certificate/key pair suitable for use by a server.
   121  func NewServer(caCertPEM, caKeyPEM string, expiry time.Time, hostnames []string) (certPEM, keyPEM string, err error) {
   122  	return newLeaf(caCertPEM, caKeyPEM, expiry, hostnames, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
   123  }
   124  
   125  // NewClient generates a certificate/key pair suitable for client authentication.
   126  func NewClient(caCertPEM, caKeyPEM string, expiry time.Time) (certPEM, keyPEM string, err error) {
   127  	return newLeaf(caCertPEM, caKeyPEM, expiry, nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
   128  }
   129  
   130  // newLeaf generates a certificate/key pair suitable for use by a leaf node.
   131  func newLeaf(caCertPEM, caKeyPEM string, expiry time.Time, hostnames []string, extKeyUsage []x509.ExtKeyUsage) (certPEM, keyPEM string, err error) {
   132  	tlsCert, err := tls.X509KeyPair([]byte(caCertPEM), []byte(caKeyPEM))
   133  	if err != nil {
   134  		return "", "", err
   135  	}
   136  	if len(tlsCert.Certificate) != 1 {
   137  		return "", "", fmt.Errorf("more than one certificate for CA")
   138  	}
   139  	caCert, err := x509.ParseCertificate(tlsCert.Certificate[0])
   140  	if err != nil {
   141  		return "", "", err
   142  	}
   143  	if !caCert.BasicConstraintsValid || !caCert.IsCA {
   144  		return "", "", fmt.Errorf("CA certificate is not a valid CA")
   145  	}
   146  	caKey, ok := tlsCert.PrivateKey.(*rsa.PrivateKey)
   147  	if !ok {
   148  		return "", "", fmt.Errorf("CA private key has unexpected type %T", tlsCert.PrivateKey)
   149  	}
   150  	key, err := rsa.GenerateKey(rand.Reader, KeyBits)
   151  	if err != nil {
   152  		return "", "", fmt.Errorf("cannot generate key: %v", err)
   153  	}
   154  	now := time.Now()
   155  	template := &x509.Certificate{
   156  		SerialNumber: new(big.Int),
   157  		Subject: pkix.Name{
   158  			// This won't match host names with dots. The hostname
   159  			// is hardcoded when connecting to avoid the issue.
   160  			CommonName:   "*",
   161  			Organization: []string{"juju"},
   162  		},
   163  		NotBefore: now.UTC().AddDate(0, 0, -7),
   164  		NotAfter:  expiry.UTC(),
   165  
   166  		SubjectKeyId: bigIntHash(key.N),
   167  		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement,
   168  		ExtKeyUsage:  extKeyUsage,
   169  	}
   170  	for _, hostname := range hostnames {
   171  		if ip := net.ParseIP(hostname); ip != nil {
   172  			template.IPAddresses = append(template.IPAddresses, ip)
   173  		} else {
   174  			template.DNSNames = append(template.DNSNames, hostname)
   175  		}
   176  	}
   177  	certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
   178  	if err != nil {
   179  		return "", "", err
   180  	}
   181  	certPEMData := pem.EncodeToMemory(&pem.Block{
   182  		Type:  "CERTIFICATE",
   183  		Bytes: certDER,
   184  	})
   185  	keyPEMData := pem.EncodeToMemory(&pem.Block{
   186  		Type:  "RSA PRIVATE KEY",
   187  		Bytes: x509.MarshalPKCS1PrivateKey(key),
   188  	})
   189  	return string(certPEMData), string(keyPEMData), nil
   190  }
   191  
   192  func bigIntHash(n *big.Int) []byte {
   193  	h := sha1.New()
   194  	h.Write(n.Bytes())
   195  	return h.Sum(nil)
   196  }