mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 16:20:59 +00:00
fix(nodes): validate every certificate in the node mTLS trust bundle (#6188)
* fix(nodes): validate every certificate in the node mTLS trust bundle AppendCertsFromPEM reports success once a single certificate parses, so a trust bundle whose later entries are damaged or truncated was accepted with those entries silently absent from the pool. Parse and validate every PEM block instead, and reject the bundle if any of them is malformed. * fix(mtls): reject malformed certificate bundle layout --------- Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
|
||||
)
|
||||
|
||||
func mustNodeCAPEM(t *testing.T, name string) string {
|
||||
t.Helper()
|
||||
ca, err := crypto.GenerateNodeCA(name)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateNodeCA(%q): %v", name, err)
|
||||
}
|
||||
return string(ca.CertPEM)
|
||||
}
|
||||
|
||||
func TestParseCertificateBundlePEM(t *testing.T) {
|
||||
first := mustNodeCAPEM(t, "bundle test CA one")
|
||||
second := mustNodeCAPEM(t, "bundle test CA two")
|
||||
|
||||
corrupt := strings.Replace(second, "-----BEGIN CERTIFICATE-----\n", "-----BEGIN CERTIFICATE-----\nAA", 1)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
bundle string
|
||||
wantCerts int
|
||||
wantErr string
|
||||
}{
|
||||
{name: "single certificate", bundle: first, wantCerts: 1},
|
||||
{name: "two certificates", bundle: first + second, wantCerts: 2},
|
||||
{name: "empty", bundle: "", wantErr: "certificate bundle is empty"},
|
||||
{name: "whitespace only", bundle: "\n\t \n", wantErr: "certificate bundle is empty"},
|
||||
{name: "leading non-PEM data", bundle: "junk\n" + first, wantErr: "certificate bundle contains malformed or non-PEM data"},
|
||||
{name: "interstitial non-PEM data", bundle: first + "junk\n" + second, wantErr: "certificate bundle contains malformed or non-PEM data"},
|
||||
{name: "second certificate corrupt", bundle: first + corrupt, wantErr: "certificate bundle contains malformed or non-PEM data"},
|
||||
{name: "trailing non-PEM data", bundle: first + "not a certificate\n", wantErr: "certificate bundle contains malformed or non-PEM data"},
|
||||
{name: "non-certificate block", bundle: first + "-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n", wantErr: "certificate bundle contains malformed or non-PEM data"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
certs, err := parseCertificateBundlePEM([]byte(tt.bundle))
|
||||
if tt.wantErr != "" {
|
||||
if err == nil || err.Error() != tt.wantErr {
|
||||
t.Fatalf("parseCertificateBundlePEM() error = %v, want %q", err, tt.wantErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("parseCertificateBundlePEM(): %v", err)
|
||||
}
|
||||
if len(certs) != tt.wantCerts {
|
||||
t.Fatalf("parseCertificateBundlePEM() = %d certs, want %d", len(certs), tt.wantCerts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeMtlsClientCAPoolRejectsPartiallyValidBundle(t *testing.T) {
|
||||
s := setupSettingMtlsDB(t)
|
||||
|
||||
valid := mustNodeCAPEM(t, "pool test CA")
|
||||
if err := s.setString("nodeMtlsClientCAPem", valid+"-----BEGIN CERTIFICATE-----\nnot base64\n-----END CERTIFICATE-----\n"); err != nil {
|
||||
t.Fatalf("setString: %v", err)
|
||||
}
|
||||
|
||||
pool, err := s.NodeMtlsClientCAPool()
|
||||
want := "nodeMtlsClientCAPem is not a valid certificate bundle: certificate bundle contains malformed or non-PEM data"
|
||||
if err == nil || err.Error() != want {
|
||||
t.Fatalf("NodeMtlsClientCAPool() = %v, error = %v, want %q", pool, err, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user