mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
cf8b0bdbf9
- Refactored HTTP client from global cache to struct-based approach (global state was shared between tests, including what certificates to use) - Enhanced pkg/test/tls to support ECDSA and ED25519 key types - Replaced static certificate files with dynamic generation in golang tests - Fixed test cleanup issues and improved resource management This eliminates dependency on external cert generation scripts and improves test maintainability. Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
705 lines
22 KiB
Go
705 lines
22 KiB
Go
package tls_test
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
"zotregistry.dev/zot/v2/pkg/test/tls"
|
|
)
|
|
|
|
func TestGenerateCACert(t *testing.T) {
|
|
Convey("Generate CA certificate", t, func() {
|
|
certPEM, keyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Certificate should be valid PEM", func() {
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
So(certBlock, ShouldNotBeNil)
|
|
So(certBlock.Type, ShouldEqual, "CERTIFICATE")
|
|
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.IsCA, ShouldBeTrue)
|
|
So(cert.Subject.Organization[0], ShouldEqual, "Test CA")
|
|
})
|
|
|
|
Convey("Private key should be valid PEM", func() {
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
So(keyBlock.Type, ShouldEqual, "RSA PRIVATE KEY")
|
|
|
|
_, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGenerateServerCert(t *testing.T) {
|
|
Convey("Generate server certificate", t, func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("With hostname", func() {
|
|
hostname := "localhost"
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: hostname,
|
|
}
|
|
certPEM, keyPEM, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
So(certBlock, ShouldNotBeNil)
|
|
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.DNSNames, ShouldContain, hostname)
|
|
So(cert.ExtKeyUsage, ShouldContain, x509.ExtKeyUsageServerAuth)
|
|
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("With IP address", func() {
|
|
ipaddr := "127.0.0.1"
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: ipaddr,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(len(cert.IPAddresses), ShouldBeGreaterThan, 0)
|
|
So(cert.IPAddresses[0].String(), ShouldEqual, ipaddr)
|
|
})
|
|
|
|
Convey("With invalid CA PEM", func() {
|
|
invalidPEM := []byte("invalid pem")
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
}
|
|
_, _, err := tls.GenerateServerCert(invalidPEM, invalidPEM, opts)
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGenerateCertWithCN(t *testing.T) {
|
|
Convey("Generate client certificate with CN", t, func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
commonName := "test-client"
|
|
opts := &tls.CertificateOptions{
|
|
CommonName: commonName,
|
|
}
|
|
certPEM, keyPEM, err := tls.GenerateClientCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Certificate should have correct properties", func() {
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
So(certBlock, ShouldNotBeNil)
|
|
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.Subject.CommonName, ShouldEqual, commonName)
|
|
So(cert.ExtKeyUsage, ShouldContain, x509.ExtKeyUsageClientAuth)
|
|
})
|
|
|
|
Convey("Private key should be valid", func() {
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGenerateSelfSignedCertWithCN(t *testing.T) {
|
|
Convey("Generate self-signed certificate with CN", t, func() {
|
|
commonName := "self-signed-client"
|
|
opts := &tls.CertificateOptions{
|
|
CommonName: commonName,
|
|
}
|
|
certPEM, keyPEM, err := tls.GenerateClientSelfSignedCert(opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Certificate should be self-signed", func() {
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
So(certBlock, ShouldNotBeNil)
|
|
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.Subject.CommonName, ShouldEqual, commonName)
|
|
So(cert.Subject.String(), ShouldEqual, cert.Issuer.String())
|
|
})
|
|
|
|
Convey("Certificate should have correct validity period", func() {
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.NotAfter.After(time.Now().AddDate(0, 11, 0)), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Private key should be valid", func() {
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
|
|
_, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestApplyOptionsCoverage(t *testing.T) {
|
|
Convey("Test applyOptions with various options", t, func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Test with custom NotBefore and NotAfter", func() {
|
|
customNotBefore := time.Now().Add(-24 * time.Hour)
|
|
customNotAfter := time.Now().Add(2 * 365 * 24 * time.Hour)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
NotBefore: customNotBefore,
|
|
NotAfter: customNotAfter,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.NotBefore.Unix(), ShouldEqual, customNotBefore.Unix())
|
|
So(cert.NotAfter.Unix(), ShouldEqual, customNotAfter.Unix())
|
|
// Verify Hostname is encoded in DNSNames (since "localhost" is a DNS name)
|
|
So(cert.DNSNames, ShouldContain, "localhost")
|
|
})
|
|
|
|
Convey("Test with explicit IPAddresses", func() {
|
|
customIPs := []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("10.0.0.1")}
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
IPAddresses: customIPs,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(len(cert.IPAddresses), ShouldEqual, 2)
|
|
So(cert.IPAddresses[0].String(), ShouldEqual, "192.168.1.1")
|
|
So(cert.IPAddresses[1].String(), ShouldEqual, "10.0.0.1")
|
|
// Verify explicit IPAddresses are used (not the Hostname IP)
|
|
So(cert.IPAddresses, ShouldNotContain, net.ParseIP("127.0.0.1"))
|
|
// Verify Hostname DNS name is still added to DNSNames when no explicit DNSNames provided
|
|
So(cert.DNSNames, ShouldContain, "localhost")
|
|
})
|
|
|
|
Convey("Test with explicit DNSNames", func() {
|
|
customDNS := []string{"example.com", "test.example.com"}
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
DNSNames: customDNS,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(len(cert.DNSNames), ShouldEqual, 2)
|
|
So(cert.DNSNames, ShouldContain, "example.com")
|
|
So(cert.DNSNames, ShouldContain, "test.example.com")
|
|
// Verify explicit DNSNames take precedence - Hostname should NOT be added
|
|
So(cert.DNSNames, ShouldNotContain, "localhost")
|
|
})
|
|
|
|
Convey("Test with EmailAddresses", func() {
|
|
customEmails := []string{"user@example.com", "admin@example.com"}
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
EmailAddresses: customEmails,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(len(cert.EmailAddresses), ShouldEqual, 2)
|
|
So(cert.EmailAddresses, ShouldContain, "user@example.com")
|
|
So(cert.EmailAddresses, ShouldContain, "admin@example.com")
|
|
})
|
|
|
|
Convey("Test with all options combined", func() {
|
|
customNotBefore := time.Now().Add(-12 * time.Hour)
|
|
customNotAfter := time.Now().Add(365 * 24 * time.Hour)
|
|
customIPs := []net.IP{net.ParseIP("192.168.1.100")}
|
|
customDNS := []string{"combined.example.com"}
|
|
customEmails := []string{"combined@example.com"}
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
NotBefore: customNotBefore,
|
|
NotAfter: customNotAfter,
|
|
IPAddresses: customIPs,
|
|
DNSNames: customDNS,
|
|
EmailAddresses: customEmails,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.NotBefore.Unix(), ShouldEqual, customNotBefore.Unix())
|
|
So(cert.NotAfter.Unix(), ShouldEqual, customNotAfter.Unix())
|
|
So(len(cert.IPAddresses), ShouldEqual, 1)
|
|
So(cert.IPAddresses[0].String(), ShouldEqual, "192.168.1.100")
|
|
So(len(cert.DNSNames), ShouldEqual, 1)
|
|
So(cert.DNSNames[0], ShouldEqual, "combined.example.com")
|
|
So(len(cert.EmailAddresses), ShouldEqual, 1)
|
|
So(cert.EmailAddresses[0], ShouldEqual, "combined@example.com")
|
|
// Verify explicit DNSNames take precedence - Hostname should NOT be added
|
|
So(cert.DNSNames, ShouldNotContain, "localhost")
|
|
})
|
|
|
|
Convey("Test Hostname as IP address is encoded in IPAddresses", func() {
|
|
ipHostname := "192.168.2.50"
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: ipHostname,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
// Verify Hostname IP is in IPAddresses
|
|
So(len(cert.IPAddresses), ShouldBeGreaterThan, 0)
|
|
So(cert.IPAddresses[0].String(), ShouldEqual, ipHostname)
|
|
// Verify it's NOT in DNSNames
|
|
So(cert.DNSNames, ShouldNotContain, ipHostname)
|
|
})
|
|
|
|
Convey("Test Hostname as DNS name is encoded in DNSNames", func() {
|
|
dnsHostname := "example.test"
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: dnsHostname,
|
|
}
|
|
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
// Verify Hostname DNS is in DNSNames
|
|
So(cert.DNSNames, ShouldContain, dnsHostname)
|
|
})
|
|
|
|
Convey("Test with nil options (CA certificate)", func() {
|
|
// This tests the nil check in applyOptions
|
|
certPEM, _, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
certBlock, _ := pem.Decode(certPEM)
|
|
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
So(cert.IsCA, ShouldBeTrue)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestErrorPaths(t *testing.T) {
|
|
Convey("Test error paths", t, func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Test parseCA with invalid cert PEM", func() {
|
|
invalidCertPEM := []byte("not a valid PEM")
|
|
_, _, err := tls.GenerateServerCert(invalidCertPEM, caKeyPEM, &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
})
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test parseCA with invalid key PEM", func() {
|
|
invalidKeyPEM := []byte("not a valid PEM")
|
|
_, _, err := tls.GenerateServerCert(caCertPEM, invalidKeyPEM, &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
})
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test GenerateServerCertToFile with nil opts", func() {
|
|
tempDir := t.TempDir()
|
|
certPath := path.Join(tempDir, "server.crt")
|
|
keyPath := path.Join(tempDir, "server.key")
|
|
|
|
err := tls.GenerateServerCertToFile(caCertPEM, caKeyPEM, certPath, keyPath, nil)
|
|
So(err, ShouldEqual, tls.ErrHostnameRequired)
|
|
})
|
|
|
|
Convey("Test GenerateCACert with nil option", func() {
|
|
// Test when opts[0] == nil - should still work (uses default options)
|
|
certPEM, keyPEM, err := tls.GenerateCACert(nil)
|
|
So(err, ShouldBeNil)
|
|
So(certPEM, ShouldNotBeNil)
|
|
So(keyPEM, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test writeCertAndKeyToFile error when cert file write fails", func() {
|
|
tempDir := t.TempDir()
|
|
// Create a directory path instead of a file path to cause write error
|
|
certPath := tempDir // This is a directory, not a file
|
|
keyPath := path.Join(tempDir, "server.key")
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
}
|
|
err := tls.GenerateServerCertToFile(caCertPEM, caKeyPEM, certPath, keyPath, opts)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test writeCertAndKeyToFile error when key file write fails", func() {
|
|
tempDir := t.TempDir()
|
|
certPath := path.Join(tempDir, "server.crt")
|
|
// Create a directory path instead of a file path to cause write error
|
|
keyPath := tempDir // This is a directory, not a file
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
}
|
|
err := tls.GenerateServerCertToFile(caCertPEM, caKeyPEM, certPath, keyPath, opts)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test GenerateServerCertToFile error propagation", func() {
|
|
// Test that error from GenerateServerCert is propagated
|
|
tempDir := t.TempDir()
|
|
certPath := path.Join(tempDir, "server.crt")
|
|
keyPath := path.Join(tempDir, "server.key")
|
|
|
|
// Use invalid CA to trigger error in GenerateServerCert
|
|
invalidPEM := []byte("invalid")
|
|
err := tls.GenerateServerCertToFile(invalidPEM, invalidPEM, certPath, keyPath, &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test GenerateClientCert with invalid PEM", func() {
|
|
// Test that parseCA error is propagated from GenerateClientCert
|
|
invalidCertPEM := []byte("not a valid PEM")
|
|
_, _, err := tls.GenerateClientCert(invalidCertPEM, caKeyPEM, nil)
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test GenerateClientCertToFile error propagation", func() {
|
|
// Test that error from GenerateClientCert is propagated
|
|
tempDir := t.TempDir()
|
|
certPath := path.Join(tempDir, "client.crt")
|
|
keyPath := path.Join(tempDir, "client.key")
|
|
|
|
// Use invalid CA to trigger error in GenerateClientCert
|
|
invalidPEM := []byte("invalid")
|
|
err := tls.GenerateClientCertToFile(invalidPEM, invalidPEM, certPath, keyPath, nil)
|
|
So(err, ShouldNotBeNil)
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test GenerateIntermediateCACert with invalid PEM", func() {
|
|
// Test that parseCA error is propagated from GenerateIntermediateCACert
|
|
invalidCertPEM := []byte("not a valid PEM")
|
|
_, _, err := tls.GenerateIntermediateCACert(invalidCertPEM, caKeyPEM)
|
|
So(err, ShouldEqual, tls.ErrDecodeCAPEM)
|
|
})
|
|
|
|
Convey("Test GenerateClientSelfSignedCertToFile error propagation", func() {
|
|
// Test writeCertAndKeyToFile error path
|
|
tempDir := t.TempDir()
|
|
// Create a directory path instead of a file path to cause write error
|
|
certPath := tempDir // This is a directory, not a file
|
|
keyPath := path.Join(tempDir, "client.key")
|
|
|
|
err := tls.GenerateClientSelfSignedCertToFile(certPath, keyPath, nil)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test generateCertificate with invalid key type", func() {
|
|
// This tests the default case in generateCertificate switch
|
|
invalidKeyType := tls.KeyType("INVALID")
|
|
opts := &tls.CertificateOptions{
|
|
KeyType: invalidKeyType,
|
|
}
|
|
_, _, err := tls.GenerateCACert(opts)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test generateCertificate with ECDSA key type", func() {
|
|
// Test that ECDSA key generation works correctly
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
KeyType: tls.KeyTypeECDSA,
|
|
}
|
|
certPEM, keyPEM, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
So(certPEM, ShouldNotBeNil)
|
|
So(keyPEM, ShouldNotBeNil)
|
|
|
|
// Verify ECDSA key was generated
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
So(keyBlock.Type, ShouldEqual, "EC PRIVATE KEY")
|
|
})
|
|
|
|
Convey("Test generateCertificate with ED25519 key type", func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
KeyType: tls.KeyTypeED25519,
|
|
}
|
|
certPEM, keyPEM, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
So(certPEM, ShouldNotBeNil)
|
|
So(keyPEM, ShouldNotBeNil)
|
|
|
|
// Verify ED25519 key was generated
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
So(keyBlock.Type, ShouldEqual, "PRIVATE KEY")
|
|
})
|
|
|
|
Convey("Test parsePrivateKeyFromPEM with PKCS8 format", func() {
|
|
// Generate a certificate with ED25519 (uses PKCS8)
|
|
opts := &tls.CertificateOptions{
|
|
KeyType: tls.KeyTypeED25519,
|
|
}
|
|
_, keyPEM, err := tls.GenerateCACert(opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Parse it back - should work with PKCS8
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
|
|
// This tests the PKCS8 path in parsePrivateKeyFromPEM
|
|
_, err = x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test parsePrivateKeyFromPEM with EC SEC1 format", func() {
|
|
// Generate a certificate with ECDSA (uses SEC1)
|
|
opts := &tls.CertificateOptions{
|
|
KeyType: tls.KeyTypeECDSA,
|
|
}
|
|
_, keyPEM, err := tls.GenerateCACert(opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Parse it back - should work
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
So(keyBlock, ShouldNotBeNil)
|
|
|
|
// This tests the EC SEC1 path in parsePrivateKeyFromPEM
|
|
_, err = x509.ParseECPrivateKey(keyBlock.Bytes)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestExtractPublicKeyFromCert(t *testing.T) {
|
|
Convey("Test ExtractPublicKeyFromCert", t, func() {
|
|
caCertPEM, _, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Extract public key from valid certificate", func() {
|
|
publicKeyPEM, err := tls.ExtractPublicKeyFromCert(caCertPEM)
|
|
So(err, ShouldBeNil)
|
|
So(publicKeyPEM, ShouldNotBeNil)
|
|
|
|
// Verify it's valid PEM
|
|
block, _ := pem.Decode(publicKeyPEM)
|
|
So(block, ShouldNotBeNil)
|
|
So(block.Type, ShouldEqual, "PUBLIC KEY")
|
|
})
|
|
|
|
Convey("Extract public key from invalid PEM", func() {
|
|
invalidPEM := []byte("not a valid PEM")
|
|
_, err := tls.ExtractPublicKeyFromCert(invalidPEM)
|
|
So(err, ShouldEqual, tls.ErrFailedDecodeCertPEM)
|
|
})
|
|
|
|
Convey("Extract public key from server certificate", func() {
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
}
|
|
serverCertPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
publicKeyPEM, err := tls.ExtractPublicKeyFromCert(serverCertPEM)
|
|
So(err, ShouldBeNil)
|
|
So(publicKeyPEM, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Extract public key from ECDSA certificate", func() {
|
|
caOpts := &tls.CertificateOptions{
|
|
KeyType: tls.KeyTypeECDSA,
|
|
}
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert(caOpts)
|
|
So(err, ShouldBeNil)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
KeyType: tls.KeyTypeECDSA,
|
|
}
|
|
serverCertPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
publicKeyPEM, err := tls.ExtractPublicKeyFromCert(serverCertPEM)
|
|
So(err, ShouldBeNil)
|
|
So(publicKeyPEM, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Extract public key from ED25519 certificate", func() {
|
|
caOpts := &tls.CertificateOptions{
|
|
KeyType: tls.KeyTypeED25519,
|
|
}
|
|
caCertPEM, caKeyPEM, err := tls.GenerateCACert(caOpts)
|
|
So(err, ShouldBeNil)
|
|
|
|
opts := &tls.CertificateOptions{
|
|
Hostname: "localhost",
|
|
KeyType: tls.KeyTypeED25519,
|
|
}
|
|
serverCertPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
publicKeyPEM, err := tls.ExtractPublicKeyFromCert(serverCertPEM)
|
|
So(err, ShouldBeNil)
|
|
So(publicKeyPEM, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Extract public key from certificate with invalid certificate data", func() {
|
|
// Create a PEM block with invalid certificate data
|
|
invalidCertPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: []byte("invalid certificate data"),
|
|
})
|
|
|
|
_, err := tls.ExtractPublicKeyFromCert(invalidCertPEM)
|
|
So(err, ShouldNotBeNil)
|
|
So(err.Error(), ShouldContainSubstring, "failed to parse certificate")
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestExtractRSAPublicKeyPKCS1(t *testing.T) {
|
|
Convey("Test ExtractRSAPublicKeyPKCS1", t, func() {
|
|
_, keyPEM, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Extract RSA public key in PKCS1 format", func() {
|
|
publicKeyPEM, err := tls.ExtractRSAPublicKeyPKCS1(keyPEM)
|
|
So(err, ShouldBeNil)
|
|
So(publicKeyPEM, ShouldNotBeNil)
|
|
|
|
// Verify it's valid PEM
|
|
block, _ := pem.Decode(publicKeyPEM)
|
|
So(block, ShouldNotBeNil)
|
|
So(block.Type, ShouldEqual, "RSA PUBLIC KEY")
|
|
})
|
|
|
|
Convey("Extract RSA public key from invalid PEM", func() {
|
|
invalidPEM := []byte("not a valid PEM")
|
|
_, err := tls.ExtractRSAPublicKeyPKCS1(invalidPEM)
|
|
So(err, ShouldEqual, tls.ErrFailedDecodeKeyPEM)
|
|
})
|
|
|
|
Convey("Extract RSA public key from non-RSA key", func() {
|
|
opts := &tls.CertificateOptions{
|
|
KeyType: tls.KeyTypeECDSA,
|
|
}
|
|
_, ecdsaKeyPEM, err := tls.GenerateCACert(opts)
|
|
So(err, ShouldBeNil)
|
|
|
|
_, err = tls.ExtractRSAPublicKeyPKCS1(ecdsaKeyPEM)
|
|
So(err, ShouldNotBeNil)
|
|
So(err.Error(), ShouldContainSubstring, "private key is not RSA")
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestWriteCertificateChainToFile(t *testing.T) {
|
|
Convey("Test WriteCertificateChainToFile", t, func() {
|
|
Convey("Write certificate chain with multiple certificates", func() {
|
|
tempDir := t.TempDir()
|
|
chainPath := path.Join(tempDir, "chain.crt")
|
|
|
|
// Generate root CA
|
|
rootCACert, rootCAKey, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
// Generate intermediate CA
|
|
intermediateCACert, _, err := tls.GenerateIntermediateCACert(rootCACert, rootCAKey)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Generate leaf certificate
|
|
leafCert, _, err := tls.GenerateClientCert(rootCACert, rootCAKey, nil)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Write chain (leaf first, then intermediate)
|
|
err = tls.WriteCertificateChainToFile(chainPath, leafCert, intermediateCACert, rootCACert)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Verify file was created
|
|
chainData, err := os.ReadFile(chainPath)
|
|
So(err, ShouldBeNil)
|
|
So(len(chainData), ShouldBeGreaterThan, 0)
|
|
|
|
// Verify it contains all certificates
|
|
So(string(chainData), ShouldContainSubstring, "BEGIN CERTIFICATE")
|
|
})
|
|
|
|
Convey("Write certificate chain with no certificates", func() {
|
|
tempDir := t.TempDir()
|
|
chainPath := path.Join(tempDir, "chain.crt")
|
|
|
|
err := tls.WriteCertificateChainToFile(chainPath)
|
|
So(err, ShouldEqual, tls.ErrNoCertificatesProvided)
|
|
})
|
|
|
|
Convey("Write certificate chain with single certificate", func() {
|
|
tempDir := t.TempDir()
|
|
chainPath := path.Join(tempDir, "chain.crt")
|
|
|
|
cert, _, err := tls.GenerateCACert()
|
|
So(err, ShouldBeNil)
|
|
|
|
err = tls.WriteCertificateChainToFile(chainPath, cert)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Verify file was created
|
|
chainData, err := os.ReadFile(chainPath)
|
|
So(err, ShouldBeNil)
|
|
So(len(chainData), ShouldBeGreaterThan, 0)
|
|
})
|
|
})
|
|
}
|