2022-03-17 09:14:40 +00:00
|
|
|
package certs
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-29 18:34:56 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
|
"crypto/ed25519"
|
|
|
|
|
"crypto/elliptic"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/rsa"
|
|
|
|
|
"crypto/x509"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"strings"
|
2022-03-17 09:14:40 +00:00
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-03-29 18:34:56 +00:00
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
2022-03-17 09:14:40 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-29 18:34:56 +00:00
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 09:14:40 +00:00
|
|
|
func TestGenerateTLSConfig(t *testing.T) {
|
2026-03-29 18:34:56 +00:00
|
|
|
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
|
2022-03-17 09:14:40 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-29 18:34:56 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
})
|
2022-03-17 09:14:40 +00:00
|
|
|
}
|