github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/x509.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package security 12 13 import ( 14 "crypto" 15 "crypto/rand" 16 "crypto/x509" 17 "crypto/x509/pkix" 18 "math/big" 19 "net" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 23 "github.com/cockroachdb/errors" 24 ) 25 26 // Utility to generate x509 certificates, both CA and not. 27 // This is mostly based on http://golang.org/src/crypto/tls/generate_cert.go 28 // Most fields and settings are hard-coded. TODO(marc): allow customization. 29 30 const ( 31 // Make certs valid a day before to handle clock issues, specifically 32 // boot2docker: https://github.com/boot2docker/boot2docker/issues/69 33 validFrom = -time.Hour * 24 34 maxPathLength = 1 35 caCommonName = "Cockroach CA" 36 ) 37 38 // newTemplate returns a partially-filled template. 39 // It should be further populated based on whether the cert is for a CA or node. 40 func newTemplate(commonName string, lifetime time.Duration) (*x509.Certificate, error) { 41 // Generate a random serial number. 42 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 43 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 44 if err != nil { 45 return nil, err 46 } 47 48 now := timeutil.Now() 49 notBefore := now.Add(validFrom) 50 notAfter := now.Add(lifetime) 51 52 cert := &x509.Certificate{ 53 SerialNumber: serialNumber, 54 Subject: pkix.Name{ 55 Organization: []string{"Cockroach"}, 56 CommonName: commonName, 57 }, 58 NotBefore: notBefore, 59 NotAfter: notAfter, 60 61 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 62 } 63 64 return cert, nil 65 } 66 67 // GenerateCA generates a CA certificate and signs it using the signer (a private key). 68 // It returns the DER-encoded certificate. 69 func GenerateCA(signer crypto.Signer, lifetime time.Duration) ([]byte, error) { 70 template, err := newTemplate(caCommonName, lifetime) 71 if err != nil { 72 return nil, err 73 } 74 75 // Set CA-specific fields. 76 template.BasicConstraintsValid = true 77 template.IsCA = true 78 template.MaxPathLen = maxPathLength 79 template.KeyUsage |= x509.KeyUsageCertSign 80 template.KeyUsage |= x509.KeyUsageContentCommitment 81 82 certBytes, err := x509.CreateCertificate( 83 rand.Reader, 84 template, 85 template, 86 signer.Public(), 87 signer) 88 if err != nil { 89 return nil, err 90 } 91 92 return certBytes, nil 93 } 94 95 func checkLifetimeAgainstCA(cert, ca *x509.Certificate) error { 96 if ca.NotAfter.After(cert.NotAfter) || ca.NotAfter.Equal(cert.NotAfter) { 97 return nil 98 } 99 100 now := timeutil.Now() 101 // Truncate the lifetime to round hours, the maximum "pretty" duration. 102 niceCALifetime := ca.NotAfter.Sub(now).Hours() 103 niceCertLifetime := cert.NotAfter.Sub(now).Hours() 104 return errors.Errorf("CA lifetime is %fh, shorter than the requested %fh. "+ 105 "Renew CA certificate, or rerun with --lifetime=%dh for a shorter duration.", 106 niceCALifetime, niceCertLifetime, int64(niceCALifetime)) 107 } 108 109 // GenerateServerCert generates a server certificate and returns the cert bytes. 110 // Takes in the CA cert and private key, the node public key, the certificate lifetime, 111 // and the list of hosts/ip addresses this certificate applies to. 112 func GenerateServerCert( 113 caCert *x509.Certificate, 114 caPrivateKey crypto.PrivateKey, 115 nodePublicKey crypto.PublicKey, 116 lifetime time.Duration, 117 user string, 118 hosts []string, 119 ) ([]byte, error) { 120 // Create template for user. 121 template, err := newTemplate(user, lifetime) 122 if err != nil { 123 return nil, err 124 } 125 126 // Don't issue certificates that outlast the CA cert. 127 if err := checkLifetimeAgainstCA(template, caCert); err != nil { 128 return nil, err 129 } 130 131 // Both server and client authentication are allowed (for inter-node RPC). 132 template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} 133 for _, h := range hosts { 134 if ip := net.ParseIP(h); ip != nil { 135 template.IPAddresses = append(template.IPAddresses, ip) 136 } else { 137 template.DNSNames = append(template.DNSNames, h) 138 } 139 } 140 141 certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, nodePublicKey, caPrivateKey) 142 if err != nil { 143 return nil, err 144 } 145 146 return certBytes, nil 147 } 148 149 // GenerateUIServerCert generates a server certificate for the Admin UI and returns the cert bytes. 150 // Takes in the CA cert and private key, the UI cert public key, the certificate lifetime, 151 // and the list of hosts/ip addresses this certificate applies to. 152 func GenerateUIServerCert( 153 caCert *x509.Certificate, 154 caPrivateKey crypto.PrivateKey, 155 certPublicKey crypto.PublicKey, 156 lifetime time.Duration, 157 hosts []string, 158 ) ([]byte, error) { 159 // Use the first host as the CN. We still place all in the alternative subject name. 160 template, err := newTemplate(hosts[0], lifetime) 161 if err != nil { 162 return nil, err 163 } 164 165 // Don't issue certificates that outlast the CA cert. 166 if err := checkLifetimeAgainstCA(template, caCert); err != nil { 167 return nil, err 168 } 169 170 // Only server authentication is allowed. 171 template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} 172 for _, h := range hosts { 173 if ip := net.ParseIP(h); ip != nil { 174 template.IPAddresses = append(template.IPAddresses, ip) 175 } else { 176 template.DNSNames = append(template.DNSNames, h) 177 } 178 } 179 180 certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, certPublicKey, caPrivateKey) 181 if err != nil { 182 return nil, err 183 } 184 185 return certBytes, nil 186 } 187 188 // GenerateClientCert generates a client certificate and returns the cert bytes. 189 // Takes in the CA cert and private key, the client public key, the certificate lifetime, 190 // and the username. 191 func GenerateClientCert( 192 caCert *x509.Certificate, 193 caPrivateKey crypto.PrivateKey, 194 clientPublicKey crypto.PublicKey, 195 lifetime time.Duration, 196 user string, 197 ) ([]byte, error) { 198 199 // TODO(marc): should we add extra checks? 200 if len(user) == 0 { 201 return nil, errors.Errorf("user cannot be empty") 202 } 203 204 // Create template for "user". 205 template, err := newTemplate(user, lifetime) 206 if err != nil { 207 return nil, err 208 } 209 210 // Don't issue certificates that outlast the CA cert. 211 if err := checkLifetimeAgainstCA(template, caCert); err != nil { 212 return nil, err 213 } 214 215 // Set client-specific fields. 216 // Client authentication only. 217 template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} 218 219 certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, clientPublicKey, caPrivateKey) 220 if err != nil { 221 return nil, err 222 } 223 224 return certBytes, nil 225 }