Skip to content

Commit

Permalink
testing/certutil: add support to RSA (#231)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AndersonQ authored Oct 2, 2024
1 parent 6c381fb commit eb7e16b
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 107 deletions.
4 changes: 3 additions & 1 deletion dev-tools/mage/gotool/noticer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ var NoticeGenerator goNoticeGenerator = runGoNoticeGenerator

func runGoNoticeGenerator(opts ...ArgOpt) error {
args := buildArgs(opts).build()
return sh.RunV("go-licence-detector", args...)

return sh.RunV("go",
append([]string{"run", "go.elastic.co/go-licence-detector"}, args...)...)
}

func (goNoticeGenerator) Dependencies(path string) ArgOpt { return flagArg("-in", path) }
Expand Down
10 changes: 0 additions & 10 deletions dev-tools/mage/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ import "github.com/elastic/elastic-agent-libs/dev-tools/mage/gotool"
var (
// GoLicenserImportPath controls the import path used to install go-licenser.
GoLicenserImportPath = "github.com/elastic/go-licenser@latest"

// GoNoticeGeneratorImportPath controls the import path used to install go-licence-detector.
GoNoticeGeneratorImportPath = "go.elastic.co/go-licence-detector@latest"
)

// InstallGoLicenser target installs go-licenser
Expand All @@ -33,10 +30,3 @@ func InstallGoLicenser() error {
gotool.Install.Package(GoLicenserImportPath),
)
}

// InstallGoNoticeGen target installs go-licenser
func InstallGoNoticeGen() error {
return gotool.Install(
gotool.Install.Package(GoNoticeGeneratorImportPath),
)
}
2 changes: 1 addition & 1 deletion dev-tools/mage/notice.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
)

func GenerateNotice(overrides, rules, noticeTemplate string) error {
mg.Deps(InstallGoNoticeGen, Deps.CheckModuleTidy)
mg.Deps(Deps.CheckModuleTidy)

err := gotool.Mod.Download(gotool.Download.All())
if err != nil {
Expand Down
235 changes: 157 additions & 78 deletions testing/certutil/certutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
Expand All @@ -39,81 +40,35 @@ type Pair struct {
Key []byte
}

// NewRootCA generates a new x509 Certificate and returns:
// NewRootCA generates a new x509 Certificate using ECDSA P-384 and returns:
// - the private key
// - the certificate
// - the certificate in PEM format as a byte slice.
// - 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() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
func NewRootCA() (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)
}

notBefore, notAfter := makeNotBeforeAndAfter()

rootTemplate := x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
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,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
}

rootCertRawBytes, err := x509.CreateCertificate(
rand.Reader, &rootTemplate, &rootTemplate, &rootKey.PublicKey, rootKey)
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
}

rootPrivKeyDER, err := x509.MarshalECPrivateKey(rootKey)
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
}

// PEM private key
var rootPrivBytesOut []byte
rootPrivateKeyBuff := bytes.NewBuffer(rootPrivBytesOut)
err = pem.Encode(rootPrivateKeyBuff, &pem.Block{
Type: "EC PRIVATE KEY", Bytes: rootPrivKeyDER})
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
}

// PEM certificate
var rootCertBytesOut []byte
rootCertPemBuff := bytes.NewBuffer(rootCertBytesOut)
err = pem.Encode(rootCertPemBuff, &pem.Block{
Type: "CERTIFICATE", Bytes: rootCertRawBytes})
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not pem.Encode certificate: %w", err)
}

// tls.Certificate
rootTLSCert, err := tls.X509KeyPair(
rootCertPemBuff.Bytes(), rootPrivateKeyBuff.Bytes())
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not create key pair: %w", err)
}
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
return rootKey, cert, pair, err
}

rootCACert, err := x509.ParseCertificate(rootTLSCert.Certificate[0])
// NewRSARootCA generates a new x509 Certificate using RSA with a 2048-bit key 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 NewRSARootCA() (crypto.PrivateKey, *x509.Certificate, Pair, error) {
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, Pair{}, fmt.Errorf("could not parse certificate: %w", err)
return nil, nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
}

return rootKey, rootCACert, Pair{
Cert: rootCertPemBuff.Bytes(),
Key: rootPrivateKeyBuff.Bytes(),
}, nil
cert, pair, err := newRootCert(rootKey, &rootKey.PublicKey)
return rootKey, cert, pair, err
}

// GenerateChildCert generates a x509 Certificate as a child of caCert and
Expand All @@ -123,7 +78,13 @@ func NewRootCA() (*ecdsa.PrivateKey, *x509.Certificate, Pair, error) {
// - the certificate and private key as a tls.Certificate
//
// 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,
priv crypto.PrivateKey,
pub crypto.PublicKey,
caPrivKey crypto.PrivateKey,
caCert *x509.Certificate) (*tls.Certificate, Pair, error) {

notBefore, notAfter := makeNotBeforeAndAfter()

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

privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create private key: %w", err)
}

certRawBytes, err := x509.CreateCertificate(
rand.Reader, certTemplate, caCert, &privateKey.PublicKey, caPrivKey)
rand.Reader, certTemplate, caCert, pub, caPrivKey)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
}

privateKeyDER, err := x509.MarshalECPrivateKey(privateKey)
privateKeyDER, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
}

// PEM private key
var privBytesOut []byte
privateKeyBuff := bytes.NewBuffer(privBytesOut)
err = pem.Encode(privateKeyBuff, &pem.Block{
Type: "EC PRIVATE KEY", Bytes: privateKeyDER})
err = pem.Encode(privateKeyBuff,
&pem.Block{Type: keyBlockType(priv), Bytes: privateKeyDER})
if err != nil {
return nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
}
Expand Down Expand Up @@ -191,28 +147,151 @@ func GenerateChildCert(name string, ips []net.IP, caPrivKey crypto.PrivateKey, c
}, nil
}

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

priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return Pair{}, Pair{}, fmt.Errorf("could not create private key: %w", err)
}

childPair, err := defaultChildCert(rootKey, priv, &priv.PublicKey, rootCACert)
return rootPair, childPair, err
}

// NewRSARootAndChildCerts returns an RSA (2048-bit) root CA and a child
// certificate and their keys for "localhost" and "127.0.0.1".
func NewRSARootAndChildCerts() (Pair, Pair, error) {
rootKey, rootCACert, rootPair, err := NewRSARootCA()
if err != nil {
return Pair{}, Pair{}, fmt.Errorf("could not generate RSA root CA: %w", err)
}

priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return Pair{}, Pair{}, fmt.Errorf("could not create RSA private key: %w", err)
}

childPair, err := defaultChildCert(rootKey, priv, &priv.PublicKey, rootCACert)
return rootPair, childPair, err
}

// newRootCert creates a new self-signed root certificate using the provided
// private key and public key.
// It returns:
// - the private key,
// - the certificate,
// - 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) {
notBefore, notAfter := makeNotBeforeAndAfter()

rootTemplate := x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
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,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
}

rootCertRawBytes, err := x509.CreateCertificate(
rand.Reader, &rootTemplate, &rootTemplate, pub, priv)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create CA: %w", err)
}

rootPrivKeyDER, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, Pair{}, fmt.Errorf("could not marshal private key: %w", err)
}

// PEM private key
var rootPrivBytesOut []byte
rootPrivateKeyBuff := bytes.NewBuffer(rootPrivBytesOut)
err = pem.Encode(rootPrivateKeyBuff,
&pem.Block{Type: keyBlockType(priv), Bytes: rootPrivKeyDER})
if err != nil {
return nil, Pair{}, fmt.Errorf("could not pem.Encode private key: %w", err)
}

// PEM certificate
var rootCertBytesOut []byte
rootCertPemBuff := bytes.NewBuffer(rootCertBytesOut)
err = pem.Encode(rootCertPemBuff,
&pem.Block{Type: "CERTIFICATE", Bytes: rootCertRawBytes})
if err != nil {
return nil, Pair{}, fmt.Errorf("could not pem.Encode certificate: %w", err)
}

// tls.Certificate
rootTLSCert, err := tls.X509KeyPair(
rootCertPemBuff.Bytes(), rootPrivateKeyBuff.Bytes())
if err != nil {
return nil, Pair{}, fmt.Errorf("could not create key pair: %w", err)
}

rootCACert, err := x509.ParseCertificate(rootTLSCert.Certificate[0])
if err != nil {
return nil, Pair{}, fmt.Errorf("could not parse certificate: %w", err)
}

return rootCACert, Pair{
Cert: rootCertPemBuff.Bytes(),
Key: rootPrivateKeyBuff.Bytes(),
}, nil
}

// 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(
rootPriv,
priv crypto.PrivateKey,
pub crypto.PublicKey,
rootCACert *x509.Certificate) (Pair, error) {
_, childPair, err :=
GenerateChildCert(
"localhost",
[]net.IP{net.ParseIP("127.0.0.1")},
rootKey,
priv,
pub,
rootPriv,
rootCACert)
if err != nil {
return Pair{}, Pair{}, fmt.Errorf(
return Pair{}, fmt.Errorf(
"could not generate child TLS certificate CA: %w", err)
}
return childPair, nil
}

return rootPair, childPair, nil
// keyBlockType returns the correct PEM block type for the given private key.
func keyBlockType(priv crypto.PrivateKey) string {
switch priv.(type) {
case *rsa.PrivateKey:
return "RSA PRIVATE KEY"
case *ecdsa.PrivateKey:
return "EC PRIVATE KEY"
default:
panic(fmt.Errorf("unsupported private key type: %T", priv))
}
}

// makeNotBeforeAndAfter returns:
// - notBefore: 1 minute before now
// - notAfter: 7 days after now
func makeNotBeforeAndAfter() (time.Time, time.Time) {
now := time.Now()
notBefore := now.Add(-1 * time.Minute)
Expand Down
Loading

0 comments on commit eb7e16b

Please sign in to comment.