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 }