Skip to content

Commit 0e8760f

Browse files
authored
certutil: add functional options (#240)
add functional options to New*RootCA and Generate*ChildCert to allow setting a prefix for the CN and multiple DNS names
1 parent 6634efe commit 0e8760f

File tree

2 files changed

+100
-26
lines changed

2 files changed

+100
-26
lines changed

testing/certutil/certutil.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,40 @@ type Pair struct {
4040
Key []byte
4141
}
4242

43+
type configs struct {
44+
cnPrefix string
45+
dnsNames []string
46+
}
47+
48+
type Option func(opt *configs)
49+
50+
// WithCNPrefix adds cnPrefix as prefix for the CN.
51+
func WithCNPrefix(cnPrefix string) Option {
52+
return func(opt *configs) {
53+
opt.cnPrefix = cnPrefix
54+
}
55+
}
56+
57+
// WithDNSNames adds dnsNames to the DNSNames.
58+
func WithDNSNames(dnsNames ...string) Option {
59+
return func(opt *configs) {
60+
opt.dnsNames = dnsNames
61+
}
62+
}
63+
4364
// NewRootCA generates a new x509 Certificate using ECDSA P-384 and returns:
4465
// - the private key
4566
// - the certificate
4667
// - the certificate and its key in PEM format as a byte slice.
4768
//
4869
// If any error occurs during the generation process, a non-nil error is returned.
49-
func NewRootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
70+
func NewRootCA(opts ...Option) (crypto.PrivateKey, *x509.Certificate, Pair, error) {
5071
rootKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
5172
if err != nil {
5273
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
5374
}
5475

55-
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
76+
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey, opts...)
5677
return rootKey, cert, pair, err
5778
}
5879

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

@@ -77,7 +98,36 @@ func NewRSARootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
7798
// - a Pair with the certificate and its key im PEM format
7899
//
79100
// If any error occurs during the generation process, a non-nil error is returned.
80-
func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate) (*tls.Certificate, Pair, error) {
101+
func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate, opts ...Option) (*tls.Certificate, Pair, error) {
102+
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
103+
if err != nil {
104+
return nil, Pair{}, fmt.Errorf("could not create ECDSA private key: %w", err)
105+
}
106+
107+
cert, childPair, err :=
108+
GenerateGenericChildCert(
109+
name,
110+
ips,
111+
priv,
112+
&priv.PublicKey,
113+
caPrivKey,
114+
caCert,
115+
opts...)
116+
if err != nil {
117+
return nil, Pair{}, fmt.Errorf(
118+
"could not generate child TLS certificate CA: %w", err)
119+
}
120+
121+
return cert, childPair, nil
122+
}
123+
124+
// GenerateRSAChildCert generates a RSA with a 2048-bit key x509 Certificate as a
125+
// child of caCert and returns the following:
126+
// - the certificate and private key as a tls.Certificate
127+
// - a Pair with the certificate and its key im PEM format
128+
//
129+
// If any error occurs during the generation process, a non-nil error is returned.
130+
func GenerateRSAChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate, opts ...Option) (*tls.Certificate, Pair, error) {
81131
priv, err := rsa.GenerateKey(rand.Reader, 2048)
82132
if err != nil {
83133
return nil, Pair{}, fmt.Errorf("could not create RSA private key: %w", err)
@@ -90,10 +140,11 @@ func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, c
90140
priv,
91141
&priv.PublicKey,
92142
caPrivKey,
93-
caCert)
143+
caCert,
144+
opts...)
94145
if err != nil {
95146
return nil, Pair{}, fmt.Errorf(
96-
"could not generate child TLS certificate CA: %w", err)
147+
"could not generate child TLS certificate: %w", err)
97148
}
98149

99150
return cert, childPair, nil
@@ -115,18 +166,26 @@ func GenerateGenericChildCert(
115166
priv crypto.PrivateKey,
116167
pub crypto.PublicKey,
117168
caPrivKey crypto.PrivateKey,
118-
caCert *x509.Certificate) (*tls.Certificate, Pair, error) {
169+
caCert *x509.Certificate,
170+
opts ...Option) (*tls.Certificate, Pair, error) {
119171

172+
cfg := getCgf(opts)
173+
174+
cn := "Police Public Call Box"
175+
if cfg.cnPrefix != "" {
176+
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
177+
}
178+
dnsNames := append([]string{name}, cfg.dnsNames...)
120179
notBefore, notAfter := makeNotBeforeAndAfter()
121180

122181
certTemplate := &x509.Certificate{
123-
DNSNames: []string{name},
182+
DNSNames: dnsNames,
124183
IPAddresses: ips,
125184
SerialNumber: big.NewInt(1658),
126185
Subject: pkix.Name{
127186
Locality: []string{"anywhere in time and space"},
128187
Organization: []string{"TARDIS"},
129-
CommonName: "Police Public Call Box",
188+
CommonName: cn,
130189
},
131190
NotBefore: notBefore,
132191
NotAfter: notAfter,
@@ -220,7 +279,12 @@ func NewRSARootAndChildCerts() (Pair, Pair, error) {
220279
// - a Pair containing the certificate and private key in PEM format.
221280
//
222281
// If an error occurs during certificate creation, it returns a non-nil error.
223-
func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificate, Pair, error) {
282+
func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey, opts ...Option) (*x509.Certificate, Pair, error) {
283+
cn := "High Council"
284+
cfg := getCgf(opts)
285+
if cfg.cnPrefix != "" {
286+
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
287+
}
224288
notBefore, notAfter := makeNotBeforeAndAfter()
225289

226290
rootTemplate := x509.Certificate{
@@ -230,7 +294,7 @@ func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificat
230294
Locality: []string{"The Capitol"},
231295
OrganizationalUnit: []string{"Time Lords"},
232296
Organization: []string{"High Council of the Time Lords"},
233-
CommonName: "High Council",
297+
CommonName: cn,
234298
},
235299
NotBefore: notBefore,
236300
NotAfter: notAfter,
@@ -286,6 +350,14 @@ func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificat
286350
}, nil
287351
}
288352

353+
func getCgf(opts []Option) configs {
354+
cfg := configs{dnsNames: []string{}}
355+
for _, opt := range opts {
356+
opt(&cfg)
357+
}
358+
return cfg
359+
}
360+
289361
// defaultChildCert generates a child certificate for localhost and 127.0.0.1.
290362
// It returns the certificate and its key as a Pair and an error if any happens.
291363
func defaultChildCert(

testing/certutil/cmd/main.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,20 @@ import (
3939
)
4040

4141
func main() {
42-
var caPath, caKeyPath, dest, name, ipList, filePrefix, pass string
43-
var rsa bool
42+
var caPath, caKeyPath, dest, name, ipList, prefix, pass string
43+
var rsaflag bool
4444
flag.StringVar(&caPath, "ca", "",
4545
"File path for CA in PEM format")
4646
flag.StringVar(&caKeyPath, "ca-key", "",
4747
"File path for the CA key in PEM format")
48-
flag.BoolVar(&rsa, "rsa", false,
48+
flag.BoolVar(&rsaflag, "rsaflag", false,
4949
"")
50+
// TODO: accept multiple DNS names
5051
flag.StringVar(&name, "name", "localhost",
5152
"used as \"distinguished name\" and \"Subject Alternate Name values\" for the child certificate")
5253
flag.StringVar(&ipList, "ips", "127.0.0.1",
5354
"a comma separated list of IP addresses for the child certificate")
54-
flag.StringVar(&filePrefix, "prefix", "current timestamp",
55+
flag.StringVar(&prefix, "prefix", "current timestamp",
5556
"a prefix to be added to the file name. If not provided a timestamp will be used")
5657
flag.StringVar(&pass, "pass", "",
5758
"a passphrase to encrypt the certificate key")
@@ -64,10 +65,10 @@ func main() {
6465
caPath, caKeyPath)
6566

6667
}
67-
if filePrefix == "" {
68-
filePrefix = fmt.Sprintf("%d", time.Now().Unix())
68+
if prefix == "current timestamp" {
69+
prefix = fmt.Sprintf("%d", time.Now().Unix())
6970
}
70-
filePrefix += "-"
71+
filePrefix := prefix + "-"
7172

7273
wd, err := os.Getwd()
7374
if err != nil {
@@ -81,16 +82,17 @@ func main() {
8182
netIPs = append(netIPs, net.ParseIP(ip))
8283
}
8384

84-
rootCert, rootKey := getCA(rsa, caPath, caKeyPath, dest, filePrefix)
85-
priv, pub := generateKey(rsa)
85+
rootCert, rootKey := getCA(rsaflag, caPath, caKeyPath, dest, prefix)
86+
priv, pub := generateKey(rsaflag)
8687

8788
childCert, childPair, err := certutil.GenerateGenericChildCert(
8889
name,
8990
netIPs,
9091
priv,
9192
pub,
9293
rootKey,
93-
rootCert)
94+
rootCert,
95+
certutil.WithCNPrefix(prefix))
9496
if err != nil {
9597
panic(fmt.Errorf("error generating child certificate: %w", err))
9698
}
@@ -114,7 +116,7 @@ func main() {
114116
}
115117

116118
blockType := "EC PRIVATE KEY"
117-
if rsa {
119+
if rsaflag {
118120
blockType = "RSA PRIVATE KEY"
119121
}
120122
encPem, err := x509.EncryptPEMBlock( //nolint:staticcheck // we need to drop support for this, but while we don't, it needs to be tested.
@@ -153,7 +155,7 @@ func generateKey(useRSA bool) (crypto.PrivateKey, crypto.PublicKey) {
153155
return priv, &priv.PublicKey
154156
}
155157

156-
func getCA(rsa bool, caPath, caKeyPath, dest, filePrefix string) (*x509.Certificate, crypto.PrivateKey) {
158+
func getCA(rsa bool, caPath, caKeyPath, dest, prefix string) (*x509.Certificate, crypto.PrivateKey) {
157159
var rootCert *x509.Certificate
158160
var rootKey crypto.PrivateKey
159161
var err error
@@ -165,12 +167,12 @@ func getCA(rsa bool, caPath, caKeyPath, dest, filePrefix string) (*x509.Certific
165167
}
166168

167169
var pair certutil.Pair
168-
rootKey, rootCert, pair, err = caFn()
170+
rootKey, rootCert, pair, err = caFn(certutil.WithCNPrefix(prefix))
169171
if err != nil {
170172
panic(fmt.Errorf("could not create root CA certificate: %w", err))
171173
}
172174

173-
savePair(dest, filePrefix+"ca", pair)
175+
savePair(dest, prefix+"-ca", pair)
174176
} else {
175177
rootKey, rootCert = loadCA(caPath, caKeyPath)
176178
}

0 commit comments

Comments
 (0)