certs/Certificate_test.go

157 lines
4.5 KiB
Go

package certs
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"net"
"os"
"os/exec"
"strings"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
const testOrganization = "yoorie.de"
func parseLeafCertificate(t *testing.T, gc *GenerateCertificate) *x509.Certificate {
t.Helper()
result, err := gc.GenerateTLSConfig()
if err != nil {
t.Fatalf("GenerateTLSConfig returned error: %v", err)
}
if result == nil || len(result.Certificates) != 1 {
t.Fatalf("expected one certificate, got %#v", result)
}
if len(result.Certificates[0].Certificate) == 0 {
t.Fatal("expected leaf certificate bytes")
}
leaf, err := x509.ParseCertificate(result.Certificates[0].Certificate[0])
if err != nil {
t.Fatalf("ParseCertificate returned error: %v", err)
}
return leaf
}
func TestPublicKey(t *testing.T) {
Convey("publicKey returns the matching public key type", t, func() {
gc := &GenerateCertificate{}
rsaKey, err := rsa.GenerateKey(rand.Reader, 1024)
So(err, ShouldBeNil)
So(gc.publicKey(rsaKey), ShouldResemble, &rsaKey.PublicKey)
ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
So(err, ShouldBeNil)
So(gc.publicKey(ecdsaKey), ShouldResemble, &ecdsaKey.PublicKey)
_, ed25519Key, err := ed25519.GenerateKey(rand.Reader)
So(err, ShouldBeNil)
So(gc.publicKey(ed25519Key), ShouldResemble, ed25519Key.Public().(ed25519.PublicKey))
So(gc.publicKey(struct{}{}), ShouldBeNil)
})
}
func TestGenerateTLSConfig(t *testing.T) {
Convey("GenerateTLSConfig creates valid certificates for supported key types", t, func() {
Convey("RSA certificates include configured SANs and CA settings", func() {
validFrom := "Jan 2 15:04:05 2006"
leaf := parseLeafCertificate(t, &GenerateCertificate{
Organization: testOrganization,
Host: "127.0.0.1,example.com",
ValidFrom: validFrom,
ValidFor: 24 * time.Hour,
IsCA: true,
RSABits: 1024,
})
expectedNotBefore, err := time.Parse("Jan 2 15:04:05 2006", validFrom)
So(err, ShouldBeNil)
So(leaf.PublicKeyAlgorithm, ShouldEqual, x509.RSA)
So(leaf.Subject.Organization, ShouldResemble, []string{testOrganization})
So(leaf.IsCA, ShouldBeTrue)
So(leaf.NotBefore.Equal(expectedNotBefore), ShouldBeTrue)
So(leaf.NotAfter.Sub(leaf.NotBefore), ShouldEqual, 24*time.Hour)
So(leaf.DNSNames, ShouldResemble, []string{"example.com"})
So(len(leaf.IPAddresses), ShouldEqual, 1)
So(leaf.IPAddresses[0].Equal(net.ParseIP("127.0.0.1")), ShouldBeTrue)
})
Convey("Ed25519 certificates are supported when explicitly requested", func() {
leaf := parseLeafCertificate(t, &GenerateCertificate{
Organization: testOrganization,
Host: "localhost",
ValidFor: 12 * time.Hour,
Ed25519Key: true,
})
So(leaf.PublicKeyAlgorithm, ShouldEqual, x509.Ed25519)
So(leaf.IsCA, ShouldBeFalse)
So(leaf.DNSNames, ShouldResemble, []string{"localhost"})
})
Convey("all supported ECDSA curves generate server certificates", func() {
curves := []string{"P224", "P256", "P384", "P521"}
for _, curve := range curves {
leaf := parseLeafCertificate(t, &GenerateCertificate{
Organization: testOrganization,
Host: "certs.example.test",
ValidFor: 6 * time.Hour,
EcdsaCurve: curve,
})
So(leaf.PublicKeyAlgorithm, ShouldEqual, x509.ECDSA)
So(leaf.DNSNames, ShouldResemble, []string{"certs.example.test"})
}
})
})
}
func TestGenerateTLSConfigFatalScenarios(t *testing.T) {
if testCase := os.Getenv("CERTS_FATAL_TEST_CASE"); testCase != "" {
gc := &GenerateCertificate{RSABits: 1024, ValidFor: time.Hour}
switch testCase {
case "invalid-curve":
gc.EcdsaCurve = "invalid"
case "invalid-valid-from":
gc.ValidFrom = "not-a-date"
default:
t.Fatalf("unknown fatal test case: %s", testCase)
}
_, _ = gc.GenerateTLSConfig()
return
}
Convey("GenerateTLSConfig terminates on invalid input", t, func() {
testCases := []struct {
name string
message string
}{
{name: "invalid-curve", message: "Unrecognized elliptic curve"},
{name: "invalid-valid-from", message: "Failed to parse creation date"},
}
for _, testCase := range testCases {
cmd := exec.Command(os.Args[0], "-test.run", "^TestGenerateTLSConfigFatalScenarios$")
cmd.Env = append(os.Environ(), "CERTS_FATAL_TEST_CASE="+testCase.name)
output, err := cmd.CombinedOutput()
So(err, ShouldNotBeNil)
So(strings.Contains(string(output), testCase.message), ShouldBeTrue)
}
})
}