github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 }