Skip to content

Commit

Permalink
testing/certutil/cmd: add passphrase protected key support (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndersonQ authored Sep 23, 2024
1 parent 4babd25 commit 6c381fb
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 15 deletions.
27 changes: 17 additions & 10 deletions testing/certutil/certutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ func NewRootCA() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
}

notBefore := time.Now()
notAfter := notBefore.Add(3 * time.Hour)
notBefore, notAfter := makeNotBeforeAndAfter()

rootTemplate := x509.Certificate{
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"Gallifrey"},
CommonName: "localhost",
Country: []string{"Gallifrey"},
Locality: []string{"The Capitol"},
OrganizationalUnit: []string{"Time Lords"},
Organization: []string{"High Council of the Time Lords"},
CommonName: "High Council",
},
NotBefore: notBefore,
NotAfter: notAfter,
Expand Down Expand Up @@ -125,16 +125,16 @@ func NewRootCA() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
// If any error occurs during the generation process, a non-nil error is returned.
func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate) (*tls.Certificate, Pair, error) {

notBefore := time.Now()
notAfter := notBefore.Add(3 * time.Hour)
notBefore, notAfter := makeNotBeforeAndAfter()

certTemplate := &x509.Certificate{
DNSNames: []string{name},
IPAddresses: ips,
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Organization: []string{"Gallifrey"},
CommonName: name,
Locality: []string{"anywhere in time and space"},
Organization: []string{"TARDIS"},
CommonName: "Police Public Call Box",
},
NotBefore: notBefore,
NotAfter: notAfter,
Expand Down Expand Up @@ -212,3 +212,10 @@ func NewRootAndChildCerts() (Pair, Pair, error) {

return rootPair, childPair, nil
}

func makeNotBeforeAndAfter() (time.Time, time.Time) {
now := time.Now()
notBefore := now.Add(-1 * time.Minute)
notAfter := now.Add(7 * 24 * time.Hour)
return notBefore, notAfter
}
59 changes: 54 additions & 5 deletions testing/certutil/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@ package main

import (
"crypto"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"

"github.com/elastic/elastic-agent-libs/testing/certutil"
)

func main() {
var caPath, caKeyPath, dest, name, ipList string
var caPath, caKeyPath, dest, name, ipList, filePrefix, pass string
flag.StringVar(&caPath, "ca", "",
"File path for CA in PEM format")
flag.StringVar(&caKeyPath, "ca-key", "",
Expand All @@ -43,6 +46,10 @@ func main() {
"used as \"distinguished name\" and \"Subject Alternate Name values\" for the child certificate")
flag.StringVar(&ipList, "ips", "127.0.0.1",
"a comma separated list of IP addresses for the child certificate")
flag.StringVar(&filePrefix, "prefix", "current timestamp",
"a prefix to be added to the file name. If not provided a timestamp will be used")
flag.StringVar(&pass, "pass", "",
"a passphrase to encrypt the certificate key")
flag.Parse()

if caPath == "" && caKeyPath != "" || caPath != "" && caKeyPath == "" {
Expand All @@ -52,6 +59,16 @@ func main() {
caPath, caKeyPath)

}
if filePrefix == "" {
filePrefix = fmt.Sprintf("%d", time.Now().Unix())
}
filePrefix += "-"

wd, err := os.Getwd()
if err != nil {
fmt.Printf("error getting current working directory: %v\n", err)
}
fmt.Println("files will be witten to:", wd)

ips := strings.Split(ipList, ",")
var netIPs []net.IP
Expand All @@ -61,25 +78,57 @@ func main() {

var rootCert *x509.Certificate
var rootKey crypto.PrivateKey
var err error
if caPath == "" && caKeyPath == "" {
var pair certutil.Pair
rootKey, rootCert, pair, err = certutil.NewRootCA()
if err != nil {
panic(fmt.Errorf("could not create root CA certificate: %w", err))
}

savePair(dest, "ca", pair)
savePair(dest, filePrefix+"ca", pair)
} else {
rootKey, rootCert = loadCA(caPath, caKeyPath)
}

_, childPair, err := certutil.GenerateChildCert(name, netIPs, rootKey, rootCert)
childCert, childPair, err := certutil.GenerateChildCert(name, netIPs, rootKey, rootCert)
if err != nil {
panic(fmt.Errorf("error generating child certificate: %w", err))
}

savePair(dest, name, childPair)
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))
}

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,
"EC PRIVATE KEY",
key,
[]byte(pass),
x509.PEMCipherAES128)
if err != nil {
panic(fmt.Errorf("failed encrypting agent child certificate key block: %v", err))
}

certKeyEnc := pem.EncodeToMemory(encPem)

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))
}
}

func loadCA(caPath string, keyPath string) (crypto.PrivateKey, *x509.Certificate) {
Expand Down

0 comments on commit 6c381fb

Please sign in to comment.