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