@marcel91200 / caddy Mirror · read-only / commits / 3ae07a73
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 12, 17:00 UTC

caddytls: clientauth: leaf verifier: make trusted leaf certs source pluggable (#6050)

* Made trusted leaf certificates pluggable into the tls.client_auth.leaf module * Added leaf loaders modules: file, folder, pem aand storage * Cleaned implementation of leaf cert loader modules * Added tests for leaf certs file and folder loaders * cmd: fix the output of the `Usage` section (#6138) * core: OnExit hooks (#6128) * core: OnExit callbacks * core: Process-global OnExit callbacks * ci: bump golangci/golangci-lint-action from 3 to 4 (#6141) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Added more leaf certificate loaders tests and cleaned up code * Modified leaf cert loaders json field names and cleaned up storage loader comment * Update modules/caddytls/leaffileloader.go * Update LeafStorageLoader certificates field name * Upgraded protobuf version --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Aziz Rmadi committed Mar 5, 2024 at 15:55 UTC 3ae07a73dc057c3a12486b78872c5e1391ec7cc9
12 files changed +649 -4
caddytest/integration/leafcertloaders_test.go new
+67
@@ -0,0 +1,67 @@
1 +package integration
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/caddyserver/caddy/v2/caddytest"
7 +)
8 +
9 +func TestLeafCertLoaders(t *testing.T) {
10 + tester := caddytest.NewTester(t)
11 + tester.InitServer(`
12 + {
13 + "admin": {
14 + "listen": "localhost:2999"
15 + },
16 + "apps": {
17 + "http": {
18 + "servers": {
19 + "srv0": {
20 + "listen": [
21 + ":443"
22 + ],
23 + "routes": [
24 + {
25 + "match": [
26 + {
27 + "host": [
28 + "localhost"
29 + ]
30 + }
31 + ],
32 + "terminal": true
33 + }
34 + ],
35 + "tls_connection_policies": [
36 + {
37 + "client_authentication": {
38 + "verifiers": [
39 + {
40 + "verifier": "leaf",
41 + "leaf_certs_loaders": [
42 + {
43 + "loader": "file",
44 + "files": ["../leafcert.pem"]
45 + },
46 + {
47 + "loader": "folder",
48 + "folders": ["../"]
49 + },
50 + {
51 + "loader": "storage"
52 + },
53 + {
54 + "loader": "pem"
55 + }
56 + ]
57 + }
58 + ]
59 + }
60 + }
61 + ]
62 + }
63 + }
64 + }
65 + }
66 + }`, "json")
67 +}
caddytest/leafcert.pem new
+15
@@ -0,0 +1,15 @@
1 +-----BEGIN CERTIFICATE-----
2 +MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
3 +MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
4 +VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
5 +NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD
6 +TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu
7 +ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j
8 +V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj
9 +gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA
10 +FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE
11 +CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS
12 +BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE
13 +BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju
14 +Wm7DCfrPNGVwFWUQOmsPue9rZBgO
15 +-----END CERTIFICATE-----
go.mod
+1 -1
@@ -148,7 +148,7 @@ require (
148 golang.org/x/text v0.14.0 // indirect
149 golang.org/x/tools v0.16.1 // indirect
150 google.golang.org/grpc v1.60.1 // indirect
151 - google.golang.org/protobuf v1.31.0 // indirect
151 + google.golang.org/protobuf v1.33.0 // indirect
152 gopkg.in/square/go-jose.v2 v2.6.0 // indirect
153 howett.net/plist v1.0.0 // indirect
154 )
go.sum
+2
@@ -855,6 +855,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
855 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
856 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
857 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
858 +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
859 +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
860 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
861 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
862 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
modules/caddytls/connpolicy.go
+34 -3
@@ -651,7 +651,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
651 }
652 trustedLeafCerts = append(trustedLeafCerts, clientCert)
653 }
654 - clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
654 + clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{trustedLeafCerts: trustedLeafCerts})
655 }
656
657 // if a custom verification function already exists, wrap it
@@ -715,7 +715,8 @@ func setDefaultTLSParams(cfg *tls.Config) {
715
716 // LeafCertClientAuth verifies the client's leaf certificate.
717 type LeafCertClientAuth struct {
718 - TrustedLeafCerts []*x509.Certificate
718 + LeafCertificateLoadersRaw []json.RawMessage `json:"leaf_certs_loaders,omitempty" caddy:"namespace=tls.leaf_cert_loader inline_key=loader"`
719 + trustedLeafCerts []*x509.Certificate
720 }
721
722 // CaddyModule returns the Caddy module information.
@@ -726,6 +727,30 @@ func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo {
727 }
728 }
729
730 +func (l *LeafCertClientAuth) Provision(ctx caddy.Context) error {
731 + if l.LeafCertificateLoadersRaw == nil {
732 + return nil
733 + }
734 + val, err := ctx.LoadModule(l, "LeafCertificateLoadersRaw")
735 + if err != nil {
736 + return fmt.Errorf("could not parse leaf certificates loaders: %s", err.Error())
737 + }
738 + trustedLeafCertloaders := []LeafCertificateLoader{}
739 + for _, loader := range val.([]any) {
740 + trustedLeafCertloaders = append(trustedLeafCertloaders, loader.(LeafCertificateLoader))
741 + }
742 + trustedLeafCertificates := []*x509.Certificate{}
743 + for _, loader := range trustedLeafCertloaders {
744 + certs, err := loader.LoadLeafCertificates()
745 + if err != nil {
746 + return fmt.Errorf("could not load leaf certificates: %s", err.Error())
747 + }
748 + trustedLeafCertificates = append(trustedLeafCertificates, certs...)
749 + }
750 + l.trustedLeafCerts = trustedLeafCertificates
751 + return nil
752 +}
753 +
754 func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
755 if len(rawCerts) == 0 {
756 return fmt.Errorf("no client certificate provided")
@@ -736,7 +761,7 @@ func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x5
761 return fmt.Errorf("can't parse the given certificate: %s", err.Error())
762 }
763
739 - for _, trustedLeafCert := range l.TrustedLeafCerts {
764 + for _, trustedLeafCert := range l.trustedLeafCerts {
765 if remoteLeafCert.Equal(trustedLeafCert) {
766 return nil
767 }
@@ -765,6 +790,12 @@ type ConnectionMatcher interface {
790 Match(*tls.ClientHelloInfo) bool
791 }
792
793 +// LeafCertificateLoader is a type that loads the trusted leaf certificates
794 +// for the tls.leaf_cert_loader modules
795 +type LeafCertificateLoader interface {
796 + LoadLeafCertificates() ([]*x509.Certificate, error)
797 +}
798 +
799 // ClientCertificateVerifier is a type which verifies client certificates.
800 // It is called during verifyPeerCertificate in the TLS handshake.
801 type ClientCertificateVerifier interface {
modules/caddytls/leaffileloader.go new
+99
@@ -0,0 +1,99 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "crypto/x509"
19 + "encoding/pem"
20 + "fmt"
21 + "os"
22 +
23 + "github.com/caddyserver/caddy/v2"
24 +)
25 +
26 +func init() {
27 + caddy.RegisterModule(LeafFileLoader{})
28 +}
29 +
30 +// LeafFileLoader loads leaf certificates from disk.
31 +type LeafFileLoader struct {
32 + Files []string `json:"files,omitempty"`
33 +}
34 +
35 +// Provision implements caddy.Provisioner.
36 +func (fl *LeafFileLoader) Provision(ctx caddy.Context) error {
37 + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
38 + if !ok {
39 + repl = caddy.NewReplacer()
40 + }
41 + for k, path := range fl.Files {
42 + fl.Files[k] = repl.ReplaceKnown(path, "")
43 + }
44 + return nil
45 +}
46 +
47 +// CaddyModule returns the Caddy module information.
48 +func (LeafFileLoader) CaddyModule() caddy.ModuleInfo {
49 + return caddy.ModuleInfo{
50 + ID: "tls.leaf_cert_loader.file",
51 + New: func() caddy.Module { return new(LeafFileLoader) },
52 + }
53 +}
54 +
55 +// LoadLeafCertificates returns the certificates to be loaded by fl.
56 +func (fl LeafFileLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
57 + certificates := make([]*x509.Certificate, 0, len(fl.Files))
58 + for _, path := range fl.Files {
59 + ders, err := convertPEMFilesToDERBytes(path)
60 + if err != nil {
61 + return nil, err
62 + }
63 + certs, err := x509.ParseCertificates(ders)
64 + if err != nil {
65 + return nil, err
66 + }
67 + certificates = append(certificates, certs...)
68 + }
69 + return certificates, nil
70 +}
71 +
72 +func convertPEMFilesToDERBytes(filename string) ([]byte, error) {
73 + certDataPEM, err := os.ReadFile(filename)
74 + if err != nil {
75 + return nil, err
76 + }
77 + var ders []byte
78 + // while block is not nil, we have more certificates in the file
79 + for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) {
80 + if block.Type != "CERTIFICATE" {
81 + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
82 + }
83 + ders = append(
84 + ders,
85 + block.Bytes...,
86 + )
87 + }
88 + // if we decoded nothing, return an error
89 + if len(ders) == 0 {
90 + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
91 + }
92 + return ders, nil
93 +}
94 +
95 +// Interface guard
96 +var (
97 + _ LeafCertificateLoader = (*LeafFileLoader)(nil)
98 + _ caddy.Provisioner = (*LeafFileLoader)(nil)
99 +)
modules/caddytls/leaffileloader_test.go new
+38
@@ -0,0 +1,38 @@
1 +package caddytls
2 +
3 +import (
4 + "context"
5 + "encoding/pem"
6 + "os"
7 + "strings"
8 + "testing"
9 +
10 + "github.com/caddyserver/caddy/v2"
11 +)
12 +
13 +func TestLeafFileLoader(t *testing.T) {
14 + fl := LeafFileLoader{Files: []string{"../../caddytest/leafcert.pem"}}
15 + fl.Provision(caddy.Context{Context: context.Background()})
16 +
17 + out, err := fl.LoadLeafCertificates()
18 + if err != nil {
19 + t.Errorf("Leaf certs file loading test failed: %v", err)
20 + }
21 + if len(out) != 1 {
22 + t.Errorf("Error loading leaf cert in memory struct")
23 + return
24 + }
25 + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
26 +
27 + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
28 + if err != nil {
29 + t.Errorf("Unable to read the example certificate from the file")
30 + }
31 +
32 + // Remove /r because windows.
33 + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
34 +
35 + if string(pemBytes) != pemFileString {
36 + t.Errorf("Leaf Certificate File Loader: Failed to load the correct certificate")
37 + }
38 +}
modules/caddytls/leaffolderloader.go new
+97
@@ -0,0 +1,97 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "crypto/x509"
19 + "fmt"
20 + "os"
21 + "path/filepath"
22 + "strings"
23 +
24 + "github.com/caddyserver/caddy/v2"
25 +)
26 +
27 +func init() {
28 + caddy.RegisterModule(LeafFolderLoader{})
29 +}
30 +
31 +// LeafFolderLoader loads certificates and their associated keys from disk
32 +// by recursively walking the specified directories, looking for PEM
33 +// files which contain both a certificate and a key.
34 +type LeafFolderLoader struct {
35 + Folders []string `json:"folders,omitempty"`
36 +}
37 +
38 +// CaddyModule returns the Caddy module information.
39 +func (LeafFolderLoader) CaddyModule() caddy.ModuleInfo {
40 + return caddy.ModuleInfo{
41 + ID: "tls.leaf_cert_loader.folder",
42 + New: func() caddy.Module { return new(LeafFolderLoader) },
43 + }
44 +}
45 +
46 +// Provision implements caddy.Provisioner.
47 +func (fl *LeafFolderLoader) Provision(ctx caddy.Context) error {
48 + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
49 + if !ok {
50 + repl = caddy.NewReplacer()
51 + }
52 + for k, path := range fl.Folders {
53 + fl.Folders[k] = repl.ReplaceKnown(path, "")
54 + }
55 + return nil
56 +}
57 +
58 +// LoadLeafCertificates loads all the leaf certificates in the directories
59 +// listed in fl from all files ending with .pem.
60 +func (fl LeafFolderLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
61 + var certs []*x509.Certificate
62 + for _, dir := range fl.Folders {
63 + err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error {
64 + if err != nil {
65 + return fmt.Errorf("unable to traverse into path: %s", fpath)
66 + }
67 + if info.IsDir() {
68 + return nil
69 + }
70 + if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") {
71 + return nil
72 + }
73 +
74 + certData, err := convertPEMFilesToDERBytes(fpath)
75 + if err != nil {
76 + return err
77 + }
78 + cert, err := x509.ParseCertificate(certData)
79 + if err != nil {
80 + return fmt.Errorf("%s: %w", fpath, err)
81 + }
82 +
83 + certs = append(certs, cert)
84 +
85 + return nil
86 + })
87 + if err != nil {
88 + return nil, err
89 + }
90 + }
91 + return certs, nil
92 +}
93 +
94 +var (
95 + _ LeafCertificateLoader = (*LeafFolderLoader)(nil)
96 + _ caddy.Provisioner = (*LeafFolderLoader)(nil)
97 +)
modules/caddytls/leaffolderloader_test.go new
+37
@@ -0,0 +1,37 @@
1 +package caddytls
2 +
3 +import (
4 + "context"
5 + "encoding/pem"
6 + "os"
7 + "strings"
8 + "testing"
9 +
10 + "github.com/caddyserver/caddy/v2"
11 +)
12 +
13 +func TestLeafFolderLoader(t *testing.T) {
14 + fl := LeafFolderLoader{Folders: []string{"../../caddytest"}}
15 + fl.Provision(caddy.Context{Context: context.Background()})
16 +
17 + out, err := fl.LoadLeafCertificates()
18 + if err != nil {
19 + t.Errorf("Leaf certs folder loading test failed: %v", err)
20 + }
21 + if len(out) != 1 {
22 + t.Errorf("Error loading leaf cert in memory struct")
23 + return
24 + }
25 + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
26 + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
27 + if err != nil {
28 + t.Errorf("Unable to read the example certificate from the file")
29 + }
30 +
31 + // Remove /r because windows.
32 + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
33 +
34 + if string(pemBytes) != pemFileString {
35 + t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate")
36 + }
37 +}
modules/caddytls/leafpemloader.go new
+76
@@ -0,0 +1,76 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "crypto/x509"
19 + "fmt"
20 +
21 + "github.com/caddyserver/caddy/v2"
22 +)
23 +
24 +func init() {
25 + caddy.RegisterModule(LeafPEMLoader{})
26 +}
27 +
28 +// LeafPEMLoader loads leaf certificates by
29 +// decoding their PEM blocks directly. This has the advantage
30 +// of not needing to store them on disk at all.
31 +type LeafPEMLoader struct {
32 + Certificates []string `json:"certificates,omitempty"`
33 +}
34 +
35 +// Provision implements caddy.Provisioner.
36 +func (pl *LeafPEMLoader) Provision(ctx caddy.Context) error {
37 + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
38 + if !ok {
39 + repl = caddy.NewReplacer()
40 + }
41 + for i, cert := range pl.Certificates {
42 + pl.Certificates[i] = repl.ReplaceKnown(cert, "")
43 + }
44 + return nil
45 +}
46 +
47 +// CaddyModule returns the Caddy module information.
48 +func (LeafPEMLoader) CaddyModule() caddy.ModuleInfo {
49 + return caddy.ModuleInfo{
50 + ID: "tls.leaf_cert_loader.pem",
51 + New: func() caddy.Module { return new(LeafPEMLoader) },
52 + }
53 +}
54 +
55 +// LoadLeafCertificates returns the certificates contained in pl.
56 +func (pl LeafPEMLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
57 + certs := make([]*x509.Certificate, 0, len(pl.Certificates))
58 + for i, cert := range pl.Certificates {
59 + derBytes, err := convertPEMToDER([]byte(cert))
60 + if err != nil {
61 + return nil, fmt.Errorf("PEM leaf certificate loader, cert %d: %v", i, err)
62 + }
63 + cert, err := x509.ParseCertificate(derBytes)
64 + if err != nil {
65 + return nil, fmt.Errorf("PEM cert %d: %v", i, err)
66 + }
67 + certs = append(certs, cert)
68 + }
69 + return certs, nil
70 +}
71 +
72 +// Interface guard
73 +var (
74 + _ LeafCertificateLoader = (*LeafPEMLoader)(nil)
75 + _ caddy.Provisioner = (*LeafPEMLoader)(nil)
76 +)
modules/caddytls/leafpemloader_test.go new
+54
@@ -0,0 +1,54 @@
1 +package caddytls
2 +
3 +import (
4 + "context"
5 + "encoding/pem"
6 + "os"
7 + "strings"
8 + "testing"
9 +
10 + "github.com/caddyserver/caddy/v2"
11 +)
12 +
13 +func TestLeafPEMLoader(t *testing.T) {
14 + pl := LeafPEMLoader{Certificates: []string{`
15 +-----BEGIN CERTIFICATE-----
16 +MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
17 +MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
18 +VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
19 +NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD
20 +TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu
21 +ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j
22 +V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj
23 +gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA
24 +FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE
25 +CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS
26 +BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE
27 +BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju
28 +Wm7DCfrPNGVwFWUQOmsPue9rZBgO
29 +-----END CERTIFICATE-----
30 +`}}
31 + pl.Provision(caddy.Context{Context: context.Background()})
32 +
33 + out, err := pl.LoadLeafCertificates()
34 + if err != nil {
35 + t.Errorf("Leaf certs pem loading test failed: %v", err)
36 + }
37 + if len(out) != 1 {
38 + t.Errorf("Error loading leaf cert in memory struct")
39 + return
40 + }
41 + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
42 +
43 + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
44 + if err != nil {
45 + t.Errorf("Unable to read the example certificate from the file")
46 + }
47 +
48 + // Remove /r because windows.
49 + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
50 +
51 + if string(pemBytes) != pemFileString {
52 + t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate")
53 + }
54 +}
modules/caddytls/leafstorageloader.go new
+129
@@ -0,0 +1,129 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "crypto/x509"
19 + "encoding/json"
20 + "encoding/pem"
21 + "fmt"
22 +
23 + "github.com/caddyserver/certmagic"
24 +
25 + "github.com/caddyserver/caddy/v2"
26 +)
27 +
28 +func init() {
29 + caddy.RegisterModule(LeafStorageLoader{})
30 +}
31 +
32 +// LeafStorageLoader loads leaf certificates from the
33 +// globally configured storage module.
34 +type LeafStorageLoader struct {
35 + // A list of certificate file names to be loaded from storage.
36 + Certificates []string `json:"certificates,omitempty"`
37 +
38 + // The storage module where the trusted leaf certificates are stored. Absent
39 + // explicit storage implies the use of Caddy default storage.
40 + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
41 +
42 + // Reference to the globally configured storage module.
43 + storage certmagic.Storage
44 +
45 + ctx caddy.Context
46 +}
47 +
48 +// CaddyModule returns the Caddy module information.
49 +func (LeafStorageLoader) CaddyModule() caddy.ModuleInfo {
50 + return caddy.ModuleInfo{
51 + ID: "tls.leaf_cert_loader.storage",
52 + New: func() caddy.Module { return new(LeafStorageLoader) },
53 + }
54 +}
55 +
56 +// Provision loads the storage module for sl.
57 +func (sl *LeafStorageLoader) Provision(ctx caddy.Context) error {
58 + if sl.StorageRaw != nil {
59 + val, err := ctx.LoadModule(sl, "StorageRaw")
60 + if err != nil {
61 + return fmt.Errorf("loading storage module: %v", err)
62 + }
63 + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
64 + if err != nil {
65 + return fmt.Errorf("creating storage configuration: %v", err)
66 + }
67 + sl.storage = cmStorage
68 + }
69 + if sl.storage == nil {
70 + sl.storage = ctx.Storage()
71 + }
72 + sl.ctx = ctx
73 +
74 + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
75 + if !ok {
76 + repl = caddy.NewReplacer()
77 + }
78 + for k, path := range sl.Certificates {
79 + sl.Certificates[k] = repl.ReplaceKnown(path, "")
80 + }
81 + return nil
82 +}
83 +
84 +// LoadLeafCertificates returns the certificates to be loaded by sl.
85 +func (sl LeafStorageLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
86 + certificates := make([]*x509.Certificate, 0, len(sl.Certificates))
87 + for _, path := range sl.Certificates {
88 + certData, err := sl.storage.Load(sl.ctx, path)
89 + if err != nil {
90 + return nil, err
91 + }
92 +
93 + ders, err := convertPEMToDER(certData)
94 + if err != nil {
95 + return nil, err
96 + }
97 + certs, err := x509.ParseCertificates(ders)
98 + if err != nil {
99 + return nil, err
100 + }
101 + certificates = append(certificates, certs...)
102 + }
103 + return certificates, nil
104 +}
105 +
106 +func convertPEMToDER(pemData []byte) ([]byte, error) {
107 + var ders []byte
108 + // while block is not nil, we have more certificates in the file
109 + for block, rest := pem.Decode(pemData); block != nil; block, rest = pem.Decode(rest) {
110 + if block.Type != "CERTIFICATE" {
111 + return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data")
112 + }
113 + ders = append(
114 + ders,
115 + block.Bytes...,
116 + )
117 + }
118 + // if we decoded nothing, return an error
119 + if len(ders) == 0 {
120 + return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data")
121 + }
122 + return ders, nil
123 +}
124 +
125 +// Interface guard
126 +var (
127 + _ LeafCertificateLoader = (*LeafStorageLoader)(nil)
128 + _ caddy.Provisioner = (*LeafStorageLoader)(nil)
129 +)