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

testing/certutil: add support to RSA #231

Merged
merged 2 commits into from
Oct 2, 2024
Merged
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
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
Loading