Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

certutil: certificates are valid for 30 days and cli improvements #243

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions testing/certutil/certutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,21 @@ type Pair struct {
}

type configs struct {
cnPrefix string
dnsNames []string
cnPrefix string
dnsNames []string
clientCert bool
}

type Option func(opt *configs)

// WithClientCert generates a client certificate, without any IP or SAN/DNS.
// It overrides any other IP or name set by other means.
func WithClientCert(clientCert bool) Option {
return func(opt *configs) {
opt.clientCert = clientCert
}
}

// WithCNPrefix adds cnPrefix as prefix for the CN.
func WithCNPrefix(cnPrefix string) Option {
return func(opt *configs) {
Expand Down Expand Up @@ -175,9 +184,9 @@ func GenerateGenericChildCert(
if cfg.cnPrefix != "" {
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
}
dnsNames := append([]string{name}, cfg.dnsNames...)
notBefore, notAfter := makeNotBeforeAndAfter()

dnsNames := append(cfg.dnsNames, name)
notBefore, notAfter := makeNotBeforeAndAfter()
certTemplate := &x509.Certificate{
DNSNames: dnsNames,
IPAddresses: ips,
Expand All @@ -189,11 +198,18 @@ func GenerateGenericChildCert(
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
KeyUsage: x509.KeyUsageDigitalSignature |
x509.KeyUsageKeyEncipherment |
x509.KeyUsageKeyAgreement,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
}

if cfg.clientCert {
certTemplate.IPAddresses = nil
certTemplate.DNSNames = nil
}

certRawBytes, err := x509.CreateCertificate(
rand.Reader, certTemplate, caCert, pub, caPrivKey)
if err != nil {
Expand Down Expand Up @@ -271,6 +287,38 @@ func NewRSARootAndChildCerts() (Pair, Pair, error) {
return rootPair, childPair, err
}

// EncryptKey accepts a *ecdsa.PrivateKey or *rsa.PrivateKey, it encrypts it
// and returns the encrypted key in PEM format.
func EncryptKey(key crypto.PrivateKey, passphrase string) ([]byte, error) {
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, fmt.Errorf("error converting private key to DER: %w", err)
}

var blockType string
switch key.(type) {
case *rsa.PrivateKey:
blockType = "RSA PRIVATE KEY"
case *ecdsa.PrivateKey:
blockType = "EC PRIVATE KEY"
default:
return nil, fmt.Errorf("unsupported private key type: %T", key)
}

encPem, err := x509.EncryptPEMBlock( //nolint:staticcheck // we need to drop support for this, but while we don't, it needs to be tested.
rand.Reader,
blockType,
keyDER,
[]byte(passphrase),
x509.PEMCipherAES128)
if err != nil {
return nil, fmt.Errorf("failed encrypting certificate key: %w", err)
}

certKeyEnc := pem.EncodeToMemory(encPem)
return certKeyEnc, nil
}

// newRootCert creates a new self-signed root certificate using the provided
// private key and public key.
// It returns:
Expand Down Expand Up @@ -398,6 +446,6 @@ func keyBlockType(priv crypto.PrivateKey) string {
func makeNotBeforeAndAfter() (time.Time, time.Time) {
now := time.Now()
notBefore := now.Add(-1 * time.Minute)
notAfter := now.Add(7 * 24 * time.Hour)
notAfter := now.Add(30 * 24 * time.Hour)
return notBefore, notAfter
}
86 changes: 42 additions & 44 deletions testing/certutil/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"net"
Expand All @@ -39,17 +38,22 @@ import (
)

func main() {
var caPath, caKeyPath, dest, name, ipList, prefix, pass string
var rsaflag bool
var caPath, caKeyPath, dest, name, names, ipList, prefix, pass string
var client, rsaflag, noip bool
flag.StringVar(&caPath, "ca", "",
"File path for CA in PEM format")
flag.StringVar(&caKeyPath, "ca-key", "",
"File path for the CA key in PEM format")
flag.BoolVar(&rsaflag, "rsaflag", false,
"")
// TODO: accept multiple DNS names
flag.BoolVar(&rsaflag, "rsa", false,
"generate a RSA with a 2048-bit key certificate")
flag.BoolVar(&client, "client", false,
"generates a client certificate without any IP or SAN/DNS")
flag.StringVar(&name, "name", "localhost",
"used as \"distinguished name\" and \"Subject Alternate Name values\" for the child certificate")
"a single \"Subject Alternate Name values\" for the child certificate. It's added to 'names' if set")
flag.StringVar(&names, "names", "",
"a comma separated list of \"Subject Alternate Name values\" for the child certificate")
flag.BoolVar(&noip, "noip", false,
"generate a certificate with no IP. It overrides -ips.")
flag.StringVar(&ipList, "ips", "127.0.0.1",
"a comma separated list of IP addresses for the child certificate")
flag.StringVar(&prefix, "prefix", "current timestamp",
Expand All @@ -76,10 +80,17 @@ func main() {
}
fmt.Println("files will be witten to:", wd)

ips := strings.Split(ipList, ",")
var netIPs []net.IP
for _, ip := range ips {
netIPs = append(netIPs, net.ParseIP(ip))
if !noip {
ips := strings.Split(ipList, ",")
for _, ip := range ips {
netIPs = append(netIPs, net.ParseIP(ip))
}
}

var dnsNames []string
if names != "" {
dnsNames = strings.Split(names, ",")
}

rootCert, rootKey := getCA(rsaflag, caPath, caKeyPath, dest, prefix)
Expand All @@ -92,48 +103,35 @@ func main() {
pub,
rootKey,
rootCert,
certutil.WithCNPrefix(prefix))
certutil.WithCNPrefix(prefix),
certutil.WithDNSNames(dnsNames...),
certutil.WithClientCert(client))
if err != nil {
panic(fmt.Errorf("error generating child certificate: %w", err))
}

savePair(dest, filePrefix+name, childPair)

if pass == "" {
return
}

fmt.Printf("passphrase present, encrypting \"%s\" certificate key\n",
name)
err = os.WriteFile(filePrefix+name+"-passphrase", []byte(pass), 0o600)
if err != nil {
panic(fmt.Errorf("error writing passphrase file: %w", err))
}

key, err := x509.MarshalPKCS8PrivateKey(childCert.PrivateKey)
if err != nil {
panic(fmt.Errorf("error getting ecdh.PrivateKey from the child's private key: %w", err))
if client {
name = "client"
}
savePair(dest, filePrefix+name, childPair)

blockType := "EC PRIVATE KEY"
if rsaflag {
blockType = "RSA PRIVATE KEY"
}
encPem, err := x509.EncryptPEMBlock( //nolint:staticcheck // we need to drop support for this, but while we don't, it needs to be tested.
rand.Reader,
blockType,
key,
[]byte(pass),
x509.PEMCipherAES128)
if err != nil {
panic(fmt.Errorf("failed encrypting agent child certificate key block: %v", err))
}
if pass != "" {
fmt.Printf("passphrase present, encrypting \"%s\" certificate key\n",
name)
err = os.WriteFile(filePrefix+name+"-passphrase", []byte(pass), 0o600)
if err != nil {
panic(fmt.Errorf("error writing passphrase file: %w", err))
}

certKeyEnc := pem.EncodeToMemory(encPem)
certKeyEnc, err := certutil.EncryptKey(childCert.PrivateKey, pass)
if err != nil {
panic(err)
}

err = os.WriteFile(filepath.Join(dest, filePrefix+name+"_enc-key.pem"), certKeyEnc, 0o600)
if err != nil {
panic(fmt.Errorf("could not save %s certificate encrypted key: %w", filePrefix+name+"_enc-key.pem", err))
err = os.WriteFile(filepath.Join(dest, filePrefix+name+"_enc-key.pem"), certKeyEnc, 0o600)
if err != nil {
panic(fmt.Errorf("could not save %s certificate encrypted key: %w", filePrefix+name+"_enc-key.pem", err))
}
}
}

Expand Down
Loading