internal/pkg/testing/certs/certs.go (153 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package certs
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"path/filepath"
"testing"
"time"
)
const ext = ".pem"
func CertToFile(t *testing.T, cert tls.Certificate, name string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, name+ext)
file, err := os.Create(path)
if err != nil {
t.Fatalf("unable to create file: %v", err)
}
if err := pem.Encode(file, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil {
t.Fatalf("unable to write cert: %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("unable to close file: %v", err)
}
return path
}
func KeyToFile(t *testing.T, cert tls.Certificate, name string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, name+ext)
file, err := os.Create(path)
if err != nil {
t.Fatalf("unable to create file: %v", err)
}
p, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey)
if err != nil {
t.Fatalf("unable to marshal private key: %v", err)
}
if err := pem.Encode(file, &pem.Block{Type: "PRIVATE KEY", Bytes: p}); err != nil {
t.Fatalf("unable to write key: %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("unable to close file: %v", err)
}
return path
}
// GenCA generates a CA for tests
// copied from elastic-agent-libs/transport/tlscommon/ca_pinning_test.go
func GenCA(t *testing.T) tls.Certificate {
t.Helper()
ca := &x509.Certificate{
SerialNumber: big.NewInt(2000),
Subject: pkix.Name{
CommonName: "localhost",
Organization: []string{"TESTING"},
Country: []string{"CANADA"},
Province: []string{"QUEBEC"},
Locality: []string{"MONTREAL"},
StreetAddress: []string{"testing road"},
PostalCode: []string{"HOH OHO"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(1 * time.Hour),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caKey, err := rsa.GenerateKey(rand.Reader, 2048) // less secure key for quicker testing.
if err != nil {
t.Fatalf("fail to generate RSA key: %v", err)
}
ca.SubjectKeyId = generateSubjectKeyID(caKey)
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caKey.PublicKey, caKey)
if err != nil {
t.Fatalf("fail to create certificate: %v", err)
}
leaf, err := x509.ParseCertificate(caBytes)
if err != nil {
t.Fatalf("fail to parse certificate: %v", err)
}
return tls.Certificate{
Certificate: [][]byte{caBytes},
PrivateKey: caKey,
Leaf: leaf,
}
}
func generateSubjectKeyID(pub crypto.PublicKey) []byte {
// SubjectKeyId generated using method 1 in RFC 7093, Section 2:
// 1) The keyIdentifier is composed of the leftmost 160-bits of the
// SHA-256 hash of the value of the BIT STRING subjectPublicKey
// (excluding the tag, length, and number of unused bits).
var publicKeyBytes []byte
switch publicKey := pub.(type) {
case *rsa.PublicKey:
publicKeyBytes = x509.MarshalPKCS1PublicKey(publicKey)
case *ecdsa.PublicKey:
//nolint:staticcheck // no alternative
publicKeyBytes = elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
}
h := sha256.Sum256(publicKeyBytes)
return h[:20]
}
// GenCert generates a test keypair and signs the cert with the passed CA.
// copied from elastic-agent-libs/transport/tlscommon/ca_pinning_test.go
func GenCert(t *testing.T, ca tls.Certificate) tls.Certificate {
t.Helper()
ts := time.Now().UTC()
cert := &x509.Certificate{
SerialNumber: big.NewInt(2000),
// Subject Alternative Name fields
IPAddresses: []net.IP{{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
DNSNames: []string{"localhost"},
Subject: pkix.Name{
CommonName: "fleet-server testing",
Organization: []string{"TESTING"},
Country: []string{"CANADA"},
Province: []string{"QUEBEC"},
Locality: []string{"MONTREAL"},
StreetAddress: []string{"testing road"},
PostalCode: []string{"HOH OHO"},
},
NotBefore: ts,
NotAfter: ts.Add(24 * time.Hour),
IsCA: false,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
}
certKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("fail to generate RSA key: %v", err)
}
certBytes, err := x509.CreateCertificate(
rand.Reader,
cert,
ca.Leaf,
&certKey.PublicKey,
ca.PrivateKey,
)
if err != nil {
t.Fatalf("fail to create signed certificate: %v", err)
}
leaf, err := x509.ParseCertificate(certBytes)
if err != nil {
t.Fatalf("fail to parse the certificate: %v", err)
}
return tls.Certificate{
Certificate: [][]byte{certBytes},
PrivateKey: certKey,
Leaf: leaf,
}
}