Files
Charon/backend/internal/services/certificate_validator_coverage_test.go

325 lines
10 KiB
Go

package services
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"software.sslmate.com/src/go-pkcs12"
)
// --- parsePFXInput ---
func TestParsePFXInput(t *testing.T) {
cert, priv, _, _ := makeRSACertAndKey(t, "pfx.test", time.Now().Add(time.Hour))
pfxData, err := pkcs12.Modern.Encode(priv, cert, nil, pkcs12.DefaultPassword)
require.NoError(t, err)
t.Run("valid PFX", func(t *testing.T) {
parsed, err := parsePFXInput(pfxData, pkcs12.DefaultPassword)
require.NoError(t, err)
assert.NotNil(t, parsed.Leaf)
assert.NotNil(t, parsed.PrivateKey)
assert.Equal(t, FormatPFX, parsed.Format)
assert.Contains(t, parsed.CertPEM, "BEGIN CERTIFICATE")
assert.Contains(t, parsed.KeyPEM, "PRIVATE KEY")
})
t.Run("PFX with chain", func(t *testing.T) {
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
caTmpl := &x509.Certificate{
SerialNumber: big.NewInt(100),
Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign,
}
caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
require.NoError(t, err)
caCert, err := x509.ParseCertificate(caDER)
require.NoError(t, err)
pfxWithChain, err := pkcs12.Modern.Encode(priv, cert, []*x509.Certificate{caCert}, pkcs12.DefaultPassword)
require.NoError(t, err)
parsed, err := parsePFXInput(pfxWithChain, pkcs12.DefaultPassword)
require.NoError(t, err)
assert.NotEmpty(t, parsed.ChainPEM)
assert.Contains(t, parsed.ChainPEM, "BEGIN CERTIFICATE")
})
t.Run("invalid PFX data", func(t *testing.T) {
_, err := parsePFXInput([]byte("not-pfx"), "password")
assert.Error(t, err)
assert.Contains(t, err.Error(), "PFX")
})
t.Run("wrong password", func(t *testing.T) {
_, err := parsePFXInput(pfxData, "wrong-password")
assert.Error(t, err)
})
}
// --- parseDERInput ---
func TestParseDERInput(t *testing.T) {
cert, priv, _, keyPEM := makeRSACertAndKey(t, "der.test", time.Now().Add(time.Hour))
t.Run("DER cert only", func(t *testing.T) {
parsed, err := parseDERInput(cert.Raw, nil)
require.NoError(t, err)
assert.NotNil(t, parsed.Leaf)
assert.Equal(t, FormatDER, parsed.Format)
assert.Contains(t, parsed.CertPEM, "BEGIN CERTIFICATE")
assert.Nil(t, parsed.PrivateKey)
})
t.Run("DER cert with PEM key", func(t *testing.T) {
parsed, err := parseDERInput(cert.Raw, keyPEM)
require.NoError(t, err)
assert.NotNil(t, parsed.PrivateKey)
assert.Contains(t, parsed.KeyPEM, "PRIVATE KEY")
})
t.Run("DER cert with DER PKCS8 key", func(t *testing.T) {
derKey, err := x509.MarshalPKCS8PrivateKey(priv)
require.NoError(t, err)
parsed, err := parseDERInput(cert.Raw, derKey)
require.NoError(t, err)
assert.NotNil(t, parsed.PrivateKey)
})
t.Run("DER cert with DER EC key", func(t *testing.T) {
ecCert, ecPriv, _, _ := makeECDSACertAndKey(t, "ec-der.test")
ecDERKey, err := x509.MarshalECPrivateKey(ecPriv)
require.NoError(t, err)
parsed, err := parseDERInput(ecCert.Raw, ecDERKey)
require.NoError(t, err)
assert.NotNil(t, parsed.PrivateKey)
})
t.Run("DER cert with invalid key", func(t *testing.T) {
_, err := parseDERInput(cert.Raw, []byte("bad-key-data"))
assert.Error(t, err)
assert.Contains(t, err.Error(), "private key")
})
t.Run("invalid DER cert data", func(t *testing.T) {
_, err := parseDERInput([]byte("not-der"), nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "DER certificate")
})
}
// --- parsePEMInput chain building ---
func TestParsePEMInput_ChainBuilding(t *testing.T) {
t.Run("cert with intermediates in cert data", func(t *testing.T) {
_, _, certPEM1, _ := makeRSACertAndKey(t, "leaf.test", time.Now().Add(time.Hour))
_, _, certPEM2, _ := makeRSACertAndKey(t, "intermediate.test", time.Now().Add(time.Hour))
combined := append(certPEM1, certPEM2...)
parsed, err := parsePEMInput(combined, nil, nil)
require.NoError(t, err)
assert.NotNil(t, parsed.Leaf)
assert.Len(t, parsed.Intermediates, 1)
assert.NotEmpty(t, parsed.ChainPEM)
assert.Contains(t, parsed.ChainPEM, "BEGIN CERTIFICATE")
})
t.Run("cert with chain file", func(t *testing.T) {
_, _, certPEM, keyPEM := makeRSACertAndKey(t, "leaf.test", time.Now().Add(time.Hour))
_, _, chainPEM, _ := makeRSACertAndKey(t, "chain.test", time.Now().Add(time.Hour))
parsed, err := parsePEMInput(certPEM, keyPEM, chainPEM)
require.NoError(t, err)
assert.NotNil(t, parsed.PrivateKey)
assert.Len(t, parsed.Intermediates, 1)
assert.Equal(t, string(chainPEM), parsed.ChainPEM)
})
t.Run("invalid chain data ignored", func(t *testing.T) {
_, _, certPEM, _ := makeRSACertAndKey(t, "leaf.test", time.Now().Add(time.Hour))
parsed, err := parsePEMInput(certPEM, nil, []byte("not-pem"))
require.NoError(t, err)
assert.Empty(t, parsed.Intermediates, "invalid PEM chain should be silently ignored")
})
t.Run("invalid cert data", func(t *testing.T) {
_, err := parsePEMInput([]byte("not-pem"), nil, nil)
assert.Error(t, err)
})
t.Run("empty PEM block", func(t *testing.T) {
emptyPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("garbage")})
_, err := parsePEMInput(emptyPEM, nil, nil)
assert.Error(t, err)
})
}
// --- ConvertPFXToPEM ---
func TestConvertPFXToPEM(t *testing.T) {
cert, priv, _, _ := makeRSACertAndKey(t, "pfx-convert.test", time.Now().Add(time.Hour))
pfxData, err := pkcs12.Modern.Encode(priv, cert, nil, pkcs12.DefaultPassword)
require.NoError(t, err)
t.Run("valid PFX", func(t *testing.T) {
certPEM, keyPEM, chainPEM, err := ConvertPFXToPEM(pfxData, pkcs12.DefaultPassword)
require.NoError(t, err)
assert.Contains(t, certPEM, "BEGIN CERTIFICATE")
assert.Contains(t, keyPEM, "PRIVATE KEY")
assert.Empty(t, chainPEM)
})
t.Run("PFX with chain", func(t *testing.T) {
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
caTmpl := &x509.Certificate{
SerialNumber: big.NewInt(200),
Subject: pkix.Name{CommonName: "PFX Test CA"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign,
}
caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
require.NoError(t, err)
caCert, err := x509.ParseCertificate(caDER)
require.NoError(t, err)
pfxWithChain, err := pkcs12.Modern.Encode(priv, cert, []*x509.Certificate{caCert}, pkcs12.DefaultPassword)
require.NoError(t, err)
certPEM, keyPEM, chainPEM, err := ConvertPFXToPEM(pfxWithChain, pkcs12.DefaultPassword)
require.NoError(t, err)
assert.Contains(t, certPEM, "BEGIN CERTIFICATE")
assert.Contains(t, keyPEM, "PRIVATE KEY")
assert.Contains(t, chainPEM, "BEGIN CERTIFICATE")
})
t.Run("invalid PFX", func(t *testing.T) {
_, _, _, err := ConvertPFXToPEM([]byte("bad"), "password")
assert.Error(t, err)
assert.Contains(t, err.Error(), "PFX")
})
}
// --- encodeKeyToPEM ---
func TestEncodeKeyToPEM(t *testing.T) {
t.Run("RSA key", func(t *testing.T) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
pemStr, err := encodeKeyToPEM(priv)
require.NoError(t, err)
assert.Contains(t, pemStr, "PRIVATE KEY")
})
t.Run("ECDSA key", func(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
pemStr, err := encodeKeyToPEM(priv)
require.NoError(t, err)
assert.Contains(t, pemStr, "PRIVATE KEY")
})
}
// --- ParseCertificateInput for PFX ---
func TestParseCertificateInput_PFX(t *testing.T) {
cert, priv, _, _ := makeRSACertAndKey(t, "pfx-parse.test", time.Now().Add(time.Hour))
pfxData, err := pkcs12.Modern.Encode(priv, cert, nil, pkcs12.DefaultPassword)
require.NoError(t, err)
t.Run("PFX format detected and parsed", func(t *testing.T) {
parsed, err := ParseCertificateInput(pfxData, nil, nil, pkcs12.DefaultPassword)
require.NoError(t, err)
assert.NotNil(t, parsed.Leaf)
assert.NotNil(t, parsed.PrivateKey)
assert.Equal(t, FormatPFX, parsed.Format)
})
}
// --- detectKeyType additional branches ---
func TestDetectKeyType_P384(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(t, err)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(99),
Subject: pkix.Name{CommonName: "p384.test"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
require.NoError(t, err)
cert, err := x509.ParseCertificate(der)
require.NoError(t, err)
assert.Equal(t, "ECDSA-P384", detectKeyType(cert))
}
// --- parsePEMPrivateKey additional formats ---
func TestParsePEMPrivateKey_PKCS1(t *testing.T) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
key, err := parsePEMPrivateKey(keyPEM)
require.NoError(t, err)
assert.NotNil(t, key)
}
func TestParsePEMPrivateKey_EC(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
ecDER, err := x509.MarshalECPrivateKey(priv)
require.NoError(t, err)
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: ecDER})
key, err := parsePEMPrivateKey(keyPEM)
require.NoError(t, err)
assert.NotNil(t, key)
}
func TestParsePEMPrivateKey_Invalid(t *testing.T) {
t.Run("no PEM data", func(t *testing.T) {
_, err := parsePEMPrivateKey([]byte("not pem"))
assert.Error(t, err)
assert.Contains(t, err.Error(), "no PEM data")
})
t.Run("unsupported key format", func(t *testing.T) {
badPEM := pem.EncodeToMemory(&pem.Block{Type: "UNKNOWN KEY", Bytes: []byte("junk")})
_, err := parsePEMPrivateKey(badPEM)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported")
})
}
// --- DetectFormat for PFX ---
func TestDetectFormat_PFX(t *testing.T) {
cert, priv, _, _ := makeRSACertAndKey(t, "detect-pfx.test", time.Now().Add(time.Hour))
pfxData, err := pkcs12.Modern.Encode(priv, cert, nil, pkcs12.DefaultPassword)
require.NoError(t, err)
format := DetectFormat(pfxData)
assert.Equal(t, FormatPFX, format, "PFX data should be detected as FormatPFX")
}