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: Add functional options #239

Closed
wants to merge 1 commit into from
Closed
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
94 changes: 83 additions & 11 deletions testing/certutil/certutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,40 @@ type Pair struct {
Key []byte
}

type configs struct {
cnPrefix string
dnsNames []string
}

type Option func(opt *configs)

// WithCNPrefix adds cnPrefix as prefix for the CN.
func WithCNPrefix(cnPrefix string) Option {
return func(opt *configs) {
opt.cnPrefix = cnPrefix
}
}

// WithDNSNames adds dnsNames to the DNSNames.
func WithDNSNames(dnsNames ...string) Option {
return func(opt *configs) {
opt.dnsNames = dnsNames
}
}

// NewRootCA generates a new x509 Certificate using ECDSA P-384 and returns:
// - the private key
// - the certificate
// - the certificate and its key in PEM format as a byte slice.
//
// If any error occurs during the generation process, a non-nil error is returned.
func NewRootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
func NewRootCA(opts ...Option) (crypto.PrivateKey, *x509.Certificate, Pair, error) {
rootKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
}

cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey, opts...)
return rootKey, cert, pair, err
}

Expand All @@ -62,12 +83,12 @@ func NewRootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
// - the certificate and its key in PEM format as a byte slice.
//
// If any error occurs during the generation process, a non-nil error is returned.
func NewRSARootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
func NewRSARootCA(opts ...Option) (crypto.PrivateKey, *x509.Certificate, Pair, error) {
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
}
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey, opts...)
return rootKey, cert, pair, err
}

Expand All @@ -77,7 +98,36 @@ func NewRSARootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
// - a Pair with the certificate and its key im PEM format
//
// 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) {
func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate, opts ...Option) (*tls.Certificate, Pair, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create RSA private key: %w", err)
}

cert, childPair, err :=
GenerateGenericChildCert(
name,
ips,
priv,
&priv.PublicKey,
caPrivKey,
caCert,
opts...)
if err != nil {
return nil, Pair{}, fmt.Errorf(
"could not generate child TLS certificate CA: %w", err)
}

return cert, childPair, nil
}

// GenerateRSAChildCert generates a RSA with a 2048-bit key x509 Certificate as a
// child of caCert and returns the following:
// - the certificate and private key as a tls.Certificate
// - a Pair with the certificate and its key im PEM format
//
// If any error occurs during the generation process, a non-nil error is returned.
func GenerateRSAChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate, opts ...Option) (*tls.Certificate, Pair, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create RSA private key: %w", err)
Expand All @@ -90,7 +140,8 @@ func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, c
priv,
&priv.PublicKey,
caPrivKey,
caCert)
caCert,
opts...)
if err != nil {
return nil, Pair{}, fmt.Errorf(
"could not generate child TLS certificate CA: %w", err)
Expand All @@ -115,18 +166,26 @@ func GenerateGenericChildCert(
priv crypto.PrivateKey,
pub crypto.PublicKey,
caPrivKey crypto.PrivateKey,
caCert *x509.Certificate) (*tls.Certificate, Pair, error) {
caCert *x509.Certificate,
opts ...Option) (*tls.Certificate, Pair, error) {

cfg := getCgf(opts)

cn := "Police Public Call Box"
if cfg.cnPrefix != "" {
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
}
dnsNames := append([]string{name}, cfg.dnsNames...)
notBefore, notAfter := makeNotBeforeAndAfter()

certTemplate := &x509.Certificate{
DNSNames: []string{name},
DNSNames: dnsNames,
IPAddresses: ips,
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Locality: []string{"anywhere in time and space"},
Organization: []string{"TARDIS"},
CommonName: "Police Public Call Box",
CommonName: cn,
},
NotBefore: notBefore,
NotAfter: notAfter,
Expand Down Expand Up @@ -220,7 +279,12 @@ func NewRSARootAndChildCerts() (Pair, Pair, error) {
// - a Pair containing the certificate and private key in PEM format.
//
// If an error occurs during certificate creation, it returns a non-nil error.
func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificate, Pair, error) {
func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey, opts ...Option) (*x509.Certificate, Pair, error) {
cn := "High Council"
cfg := getCgf(opts)
if cfg.cnPrefix != "" {
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
}
notBefore, notAfter := makeNotBeforeAndAfter()

rootTemplate := x509.Certificate{
Expand All @@ -230,7 +294,7 @@ func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificat
Locality: []string{"The Capitol"},
OrganizationalUnit: []string{"Time Lords"},
Organization: []string{"High Council of the Time Lords"},
CommonName: "High Council",
CommonName: cn,
},
NotBefore: notBefore,
NotAfter: notAfter,
Expand Down Expand Up @@ -286,6 +350,14 @@ func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificat
}, nil
}

func getCgf(opts []Option) configs {
cfg := configs{dnsNames: []string{}}
for _, opt := range opts {
opt(&cfg)
}
return cfg
}

// defaultChildCert generates a child certificate for localhost and 127.0.0.1.
// It returns the certificate and its key as a Pair and an error if any happens.
func defaultChildCert(
Expand Down
32 changes: 17 additions & 15 deletions testing/certutil/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@
)

func main() {
var caPath, caKeyPath, dest, name, ipList, filePrefix, pass string
var rsa bool
var caPath, caKeyPath, dest, name, ipList, prefix, pass string
var rsaflag 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(&rsa, "rsa", false,
flag.BoolVar(&rsaflag, "rsaflag", false,
"")
// TODO: accept multiple DNS names
flag.StringVar(&name, "name", "localhost",
"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",
flag.StringVar(&prefix, "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")
Expand All @@ -64,10 +65,10 @@
caPath, caKeyPath)

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

wd, err := os.Getwd()
if err != nil {
Expand All @@ -78,19 +79,20 @@
ips := strings.Split(ipList, ",")
var netIPs []net.IP
for _, ip := range ips {
netIPs = append(netIPs, net.ParseIP(ip))

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (windows)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (linux)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (darwin)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (windows)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (linux)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)

Check failure on line 82 in testing/certutil/cmd/main.go

View workflow job for this annotation

GitHub Actions / lint (darwin)

SA4010: this result of append is never used, except maybe in other appends (staticcheck)
}

rootCert, rootKey := getCA(rsa, caPath, caKeyPath, dest, filePrefix)
priv, pub := generateKey(rsa)
rootCert, rootKey := getCA(rsaflag, caPath, caKeyPath, dest, prefix)
priv, pub := generateKey(rsaflag)

childCert, childPair, err := certutil.GenerateGenericChildCert(
name,
netIPs,
nil, // netIPs,
priv,
pub,
rootKey,
rootCert)
rootCert,
certutil.WithCNPrefix(prefix))
if err != nil {
panic(fmt.Errorf("error generating child certificate: %w", err))
}
Expand All @@ -114,7 +116,7 @@
}

blockType := "EC PRIVATE KEY"
if rsa {
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.
Expand Down Expand Up @@ -153,7 +155,7 @@
return priv, &priv.PublicKey
}

func getCA(rsa bool, caPath, caKeyPath, dest, filePrefix string) (*x509.Certificate, crypto.PrivateKey) {
func getCA(rsa bool, caPath, caKeyPath, dest, prefix string) (*x509.Certificate, crypto.PrivateKey) {
var rootCert *x509.Certificate
var rootKey crypto.PrivateKey
var err error
Expand All @@ -165,12 +167,12 @@
}

var pair certutil.Pair
rootKey, rootCert, pair, err = caFn()
rootKey, rootCert, pair, err = caFn(certutil.WithCNPrefix(prefix))
if err != nil {
panic(fmt.Errorf("could not create root CA certificate: %w", err))
}

savePair(dest, filePrefix+"ca", pair)
savePair(dest, prefix+"-ca", pair)
} else {
rootKey, rootCert = loadCA(caPath, caKeyPath)
}
Expand Down
Loading