Files
zot/pkg/test/tls/tls_test.go
T
Andrei Aaron cf8b0bdbf9 refactor: enhance TLS cert generation and refactor HTTP client architecture (#3638)
- 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>
2025-12-13 09:47:32 +02:00

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)
})
})
}