202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
|
package x509_cert
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"crypto/rsa"
|
||
|
"crypto/x509"
|
||
|
"crypto/x509/pkix"
|
||
|
"encoding/pem"
|
||
|
"math/big"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"software.sslmate.com/src/go-pkcs12"
|
||
|
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
"github.com/influxdata/telegraf/testutil"
|
||
|
)
|
||
|
|
||
|
type selfSignedCert struct {
|
||
|
certPEM []byte
|
||
|
keyPEM []byte
|
||
|
certDER []byte
|
||
|
}
|
||
|
|
||
|
// generateTestKeystores creates temporary JKS & PKCS#12 keystores for testing
|
||
|
func generateTestKeystores(t *testing.T) (pkcs12Path, jksPath string) {
|
||
|
t.Helper()
|
||
|
|
||
|
// Generate a test certificate
|
||
|
selfSigned := generateselfSignedCert(t)
|
||
|
|
||
|
pkcs12Path = createTestPKCS12(t, selfSigned.certPEM, selfSigned.keyPEM)
|
||
|
jksPath = createTestJKS(t, selfSigned.certDER)
|
||
|
|
||
|
return pkcs12Path, jksPath
|
||
|
}
|
||
|
|
||
|
// generateselfSignedCert generates a dummy self-signed certificate
|
||
|
func generateselfSignedCert(t *testing.T) selfSignedCert {
|
||
|
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
template := x509.Certificate{
|
||
|
SerialNumber: big.NewInt(1),
|
||
|
Subject: pkix.Name{
|
||
|
CommonName: "Test Certificate",
|
||
|
Organization: []string{"Test Org"},
|
||
|
},
|
||
|
NotBefore: time.Now(),
|
||
|
NotAfter: time.Now().Add(24 * time.Hour),
|
||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||
|
BasicConstraintsValid: true,
|
||
|
}
|
||
|
|
||
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
return selfSignedCert{
|
||
|
certPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
|
||
|
keyPEM: pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)}),
|
||
|
certDER: certDER,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// createTestPKCS12 creates a temporary PKCS#12 keystore
|
||
|
func createTestPKCS12(t *testing.T, certPEM, keyPEM []byte) string {
|
||
|
t.Helper()
|
||
|
|
||
|
// Decode certificate
|
||
|
block, _ := pem.Decode(certPEM)
|
||
|
require.NotNil(t, block, "failed to parse certificate PEM")
|
||
|
|
||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Decode private key
|
||
|
block, _ = pem.Decode(keyPEM)
|
||
|
require.NotNil(t, block, "failed to parse private key PEM")
|
||
|
|
||
|
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Encode PKCS#12 keystore
|
||
|
pfxData, err := pkcs12.Modern.Encode(privKey, cert, nil, "test-password")
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Use `t.TempDir()` to ensure cleanup
|
||
|
tempDir := t.TempDir()
|
||
|
pkcs12Path := filepath.Join(tempDir, "test-keystore.p12")
|
||
|
|
||
|
err = os.WriteFile(pkcs12Path, pfxData, 0600)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
pkcs12Path = filepath.ToSlash(pkcs12Path)
|
||
|
if !strings.HasPrefix(pkcs12Path, "/") {
|
||
|
pkcs12Path = "/" + pkcs12Path
|
||
|
}
|
||
|
|
||
|
return "pkcs12://" + pkcs12Path
|
||
|
}
|
||
|
|
||
|
// createTestJKS creates a temporary JKS keystore
|
||
|
func createTestJKS(t *testing.T, certDER []byte) string {
|
||
|
t.Helper()
|
||
|
|
||
|
// Use `t.TempDir()` to ensure cleanup
|
||
|
tempDir := t.TempDir()
|
||
|
jksPath := filepath.Join(tempDir, "test-keystore.jks")
|
||
|
|
||
|
// Create JKS keystore and add a trusted certificate
|
||
|
jks := keystore.New()
|
||
|
err := jks.SetTrustedCertificateEntry("test-alias", keystore.TrustedCertificateEntry{
|
||
|
Certificate: keystore.Certificate{
|
||
|
Type: "X.509",
|
||
|
Content: certDER,
|
||
|
},
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Write keystore to file
|
||
|
output, err := os.Create(jksPath)
|
||
|
require.NoError(t, err)
|
||
|
defer output.Close()
|
||
|
|
||
|
require.NoError(t, jks.Store(output, []byte("test-password")))
|
||
|
|
||
|
jksPath = filepath.ToSlash(jksPath)
|
||
|
if !strings.HasPrefix(jksPath, "/") {
|
||
|
jksPath = "/" + jksPath
|
||
|
}
|
||
|
|
||
|
return "jks://" + jksPath
|
||
|
}
|
||
|
|
||
|
func TestGatherKeystores(t *testing.T) {
|
||
|
pkcs12Path, jksPath := generateTestKeystores(t)
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
content string
|
||
|
password string
|
||
|
}{
|
||
|
{name: "valid PKCS12 keystore", content: pkcs12Path, password: "test-password"},
|
||
|
{name: "valid JKS keystore", content: jksPath, password: "test-password"},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
plugin := X509Cert{
|
||
|
Sources: []string{test.content},
|
||
|
Password: config.NewSecret([]byte(test.password)),
|
||
|
Log: testutil.Logger{},
|
||
|
}
|
||
|
require.NoError(t, plugin.Init())
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, plugin.Gather(&acc))
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGatherKeystoresFail(t *testing.T) {
|
||
|
pkcs12Path, jksPath := generateTestKeystores(t)
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
content string
|
||
|
password string
|
||
|
expected string
|
||
|
}{
|
||
|
{name: "missing password PKCS12", content: pkcs12Path, expected: "decryption password incorrect"},
|
||
|
{name: "missing password JKS", content: jksPath, expected: "got invalid digest"},
|
||
|
{name: "wrong password PKCS12", content: pkcs12Path, password: "wrong-password", expected: "decryption password incorrect"},
|
||
|
{name: "wrong password JKS", content: jksPath, password: "wrong-password", expected: "got invalid digest"},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
plugin := X509Cert{
|
||
|
Sources: []string{test.content},
|
||
|
Log: testutil.Logger{},
|
||
|
}
|
||
|
if test.password != "" {
|
||
|
plugin.Password = config.NewSecret([]byte(test.password))
|
||
|
} else {
|
||
|
plugin.Password = config.NewSecret(nil)
|
||
|
}
|
||
|
require.NoError(t, plugin.Init())
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, plugin.Gather(&acc))
|
||
|
require.NotEmpty(t, acc.Errors)
|
||
|
require.ErrorContains(t, acc.Errors[0], test.expected)
|
||
|
})
|
||
|
}
|
||
|
}
|