setting_mtls_bundle_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package service
  2. import (
  3. "errors"
  4. "strings"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  7. )
  8. func mustNodeCAPEM(t *testing.T, name string) string {
  9. t.Helper()
  10. ca, err := crypto.GenerateNodeCA(name)
  11. if err != nil {
  12. t.Fatalf("GenerateNodeCA(%q): %v", name, err)
  13. }
  14. return string(ca.CertPEM)
  15. }
  16. func TestParseCertificateBundlePEM(t *testing.T) {
  17. first := mustNodeCAPEM(t, "bundle test CA one")
  18. second := mustNodeCAPEM(t, "bundle test CA two")
  19. corrupt := strings.Replace(second, "-----BEGIN CERTIFICATE-----\n", "-----BEGIN CERTIFICATE-----\nAA", 1)
  20. tests := []struct {
  21. name string
  22. bundle string
  23. wantCerts int
  24. wantErr string
  25. }{
  26. {name: "single certificate", bundle: first, wantCerts: 1},
  27. {name: "two certificates", bundle: first + second, wantCerts: 2},
  28. {name: "empty", bundle: "", wantErr: "certificate bundle is empty"},
  29. {name: "whitespace only", bundle: "\n\t \n", wantErr: "certificate bundle is empty"},
  30. {name: "leading non-PEM data", bundle: "junk\n" + first, wantErr: "certificate bundle contains malformed or non-PEM data"},
  31. {name: "interstitial non-PEM data", bundle: first + "junk\n" + second, wantErr: "certificate bundle contains malformed or non-PEM data"},
  32. {name: "second certificate corrupt", bundle: first + corrupt, wantErr: "certificate bundle contains malformed or non-PEM data"},
  33. {name: "trailing non-PEM data", bundle: first + "not a certificate\n", wantErr: "certificate bundle contains malformed or non-PEM data"},
  34. {name: "non-certificate block", bundle: first + "-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n", wantErr: "certificate bundle contains malformed or non-PEM data"},
  35. }
  36. for _, tt := range tests {
  37. t.Run(tt.name, func(t *testing.T) {
  38. certs, err := parseCertificateBundlePEM([]byte(tt.bundle))
  39. if tt.wantErr != "" {
  40. if err == nil || err.Error() != tt.wantErr {
  41. t.Fatalf("parseCertificateBundlePEM() error = %v, want %q", err, tt.wantErr)
  42. }
  43. return
  44. }
  45. if err != nil {
  46. t.Fatalf("parseCertificateBundlePEM(): %v", err)
  47. }
  48. if len(certs) != tt.wantCerts {
  49. t.Fatalf("parseCertificateBundlePEM() = %d certs, want %d", len(certs), tt.wantCerts)
  50. }
  51. })
  52. }
  53. }
  54. func TestNodeMtlsClientCAPoolRejectsPartiallyValidBundle(t *testing.T) {
  55. s := setupSettingMtlsDB(t)
  56. valid := mustNodeCAPEM(t, "pool test CA")
  57. if err := s.setString("nodeMtlsClientCAPem", valid+"-----BEGIN CERTIFICATE-----\nnot base64\n-----END CERTIFICATE-----\n"); err != nil {
  58. t.Fatalf("setString: %v", err)
  59. }
  60. pool, err := s.NodeMtlsClientCAPool()
  61. want := "nodeMtlsClientCAPem is not a valid certificate bundle: certificate bundle contains malformed or non-PEM data"
  62. if err == nil || err.Error() != want {
  63. t.Fatalf("NodeMtlsClientCAPool() = %v, error = %v, want %q", pool, err, want)
  64. }
  65. }
  66. // The boot path tells the operator whether the bundle itself is unusable or the
  67. // settings read failed, so the parse failure has to carry a matchable cause.
  68. func TestNodeMtlsClientCAPoolTagsAnInvalidBundle(t *testing.T) {
  69. s := setupSettingMtlsDB(t)
  70. if err := s.setString("nodeMtlsClientCAPem", "-----BEGIN CERTIFICATE-----\nnot base64\n-----END CERTIFICATE-----\n"); err != nil {
  71. t.Fatalf("setString: %v", err)
  72. }
  73. pool, err := s.NodeMtlsClientCAPool()
  74. if pool != nil {
  75. t.Fatalf("NodeMtlsClientCAPool() returned a pool built from an unusable bundle")
  76. }
  77. if !errors.Is(err, ErrNodeMtlsTrustBundleInvalid) {
  78. t.Fatalf("NodeMtlsClientCAPool() error = %v, want it to wrap ErrNodeMtlsTrustBundleInvalid", err)
  79. }
  80. }