| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package server |
| |
| import ( |
| "bytes" |
| "crypto" |
| "crypto/ecdsa" |
| "crypto/elliptic" |
| "crypto/rand" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/pem" |
| "fmt" |
| "math/big" |
| "net" |
| "os" |
| "time" |
| |
| "github.com/hashicorp/vault/sdk/helper/certutil" |
| ) |
| |
| type CaCert struct { |
| PEM string |
| Template *x509.Certificate |
| Signer crypto.Signer |
| } |
| |
| // GenerateCert creates a new leaf cert from provided CA template and signer |
| func GenerateCert(caCertTemplate *x509.Certificate, caSigner crypto.Signer) (string, string, error) { |
| // Create the private key |
| signer, keyPEM, err := privateKey() |
| if err != nil { |
| return "", "", fmt.Errorf("error generating private key for server certificate: %v", err) |
| } |
| |
| // The serial number for the cert |
| sn, err := serialNumber() |
| if err != nil { |
| return "", "", fmt.Errorf("error generating serial number: %v", err) |
| } |
| |
| signerKeyId, err := certutil.GetSubjKeyID(signer) |
| if err != nil { |
| return "", "", fmt.Errorf("error getting subject key id from key: %v", err) |
| } |
| |
| hostname, err := os.Hostname() |
| if err != nil { |
| return "", "", fmt.Errorf("error getting hostname: %v", err) |
| } |
| |
| if hostname == "" { |
| hostname = "localhost" |
| } |
| |
| // Create the leaf cert |
| template := x509.Certificate{ |
| SerialNumber: sn, |
| Subject: pkix.Name{CommonName: hostname}, |
| KeyUsage: x509.KeyUsageDigitalSignature, |
| ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, |
| NotAfter: time.Now().Add(365 * 24 * time.Hour), |
| NotBefore: time.Now().Add(-1 * time.Minute), |
| IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, |
| DNSNames: []string{"localhost", "localhost4", "localhost6", "localhost.localdomain"}, |
| AuthorityKeyId: caCertTemplate.AuthorityKeyId, |
| SubjectKeyId: signerKeyId, |
| } |
| |
| // Only add our hostname to SANs if it isn't found. |
| foundHostname := false |
| for _, value := range template.DNSNames { |
| if value == hostname { |
| foundHostname = true |
| break |
| } |
| } |
| if !foundHostname { |
| template.DNSNames = append(template.DNSNames, hostname) |
| } |
| |
| bs, err := x509.CreateCertificate( |
| rand.Reader, &template, caCertTemplate, signer.Public(), caSigner) |
| if err != nil { |
| return "", "", fmt.Errorf("error creating server certificate: %v", err) |
| } |
| var buf bytes.Buffer |
| err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) |
| if err != nil { |
| return "", "", fmt.Errorf("error encoding server certificate: %v", err) |
| } |
| |
| return buf.String(), keyPEM, nil |
| } |
| |
| // GenerateCA generates a new self-signed CA cert and returns a |
| // CaCert struct containing the PEM encoded cert, |
| // X509 Certificate Template, and crypto.Signer |
| func GenerateCA() (*CaCert, error) { |
| // Create the private key we'll use for this CA cert. |
| signer, _, err := privateKey() |
| if err != nil { |
| return nil, fmt.Errorf("error generating private key for CA: %v", err) |
| } |
| |
| signerKeyId, err := certutil.GetSubjKeyID(signer) |
| if err != nil { |
| return nil, fmt.Errorf("error getting subject key id from key: %v", err) |
| } |
| |
| // The serial number for the cert |
| sn, err := serialNumber() |
| if err != nil { |
| return nil, fmt.Errorf("error generating serial number: %v", err) |
| } |
| |
| // Create the CA cert |
| template := x509.Certificate{ |
| SerialNumber: sn, |
| Subject: pkix.Name{CommonName: "Vault Dev CA"}, |
| BasicConstraintsValid: true, |
| KeyUsage: x509.KeyUsageCertSign, |
| ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, |
| IsCA: true, |
| NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), |
| NotBefore: time.Now().Add(-1 * time.Minute), |
| AuthorityKeyId: signerKeyId, |
| SubjectKeyId: signerKeyId, |
| } |
| |
| bs, err := x509.CreateCertificate( |
| rand.Reader, &template, &template, signer.Public(), signer) |
| if err != nil { |
| return nil, fmt.Errorf("error creating CA certificate: %v", err) |
| } |
| |
| var buf bytes.Buffer |
| err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) |
| if err != nil { |
| return nil, fmt.Errorf("error encoding CA certificate: %v", err) |
| } |
| return &CaCert{ |
| PEM: buf.String(), |
| Template: &template, |
| Signer: signer, |
| }, nil |
| } |
| |
| // privateKey returns a new ECDSA-based private key. Both a crypto.Signer |
| // and the key in PEM format are returned. |
| func privateKey() (crypto.Signer, string, error) { |
| pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| if err != nil { |
| return nil, "", err |
| } |
| |
| bs, err := x509.MarshalECPrivateKey(pk) |
| if err != nil { |
| return nil, "", err |
| } |
| |
| var buf bytes.Buffer |
| err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs}) |
| if err != nil { |
| return nil, "", err |
| } |
| |
| return pk, buf.String(), nil |
| } |
| |
| // serialNumber generates a new random serial number. |
| func serialNumber() (*big.Int, error) { |
| return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) |
| } |