github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/certs.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 "context" 15 "crypto" 16 "crypto/rand" 17 "crypto/rsa" 18 "crypto/tls" 19 "crypto/x509" 20 "encoding/pem" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "time" 25 26 "github.com/cockroachdb/cockroach/pkg/util/envutil" 27 "github.com/cockroachdb/cockroach/pkg/util/log" 28 "github.com/cockroachdb/errors" 29 ) 30 31 const ( 32 keyFileMode = 0600 33 certFileMode = 0644 34 ) 35 36 // loadCACertAndKey loads the certificate and key files,parses them, 37 // and returns the x509 certificate and private key. 38 func loadCACertAndKey(sslCA, sslCAKey string) (*x509.Certificate, crypto.PrivateKey, error) { 39 // LoadX509KeyPair does a bunch of validation, including len(Certificates) != 0. 40 caCert, err := tls.LoadX509KeyPair(sslCA, sslCAKey) 41 if err != nil { 42 return nil, nil, errors.Errorf("error loading CA certificate %s and key %s: %s", 43 sslCA, sslCAKey, err) 44 } 45 46 // Extract x509 certificate from tls cert. 47 x509Cert, err := x509.ParseCertificate(caCert.Certificate[0]) 48 if err != nil { 49 return nil, nil, errors.Errorf("error parsing CA certificate %s: %s", sslCA, err) 50 } 51 return x509Cert, caCert.PrivateKey, nil 52 } 53 54 func writeCertificateToFile(certFilePath string, certificate []byte, overwrite bool) error { 55 certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: certificate} 56 57 return WritePEMToFile(certFilePath, certFileMode, overwrite, certBlock) 58 } 59 60 func writeKeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error { 61 keyBlock, err := PrivateKeyToPEM(key) 62 if err != nil { 63 return err 64 } 65 66 return WritePEMToFile(keyFilePath, keyFileMode, overwrite, keyBlock) 67 } 68 69 func writePKCS8KeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error { 70 keyBytes, err := PrivateKeyToPKCS8(key) 71 if err != nil { 72 return err 73 } 74 75 return SafeWriteToFile(keyFilePath, keyFileMode, overwrite, keyBytes) 76 } 77 78 // CreateCAPair creates a general CA certificate and associated key. 79 func CreateCAPair( 80 certsDir, caKeyPath string, 81 keySize int, 82 lifetime time.Duration, 83 allowKeyReuse bool, 84 overwrite bool, 85 ) error { 86 return createCACertAndKey(certsDir, caKeyPath, CAPem, keySize, lifetime, allowKeyReuse, overwrite) 87 } 88 89 // CreateClientCAPair creates a client CA certificate and associated key. 90 func CreateClientCAPair( 91 certsDir, caKeyPath string, 92 keySize int, 93 lifetime time.Duration, 94 allowKeyReuse bool, 95 overwrite bool, 96 ) error { 97 return createCACertAndKey(certsDir, caKeyPath, ClientCAPem, keySize, lifetime, allowKeyReuse, overwrite) 98 } 99 100 // CreateUICAPair creates a UI CA certificate and associated key. 101 func CreateUICAPair( 102 certsDir, caKeyPath string, 103 keySize int, 104 lifetime time.Duration, 105 allowKeyReuse bool, 106 overwrite bool, 107 ) error { 108 return createCACertAndKey(certsDir, caKeyPath, UICAPem, keySize, lifetime, allowKeyReuse, overwrite) 109 } 110 111 // createCACertAndKey creates a CA key and a CA certificate. 112 // If the certs directory does not exist, it is created. 113 // If the key does not exist, it is created. 114 // The certificate is written to the certs directory. If the file already exists, 115 // we append the original certificates to the new certificate. 116 // 117 // The filename of the certificate file must be specified. 118 // It should be one of: 119 // - ca.crt: the general CA certificate 120 // - ca-client.crt: the CA certificate to verify client certificates 121 func createCACertAndKey( 122 certsDir, caKeyPath string, 123 caType PemUsage, 124 keySize int, 125 lifetime time.Duration, 126 allowKeyReuse bool, 127 overwrite bool, 128 ) error { 129 if len(caKeyPath) == 0 { 130 return errors.New("the path to the CA key is required") 131 } 132 if len(certsDir) == 0 { 133 return errors.New("the path to the certs directory is required") 134 } 135 if caType != CAPem && caType != ClientCAPem && caType != UICAPem { 136 return fmt.Errorf("caType argument to createCACertAndKey must be one of CAPem (%d), ClientCAPem (%d), or UICAPem (%d), got: %d", 137 CAPem, ClientCAPem, UICAPem, caType) 138 } 139 140 // The certificate manager expands the env for the certs directory. 141 // For consistency, we need to do this for the key as well. 142 caKeyPath = os.ExpandEnv(caKeyPath) 143 144 // Create a certificate manager with "create dir if not exist". 145 cm, err := NewCertificateManagerFirstRun(certsDir) 146 if err != nil { 147 return err 148 } 149 150 var key crypto.PrivateKey 151 if _, err := os.Stat(caKeyPath); err != nil { 152 if !os.IsNotExist(err) { 153 return errors.Errorf("could not stat CA key file %s: %v", caKeyPath, err) 154 } 155 156 // The key does not exist: create it. 157 key, err = rsa.GenerateKey(rand.Reader, keySize) 158 if err != nil { 159 return errors.Errorf("could not generate new CA key: %v", err) 160 } 161 162 // overwrite is not technically needed here, but use it in case something else created it. 163 if err := writeKeyToFile(caKeyPath, key, overwrite); err != nil { 164 return errors.Errorf("could not write CA key to file %s: %v", caKeyPath, err) 165 } 166 167 log.Infof(context.Background(), "Generated CA key %s", caKeyPath) 168 } else { 169 if !allowKeyReuse { 170 return errors.Errorf("CA key %s exists, but key reuse is disabled", caKeyPath) 171 } 172 // The key exists, parse it. 173 contents, err := ioutil.ReadFile(caKeyPath) 174 if err != nil { 175 return errors.Errorf("could not read CA key file %s: %v", caKeyPath, err) 176 } 177 178 key, err = PEMToPrivateKey(contents) 179 if err != nil { 180 return errors.Errorf("could not parse CA key file %s: %v", caKeyPath, err) 181 } 182 183 log.Infof(context.Background(), "Using CA key from file %s", caKeyPath) 184 } 185 186 // Generate certificate. 187 certContents, err := GenerateCA(key.(crypto.Signer), lifetime) 188 if err != nil { 189 return errors.Errorf("could not generate CA certificate: %v", err) 190 } 191 192 var certPath string 193 // We've already checked the caType value at the beginning of this function. 194 switch caType { 195 case CAPem: 196 certPath = cm.CACertPath() 197 case ClientCAPem: 198 certPath = cm.ClientCACertPath() 199 case UICAPem: 200 certPath = cm.UICACertPath() 201 } 202 203 var existingCertificates []*pem.Block 204 if _, err := os.Stat(certPath); err == nil { 205 // The cert file already exists, load certificates. 206 contents, err := ioutil.ReadFile(certPath) 207 if err != nil { 208 return errors.Errorf("could not read existing CA cert file %s: %v", certPath, err) 209 } 210 211 existingCertificates, err = PEMToCertificates(contents) 212 if err != nil { 213 return errors.Errorf("could not parse existing CA cert file %s: %v", certPath, err) 214 } 215 log.Infof(context.Background(), "Found %d certificates in %s", 216 len(existingCertificates), certPath) 217 } else if !os.IsNotExist(err) { 218 return errors.Errorf("could not stat CA cert file %s: %v", certPath, err) 219 } 220 221 // Always place the new certificate first. 222 certificates := []*pem.Block{{Type: "CERTIFICATE", Bytes: certContents}} 223 certificates = append(certificates, existingCertificates...) 224 225 if err := WritePEMToFile(certPath, certFileMode, overwrite, certificates...); err != nil { 226 return errors.Errorf("could not write CA certificate file %s: %v", certPath, err) 227 } 228 229 log.Infof(context.Background(), "Wrote %d certificates to %s", len(certificates), certPath) 230 231 return nil 232 } 233 234 // CreateNodePair creates a node key and certificate. 235 // The CA cert and key must load properly. If multiple certificates 236 // exist in the CA cert, the first one is used. 237 func CreateNodePair( 238 certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, hosts []string, 239 ) error { 240 if len(caKeyPath) == 0 { 241 return errors.New("the path to the CA key is required") 242 } 243 if len(certsDir) == 0 { 244 return errors.New("the path to the certs directory is required") 245 } 246 247 // The certificate manager expands the env for the certs directory. 248 // For consistency, we need to do this for the key as well. 249 caKeyPath = os.ExpandEnv(caKeyPath) 250 251 // Create a certificate manager with "create dir if not exist". 252 cm, err := NewCertificateManagerFirstRun(certsDir) 253 if err != nil { 254 return err 255 } 256 257 // Load the CA pair. 258 caCert, caPrivateKey, err := loadCACertAndKey(cm.CACertPath(), caKeyPath) 259 if err != nil { 260 return err 261 } 262 263 // Generate certificates and keys. 264 nodeKey, err := rsa.GenerateKey(rand.Reader, keySize) 265 if err != nil { 266 return errors.Errorf("could not generate new node key: %v", err) 267 } 268 269 // Allow control of the principal to place in the cert via an env var. This 270 // is intended for testing purposes only. 271 nodeUser := envutil.EnvOrDefaultString("COCKROACH_CERT_NODE_USER", NodeUser) 272 nodeCert, err := GenerateServerCert(caCert, caPrivateKey, 273 nodeKey.Public(), lifetime, nodeUser, hosts) 274 if err != nil { 275 return errors.Errorf("error creating node server certificate and key: %s", err) 276 } 277 278 certPath := cm.NodeCertPath() 279 if err := writeCertificateToFile(certPath, nodeCert, overwrite); err != nil { 280 return errors.Errorf("error writing node server certificate to %s: %v", certPath, err) 281 } 282 log.Infof(context.Background(), "Generated node certificate: %s", certPath) 283 284 keyPath := cm.NodeKeyPath() 285 if err := writeKeyToFile(keyPath, nodeKey, overwrite); err != nil { 286 return errors.Errorf("error writing node server key to %s: %v", keyPath, err) 287 } 288 log.Infof(context.Background(), "Generated node key: %s", keyPath) 289 290 return nil 291 } 292 293 // CreateUIPair creates a UI certificate and key using the UI CA. 294 // The CA cert and key must load properly. If multiple certificates 295 // exist in the CA cert, the first one is used. 296 func CreateUIPair( 297 certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, hosts []string, 298 ) error { 299 if len(caKeyPath) == 0 { 300 return errors.New("the path to the CA key is required") 301 } 302 if len(certsDir) == 0 { 303 return errors.New("the path to the certs directory is required") 304 } 305 306 // The certificate manager expands the env for the certs directory. 307 // For consistency, we need to do this for the key as well. 308 caKeyPath = os.ExpandEnv(caKeyPath) 309 310 // Create a certificate manager with "create dir if not exist". 311 cm, err := NewCertificateManagerFirstRun(certsDir) 312 if err != nil { 313 return err 314 } 315 316 // Load the CA pair. 317 caCert, caPrivateKey, err := loadCACertAndKey(cm.UICACertPath(), caKeyPath) 318 if err != nil { 319 return err 320 } 321 322 // Generate certificates and keys. 323 uiKey, err := rsa.GenerateKey(rand.Reader, keySize) 324 if err != nil { 325 return errors.Errorf("could not generate new UI key: %v", err) 326 } 327 328 uiCert, err := GenerateUIServerCert(caCert, caPrivateKey, uiKey.Public(), lifetime, hosts) 329 if err != nil { 330 return errors.Errorf("error creating UI server certificate and key: %s", err) 331 } 332 333 certPath := cm.UICertPath() 334 if err := writeCertificateToFile(certPath, uiCert, overwrite); err != nil { 335 return errors.Errorf("error writing UI server certificate to %s: %v", certPath, err) 336 } 337 log.Infof(context.Background(), "Generated UI certificate: %s", certPath) 338 339 keyPath := cm.UIKeyPath() 340 if err := writeKeyToFile(keyPath, uiKey, overwrite); err != nil { 341 return errors.Errorf("error writing UI server key to %s: %v", keyPath, err) 342 } 343 log.Infof(context.Background(), "Generated UI key: %s", keyPath) 344 345 return nil 346 } 347 348 // CreateClientPair creates a node key and certificate. 349 // The CA cert and key must load properly. If multiple certificates 350 // exist in the CA cert, the first one is used. 351 // If a client CA exists, this is used instead. 352 // If wantPKCS8Key is true, the private key in PKCS#8 encoding is written as well. 353 func CreateClientPair( 354 certsDir, caKeyPath string, 355 keySize int, 356 lifetime time.Duration, 357 overwrite bool, 358 user string, 359 wantPKCS8Key bool, 360 ) error { 361 if len(caKeyPath) == 0 { 362 return errors.New("the path to the CA key is required") 363 } 364 if len(certsDir) == 0 { 365 return errors.New("the path to the certs directory is required") 366 } 367 368 // The certificate manager expands the env for the certs directory. 369 // For consistency, we need to do this for the key as well. 370 caKeyPath = os.ExpandEnv(caKeyPath) 371 372 // Create a certificate manager with "create dir if not exist". 373 cm, err := NewCertificateManagerFirstRun(certsDir) 374 if err != nil { 375 return err 376 } 377 378 var caCertPath string 379 // Check to see if we are using a client CA. 380 // We only check for its presence, not whether it has errors. 381 if cm.ClientCACert() != nil { 382 caCertPath = cm.ClientCACertPath() 383 } else { 384 caCertPath = cm.CACertPath() 385 } 386 387 // Load the CA pair. 388 caCert, caPrivateKey, err := loadCACertAndKey(caCertPath, caKeyPath) 389 if err != nil { 390 return err 391 } 392 393 // Generate certificates and keys. 394 clientKey, err := rsa.GenerateKey(rand.Reader, keySize) 395 if err != nil { 396 return errors.Errorf("could not generate new client key: %v", err) 397 } 398 399 clientCert, err := GenerateClientCert(caCert, caPrivateKey, clientKey.Public(), lifetime, user) 400 if err != nil { 401 return errors.Errorf("error creating client certificate and key: %s", err) 402 } 403 404 certPath := cm.ClientCertPath(user) 405 if err := writeCertificateToFile(certPath, clientCert, overwrite); err != nil { 406 return errors.Errorf("error writing client certificate to %s: %v", certPath, err) 407 } 408 log.Infof(context.Background(), "Generated client certificate: %s", certPath) 409 410 keyPath := cm.ClientKeyPath(user) 411 if err := writeKeyToFile(keyPath, clientKey, overwrite); err != nil { 412 return errors.Errorf("error writing client key to %s: %v", keyPath, err) 413 } 414 log.Infof(context.Background(), "Generated client key: %s", keyPath) 415 416 if wantPKCS8Key { 417 pkcs8KeyPath := keyPath + ".pk8" 418 if err := writePKCS8KeyToFile(pkcs8KeyPath, clientKey, overwrite); err != nil { 419 return errors.Errorf("error writing client PKCS8 key to %s: %v", pkcs8KeyPath, err) 420 } 421 log.Infof(context.Background(), "Generated PKCS8 client key: %s", pkcs8KeyPath) 422 } 423 424 return nil 425 } 426 427 // PEMContentsToX509 takes raw pem-encoded contents and attempts to parse into 428 // x509.Certificate objects. 429 func PEMContentsToX509(contents []byte) ([]*x509.Certificate, error) { 430 derCerts, err := PEMToCertificates(contents) 431 if err != nil { 432 return nil, err 433 } 434 435 certs := make([]*x509.Certificate, len(derCerts)) 436 for i, c := range derCerts { 437 x509Cert, err := x509.ParseCertificate(c.Bytes) 438 if err != nil { 439 return nil, err 440 } 441 442 certs[i] = x509Cert 443 } 444 445 return certs, nil 446 }