Skip to content

Commit 3ae07a7

Browse files
armadi1809mohammed90mholtdependabot[bot]
authored
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](golangci/golangci-lint-action@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] <[email protected]> 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] <[email protected]> Co-authored-by: Mohammed Al Sahaf <[email protected]> Co-authored-by: Matt Holt <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent e473ae6 commit 3ae07a7

12 files changed

+649
-4
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ require (
148148
golang.org/x/text v0.14.0 // indirect
149149
golang.org/x/tools v0.16.1 // indirect
150150
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
152152
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
153153
howett.net/plist v1.0.0 // indirect
154154
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
855855
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
856856
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
857857
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=
858860
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
859861
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
860862
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

modules/caddytls/connpolicy.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
651651
}
652652
trustedLeafCerts = append(trustedLeafCerts, clientCert)
653653
}
654-
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
654+
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{trustedLeafCerts: trustedLeafCerts})
655655
}
656656

657657
// if a custom verification function already exists, wrap it
@@ -715,7 +715,8 @@ func setDefaultTLSParams(cfg *tls.Config) {
715715

716716
// LeafCertClientAuth verifies the client's leaf certificate.
717717
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
719720
}
720721

721722
// CaddyModule returns the Caddy module information.
@@ -726,6 +727,30 @@ func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo {
726727
}
727728
}
728729

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+
729754
func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
730755
if len(rawCerts) == 0 {
731756
return fmt.Errorf("no client certificate provided")
@@ -736,7 +761,7 @@ func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x5
736761
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
737762
}
738763

739-
for _, trustedLeafCert := range l.TrustedLeafCerts {
764+
for _, trustedLeafCert := range l.trustedLeafCerts {
740765
if remoteLeafCert.Equal(trustedLeafCert) {
741766
return nil
742767
}
@@ -765,6 +790,12 @@ type ConnectionMatcher interface {
765790
Match(*tls.ClientHelloInfo) bool
766791
}
767792

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+
768799
// ClientCertificateVerifier is a type which verifies client certificates.
769800
// It is called during verifyPeerCertificate in the TLS handshake.
770801
type ClientCertificateVerifier interface {

modules/caddytls/leaffileloader.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)