177 lines
4.8 KiB
Go
177 lines
4.8 KiB
Go
|
// 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))
|
||
|
}
|