Skip to content

Commit 26a9ae3

Browse files
authored
certutil: certificates are valid for 30 days and cli improvments (#243)
- the generated certificates are valid for 30 days instead of 7 - add `-noip` flag to allow generating certificates without ips - restore the `-rsa` flag. It had been changed to `rsaflag` by mistake - add `-names` flag to allow setting multiple dns names - add `-client` flag to generate a certificate without any SAN/DNS or IP
1 parent 4babafd commit 26a9ae3

File tree

2 files changed

+96
-50
lines changed

2 files changed

+96
-50
lines changed

testing/certutil/certutil.go

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,21 @@ type Pair struct {
4141
}
4242

4343
type configs struct {
44-
cnPrefix string
45-
dnsNames []string
44+
cnPrefix string
45+
dnsNames []string
46+
clientCert bool
4647
}
4748

4849
type Option func(opt *configs)
4950

51+
// WithClientCert generates a client certificate, without any IP or SAN/DNS.
52+
// It overrides any other IP or name set by other means.
53+
func WithClientCert(clientCert bool) Option {
54+
return func(opt *configs) {
55+
opt.clientCert = clientCert
56+
}
57+
}
58+
5059
// WithCNPrefix adds cnPrefix as prefix for the CN.
5160
func WithCNPrefix(cnPrefix string) Option {
5261
return func(opt *configs) {
@@ -175,9 +184,9 @@ func GenerateGenericChildCert(
175184
if cfg.cnPrefix != "" {
176185
cn = fmt.Sprintf("[%s] %s", cfg.cnPrefix, cn)
177186
}
178-
dnsNames := append([]string{name}, cfg.dnsNames...)
179-
notBefore, notAfter := makeNotBeforeAndAfter()
180187

188+
dnsNames := append(cfg.dnsNames, name)
189+
notBefore, notAfter := makeNotBeforeAndAfter()
181190
certTemplate := &x509.Certificate{
182191
DNSNames: dnsNames,
183192
IPAddresses: ips,
@@ -189,11 +198,18 @@ func GenerateGenericChildCert(
189198
},
190199
NotBefore: notBefore,
191200
NotAfter: notAfter,
192-
KeyUsage: x509.KeyUsageDigitalSignature,
201+
KeyUsage: x509.KeyUsageDigitalSignature |
202+
x509.KeyUsageKeyEncipherment |
203+
x509.KeyUsageKeyAgreement,
193204
ExtKeyUsage: []x509.ExtKeyUsage{
194205
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
195206
}
196207

208+
if cfg.clientCert {
209+
certTemplate.IPAddresses = nil
210+
certTemplate.DNSNames = nil
211+
}
212+
197213
certRawBytes, err := x509.CreateCertificate(
198214
rand.Reader, certTemplate, caCert, pub, caPrivKey)
199215
if err != nil {
@@ -271,6 +287,38 @@ func NewRSARootAndChildCerts() (Pair, Pair, error) {
271287
return rootPair, childPair, err
272288
}
273289

290+
// EncryptKey accepts a *ecdsa.PrivateKey or *rsa.PrivateKey, it encrypts it
291+
// and returns the encrypted key in PEM format.
292+
func EncryptKey(key crypto.PrivateKey, passphrase string) ([]byte, error) {
293+
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
294+
if err != nil {
295+
return nil, fmt.Errorf("error converting private key to DER: %w", err)
296+
}
297+
298+
var blockType string
299+
switch key.(type) {
300+
case *rsa.PrivateKey:
301+
blockType = "RSA PRIVATE KEY"
302+
case *ecdsa.PrivateKey:
303+
blockType = "EC PRIVATE KEY"
304+
default:
305+
return nil, fmt.Errorf("unsupported private key type: %T", key)
306+
}
307+
308+
encPem, err := x509.EncryptPEMBlock( //nolint:staticcheck // we need to drop support for this, but while we don't, it needs to be tested.
309+
rand.Reader,
310+
blockType,
311+
keyDER,
312+
[]byte(passphrase),
313+
x509.PEMCipherAES128)
314+
if err != nil {
315+
return nil, fmt.Errorf("failed encrypting certificate key: %w", err)
316+
}
317+
318+
certKeyEnc := pem.EncodeToMemory(encPem)
319+
return certKeyEnc, nil
320+
}
321+
274322
// newRootCert creates a new self-signed root certificate using the provided
275323
// private key and public key.
276324
// It returns:
@@ -398,6 +446,6 @@ func keyBlockType(priv crypto.PrivateKey) string {
398446
func makeNotBeforeAndAfter() (time.Time, time.Time) {
399447
now := time.Now()
400448
notBefore := now.Add(-1 * time.Minute)
401-
notAfter := now.Add(7 * 24 * time.Hour)
449+
notAfter := now.Add(30 * 24 * time.Hour)
402450
return notBefore, notAfter
403451
}

testing/certutil/cmd/main.go

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"crypto/rsa"
2727
"crypto/tls"
2828
"crypto/x509"
29-
"encoding/pem"
3029
"flag"
3130
"fmt"
3231
"net"
@@ -39,17 +38,22 @@ import (
3938
)
4039

4140
func main() {
42-
var caPath, caKeyPath, dest, name, ipList, prefix, pass string
43-
var rsaflag bool
41+
var caPath, caKeyPath, dest, name, names, ipList, prefix, pass string
42+
var client, rsaflag, noip bool
4443
flag.StringVar(&caPath, "ca", "",
4544
"File path for CA in PEM format")
4645
flag.StringVar(&caKeyPath, "ca-key", "",
4746
"File path for the CA key in PEM format")
48-
flag.BoolVar(&rsaflag, "rsaflag", false,
49-
"")
50-
// TODO: accept multiple DNS names
47+
flag.BoolVar(&rsaflag, "rsa", false,
48+
"generate a RSA with a 2048-bit key certificate")
49+
flag.BoolVar(&client, "client", false,
50+
"generates a client certificate without any IP or SAN/DNS")
5151
flag.StringVar(&name, "name", "localhost",
52-
"used as \"distinguished name\" and \"Subject Alternate Name values\" for the child certificate")
52+
"a single \"Subject Alternate Name values\" for the child certificate. It's added to 'names' if set")
53+
flag.StringVar(&names, "names", "",
54+
"a comma separated list of \"Subject Alternate Name values\" for the child certificate")
55+
flag.BoolVar(&noip, "noip", false,
56+
"generate a certificate with no IP. It overrides -ips.")
5357
flag.StringVar(&ipList, "ips", "127.0.0.1",
5458
"a comma separated list of IP addresses for the child certificate")
5559
flag.StringVar(&prefix, "prefix", "current timestamp",
@@ -76,10 +80,17 @@ func main() {
7680
}
7781
fmt.Println("files will be witten to:", wd)
7882

79-
ips := strings.Split(ipList, ",")
8083
var netIPs []net.IP
81-
for _, ip := range ips {
82-
netIPs = append(netIPs, net.ParseIP(ip))
84+
if !noip {
85+
ips := strings.Split(ipList, ",")
86+
for _, ip := range ips {
87+
netIPs = append(netIPs, net.ParseIP(ip))
88+
}
89+
}
90+
91+
var dnsNames []string
92+
if names != "" {
93+
dnsNames = strings.Split(names, ",")
8394
}
8495

8596
rootCert, rootKey := getCA(rsaflag, caPath, caKeyPath, dest, prefix)
@@ -92,48 +103,35 @@ func main() {
92103
pub,
93104
rootKey,
94105
rootCert,
95-
certutil.WithCNPrefix(prefix))
106+
certutil.WithCNPrefix(prefix),
107+
certutil.WithDNSNames(dnsNames...),
108+
certutil.WithClientCert(client))
96109
if err != nil {
97110
panic(fmt.Errorf("error generating child certificate: %w", err))
98111
}
99112

100-
savePair(dest, filePrefix+name, childPair)
101-
102-
if pass == "" {
103-
return
104-
}
105-
106-
fmt.Printf("passphrase present, encrypting \"%s\" certificate key\n",
107-
name)
108-
err = os.WriteFile(filePrefix+name+"-passphrase", []byte(pass), 0o600)
109-
if err != nil {
110-
panic(fmt.Errorf("error writing passphrase file: %w", err))
111-
}
112-
113-
key, err := x509.MarshalPKCS8PrivateKey(childCert.PrivateKey)
114-
if err != nil {
115-
panic(fmt.Errorf("error getting ecdh.PrivateKey from the child's private key: %w", err))
113+
if client {
114+
name = "client"
116115
}
116+
savePair(dest, filePrefix+name, childPair)
117117

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

132-
certKeyEnc := pem.EncodeToMemory(encPem)
126+
certKeyEnc, err := certutil.EncryptKey(childCert.PrivateKey, pass)
127+
if err != nil {
128+
panic(err)
129+
}
133130

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

0 commit comments

Comments
 (0)