Skip to content

Commit eb7e16b

Browse files
authored
testing/certutil: add support to RSA (#231)
* add support to generate RSA certificates * add `-rsa` cli to generate RSA certificates * fix CI: use go.elastic.co/go-licence-detector defined on go.mod
1 parent 6c381fb commit eb7e16b

File tree

6 files changed

+311
-107
lines changed

6 files changed

+311
-107
lines changed

dev-tools/mage/gotool/noticer.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ var NoticeGenerator goNoticeGenerator = runGoNoticeGenerator
2626

2727
func runGoNoticeGenerator(opts ...ArgOpt) error {
2828
args := buildArgs(opts).build()
29-
return sh.RunV("go-licence-detector", args...)
29+
30+
return sh.RunV("go",
31+
append([]string{"run", "go.elastic.co/go-licence-detector"}, args...)...)
3032
}
3133

3234
func (goNoticeGenerator) Dependencies(path string) ArgOpt { return flagArg("-in", path) }

dev-tools/mage/install.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ import "github.com/elastic/elastic-agent-libs/dev-tools/mage/gotool"
2222
var (
2323
// GoLicenserImportPath controls the import path used to install go-licenser.
2424
GoLicenserImportPath = "github.com/elastic/go-licenser@latest"
25-
26-
// GoNoticeGeneratorImportPath controls the import path used to install go-licence-detector.
27-
GoNoticeGeneratorImportPath = "go.elastic.co/go-licence-detector@latest"
2825
)
2926

3027
// InstallGoLicenser target installs go-licenser
@@ -33,10 +30,3 @@ func InstallGoLicenser() error {
3330
gotool.Install.Package(GoLicenserImportPath),
3431
)
3532
}
36-
37-
// InstallGoNoticeGen target installs go-licenser
38-
func InstallGoNoticeGen() error {
39-
return gotool.Install(
40-
gotool.Install.Package(GoNoticeGeneratorImportPath),
41-
)
42-
}

dev-tools/mage/notice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
)
2828

2929
func GenerateNotice(overrides, rules, noticeTemplate string) error {
30-
mg.Deps(InstallGoNoticeGen, Deps.CheckModuleTidy)
30+
mg.Deps(Deps.CheckModuleTidy)
3131

3232
err := gotool.Mod.Download(gotool.Download.All())
3333
if err != nil {

testing/certutil/certutil.go

Lines changed: 157 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"crypto/ecdsa"
2424
"crypto/elliptic"
2525
"crypto/rand"
26+
"crypto/rsa"
2627
"crypto/tls"
2728
"crypto/x509"
2829
"crypto/x509/pkix"
@@ -39,81 +40,35 @@ type Pair struct {
3940
Key []byte
4041
}
4142

42-
// NewRootCA generates a new x509 Certificate and returns:
43+
// NewRootCA generates a new x509 Certificate using ECDSA P-384 and returns:
4344
// - the private key
4445
// - the certificate
45-
// - the certificate in PEM format as a byte slice.
46+
// - the certificate and its key in PEM format as a byte slice.
4647
//
4748
// If any error occurs during the generation process, a non-nil error is returned.
48-
func NewRootCA() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
49+
func NewRootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
4950
rootKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
5051
if err != nil {
5152
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
5253
}
5354

54-
notBefore, notAfter := makeNotBeforeAndAfter()
55-
56-
rootTemplate := x509.Certificate{
57-
SerialNumber: big.NewInt(1653),
58-
Subject: pkix.Name{
59-
Country: []string{"Gallifrey"},
60-
Locality: []string{"The Capitol"},
61-
OrganizationalUnit: []string{"Time Lords"},
62-
Organization: []string{"High Council of the Time Lords"},
63-
CommonName: "High Council",
64-
},
65-
NotBefore: notBefore,
66-
NotAfter: notAfter,
67-
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
68-
BasicConstraintsValid: true,
69-
IsCA: true,
70-
}
71-
72-
rootCertRawBytes, err := x509.CreateCertificate(
73-
rand.Reader, &rootTemplate, &rootTemplate, &rootKey.PublicKey, rootKey)
74-
if err != nil {
75-
return nil, nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
76-
}
77-
78-
rootPrivKeyDER, err := x509.MarshalECPrivateKey(rootKey)
79-
if err != nil {
80-
return nil, nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
81-
}
82-
83-
// PEM private key
84-
var rootPrivBytesOut []byte
85-
rootPrivateKeyBuff := bytes.NewBuffer(rootPrivBytesOut)
86-
err = pem.Encode(rootPrivateKeyBuff, &pem.Block{
87-
Type: "EC PRIVATE KEY", Bytes: rootPrivKeyDER})
88-
if err != nil {
89-
return nil, nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
90-
}
91-
92-
// PEM certificate
93-
var rootCertBytesOut []byte
94-
rootCertPemBuff := bytes.NewBuffer(rootCertBytesOut)
95-
err = pem.Encode(rootCertPemBuff, &pem.Block{
96-
Type: "CERTIFICATE", Bytes: rootCertRawBytes})
97-
if err != nil {
98-
return nil, nil, Pair{}, fmt.Errorf("could not pem.Encode certificate: %w", err)
99-
}
100-
101-
// tls.Certificate
102-
rootTLSCert, err := tls.X509KeyPair(
103-
rootCertPemBuff.Bytes(), rootPrivateKeyBuff.Bytes())
104-
if err != nil {
105-
return nil, nil, Pair{}, fmt.Errorf("could not create key pair: %w", err)
106-
}
55+
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
56+
return rootKey, cert, pair, err
57+
}
10758

108-
rootCACert, err := x509.ParseCertificate(rootTLSCert.Certificate[0])
59+
// NewRSARootCA generates a new x509 Certificate using RSA with a 2048-bit key and returns:
60+
// - the private key
61+
// - the certificate
62+
// - the certificate and its key in PEM format as a byte slice.
63+
//
64+
// If any error occurs during the generation process, a non-nil error is returned.
65+
func NewRSARootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
66+
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
10967
if err != nil {
110-
return nil, nil, Pair{}, fmt.Errorf("could not parse certificate: %w", err)
68+
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
11169
}
112-
113-
return rootKey, rootCACert, Pair{
114-
Cert: rootCertPemBuff.Bytes(),
115-
Key: rootPrivateKeyBuff.Bytes(),
116-
}, nil
70+
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
71+
return rootKey, cert, pair, err
11772
}
11873

11974
// GenerateChildCert generates a x509 Certificate as a child of caCert and
@@ -123,7 +78,13 @@ func NewRootCA() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
12378
// - the certificate and private key as a tls.Certificate
12479
//
12580
// If any error occurs during the generation process, a non-nil error is returned.
126-
func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, caCert *x509.Certificate) (*tls.Certificate, Pair, error) {
81+
func GenerateChildCert(
82+
name string,
83+
ips []net.IP,
84+
priv crypto.PrivateKey,
85+
pub crypto.PublicKey,
86+
caPrivKey crypto.PrivateKey,
87+
caCert *x509.Certificate) (*tls.Certificate, Pair, error) {
12788

12889
notBefore, notAfter := makeNotBeforeAndAfter()
12990

@@ -143,27 +104,22 @@ func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, c
143104
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
144105
}
145106

146-
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
147-
if err != nil {
148-
return nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
149-
}
150-
151107
certRawBytes, err := x509.CreateCertificate(
152-
rand.Reader, certTemplate, caCert, &privateKey.PublicKey, caPrivKey)
108+
rand.Reader, certTemplate, caCert, pub, caPrivKey)
153109
if err != nil {
154110
return nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
155111
}
156112

157-
privateKeyDER, err := x509.MarshalECPrivateKey(privateKey)
113+
privateKeyDER, err := x509.MarshalPKCS8PrivateKey(priv)
158114
if err != nil {
159115
return nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
160116
}
161117

162118
// PEM private key
163119
var privBytesOut []byte
164120
privateKeyBuff := bytes.NewBuffer(privBytesOut)
165-
err = pem.Encode(privateKeyBuff, &pem.Block{
166-
Type: "EC PRIVATE KEY", Bytes: privateKeyDER})
121+
err = pem.Encode(privateKeyBuff,
122+
&pem.Block{Type: keyBlockType(priv), Bytes: privateKeyDER})
167123
if err != nil {
168124
return nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
169125
}
@@ -191,28 +147,151 @@ func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, c
191147
}, nil
192148
}
193149

194-
// NewRootAndChildCerts returns a root CA and a child certificate and their keys
195-
// for "localhost" and "127.0.0.1".
150+
// NewRootAndChildCerts returns an ECDSA (P-384) root CA and a child certificate
151+
// and their keys for "localhost" and "127.0.0.1".
196152
func NewRootAndChildCerts() (Pair, Pair, error) {
197153
rootKey, rootCACert, rootPair, err := NewRootCA()
198154
if err != nil {
199155
return Pair{}, Pair{}, fmt.Errorf("could not generate root CA: %w", err)
200156
}
201157

158+
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
159+
if err != nil {
160+
return Pair{}, Pair{}, fmt.Errorf("could not create private key: %w", err)
161+
}
162+
163+
childPair, err := defaultChildCert(rootKey, priv, &priv.PublicKey, rootCACert)
164+
return rootPair, childPair, err
165+
}
166+
167+
// NewRSARootAndChildCerts returns an RSA (2048-bit) root CA and a child
168+
// certificate and their keys for "localhost" and "127.0.0.1".
169+
func NewRSARootAndChildCerts() (Pair, Pair, error) {
170+
rootKey, rootCACert, rootPair, err := NewRSARootCA()
171+
if err != nil {
172+
return Pair{}, Pair{}, fmt.Errorf("could not generate RSA root CA: %w", err)
173+
}
174+
175+
priv, err := rsa.GenerateKey(rand.Reader, 2048)
176+
if err != nil {
177+
return Pair{}, Pair{}, fmt.Errorf("could not create RSA private key: %w", err)
178+
}
179+
180+
childPair, err := defaultChildCert(rootKey, priv, &priv.PublicKey, rootCACert)
181+
return rootPair, childPair, err
182+
}
183+
184+
// newRootCert creates a new self-signed root certificate using the provided
185+
// private key and public key.
186+
// It returns:
187+
// - the private key,
188+
// - the certificate,
189+
// - a Pair containing the certificate and private key in PEM format.
190+
//
191+
// If an error occurs during certificate creation, it returns a non-nil error.
192+
func newRootCert(priv crypto.PrivateKey, pub crypto.PublicKey) (*x509.Certificate, Pair, error) {
193+
notBefore, notAfter := makeNotBeforeAndAfter()
194+
195+
rootTemplate := x509.Certificate{
196+
SerialNumber: big.NewInt(1653),
197+
Subject: pkix.Name{
198+
Country: []string{"Gallifrey"},
199+
Locality: []string{"The Capitol"},
200+
OrganizationalUnit: []string{"Time Lords"},
201+
Organization: []string{"High Council of the Time Lords"},
202+
CommonName: "High Council",
203+
},
204+
NotBefore: notBefore,
205+
NotAfter: notAfter,
206+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
207+
BasicConstraintsValid: true,
208+
IsCA: true,
209+
}
210+
211+
rootCertRawBytes, err := x509.CreateCertificate(
212+
rand.Reader, &rootTemplate, &rootTemplate, pub, priv)
213+
if err != nil {
214+
return nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
215+
}
216+
217+
rootPrivKeyDER, err := x509.MarshalPKCS8PrivateKey(priv)
218+
if err != nil {
219+
return nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
220+
}
221+
222+
// PEM private key
223+
var rootPrivBytesOut []byte
224+
rootPrivateKeyBuff := bytes.NewBuffer(rootPrivBytesOut)
225+
err = pem.Encode(rootPrivateKeyBuff,
226+
&pem.Block{Type: keyBlockType(priv), Bytes: rootPrivKeyDER})
227+
if err != nil {
228+
return nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
229+
}
230+
231+
// PEM certificate
232+
var rootCertBytesOut []byte
233+
rootCertPemBuff := bytes.NewBuffer(rootCertBytesOut)
234+
err = pem.Encode(rootCertPemBuff,
235+
&pem.Block{Type: "CERTIFICATE", Bytes: rootCertRawBytes})
236+
if err != nil {
237+
return nil, Pair{}, fmt.Errorf("could not pem.Encode certificate: %w", err)
238+
}
239+
240+
// tls.Certificate
241+
rootTLSCert, err := tls.X509KeyPair(
242+
rootCertPemBuff.Bytes(), rootPrivateKeyBuff.Bytes())
243+
if err != nil {
244+
return nil, Pair{}, fmt.Errorf("could not create key pair: %w", err)
245+
}
246+
247+
rootCACert, err := x509.ParseCertificate(rootTLSCert.Certificate[0])
248+
if err != nil {
249+
return nil, Pair{}, fmt.Errorf("could not parse certificate: %w", err)
250+
}
251+
252+
return rootCACert, Pair{
253+
Cert: rootCertPemBuff.Bytes(),
254+
Key: rootPrivateKeyBuff.Bytes(),
255+
}, nil
256+
}
257+
258+
// defaultChildCert generates a child certificate for localhost and 127.0.0.1.
259+
// It returns the certificate and its key as a Pair and an error if any happens.
260+
func defaultChildCert(
261+
rootPriv,
262+
priv crypto.PrivateKey,
263+
pub crypto.PublicKey,
264+
rootCACert *x509.Certificate) (Pair, error) {
202265
_, childPair, err :=
203266
GenerateChildCert(
204267
"localhost",
205268
[]net.IP{net.ParseIP("127.0.0.1")},
206-
rootKey,
269+
priv,
270+
pub,
271+
rootPriv,
207272
rootCACert)
208273
if err != nil {
209-
return Pair{}, Pair{}, fmt.Errorf(
274+
return Pair{}, fmt.Errorf(
210275
"could not generate child TLS certificate CA: %w", err)
211276
}
277+
return childPair, nil
278+
}
212279

213-
return rootPair, childPair, nil
280+
// keyBlockType returns the correct PEM block type for the given private key.
281+
func keyBlockType(priv crypto.PrivateKey) string {
282+
switch priv.(type) {
283+
case *rsa.PrivateKey:
284+
return "RSA PRIVATE KEY"
285+
case *ecdsa.PrivateKey:
286+
return "EC PRIVATE KEY"
287+
default:
288+
panic(fmt.Errorf("unsupported private key type: %T", priv))
289+
}
214290
}
215291

292+
// makeNotBeforeAndAfter returns:
293+
// - notBefore: 1 minute before now
294+
// - notAfter: 7 days after now
216295
func makeNotBeforeAndAfter() (time.Time, time.Time) {
217296
now := time.Now()
218297
notBefore := now.Add(-1 * time.Minute)

0 commit comments

Comments
 (0)