github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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().Add(-5 * time.Minute), 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().Add(-5 * time.Minute), 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 }