@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 180 lines 5.24 KB
Raw
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 "bytes"
19 "crypto/tls"
20 "encoding/pem"
21 "fmt"
22 "io/fs"
23 "os"
24 "path/filepath"
25 "strings"
26
27 "github.com/caddyserver/caddy/v2"
28 )
29
30 func init() {
31 caddy.RegisterModule(FolderLoader{})
32 }
33
34 // FolderLoader loads certificates and their associated keys from disk
35 // by recursively walking the specified directories, looking for PEM
36 // files which contain both a certificate and a key.
37 type FolderLoader []string
38
39 // CaddyModule returns the Caddy module information.
40 func (FolderLoader) CaddyModule() caddy.ModuleInfo {
41 return caddy.ModuleInfo{
42 ID: "tls.certificates.load_folders",
43 New: func() caddy.Module { return new(FolderLoader) },
44 }
45 }
46
47 // Provision implements caddy.Provisioner.
48 func (fl FolderLoader) Provision(ctx caddy.Context) error {
49 repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
50 if !ok {
51 repl = caddy.NewReplacer()
52 }
53 for k, path := range fl {
54 fl[k] = repl.ReplaceKnown(path, "")
55 }
56 return nil
57 }
58
59 // LoadCertificates loads all the certificates+keys in the directories
60 // listed in fl from all files ending with .pem. This method of loading
61 // certificates expects the certificate and key to be bundled into the
62 // same file.
63 func (fl FolderLoader) LoadCertificates() ([]Certificate, error) {
64 var certs []Certificate
65 for _, dir := range fl {
66 root, err := os.OpenRoot(dir)
67 if err != nil {
68 return nil, fmt.Errorf("unable to open root directory %s: %w", dir, err)
69 }
70 err = filepath.WalkDir(dir, func(fpath string, d fs.DirEntry, err error) error {
71 if err != nil {
72 return fmt.Errorf("unable to traverse into path: %s", fpath)
73 }
74 if d.IsDir() {
75 return nil
76 }
77 if !strings.HasSuffix(strings.ToLower(d.Name()), ".pem") {
78 return nil
79 }
80
81 rel, err := filepath.Rel(dir, fpath)
82 if err != nil {
83 return fmt.Errorf("unable to get relative path for %s: %w", fpath, err)
84 }
85
86 bundle, err := root.ReadFile(rel)
87 if err != nil {
88 return err
89 }
90 cert, err := tlsCertFromCertAndKeyPEMBundle(bundle)
91 if err != nil {
92 return fmt.Errorf("%s: %w", fpath, err)
93 }
94
95 certs = append(certs, Certificate{Certificate: cert})
96 return nil
97 })
98 _ = root.Close()
99 if err != nil {
100 return nil, fmt.Errorf("walking certificates directory %s: %w", dir, err)
101 }
102 }
103 return certs, nil
104 }
105
106 func tlsCertFromCertAndKeyPEMBundle(bundle []byte) (tls.Certificate, error) {
107 certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer)
108 var foundKey bool // use only the first key in the file
109
110 for {
111 // Decode next block so we can see what type it is
112 var derBlock *pem.Block
113 derBlock, bundle = pem.Decode(bundle)
114 if derBlock == nil {
115 break
116 }
117
118 if derBlock.Type == "CERTIFICATE" {
119 // Re-encode certificate as PEM, appending to certificate chain
120 if err := pem.Encode(certBuilder, derBlock); err != nil {
121 return tls.Certificate{}, err
122 }
123 } else if derBlock.Type == "EC PARAMETERS" {
124 // EC keys generated from openssl can be composed of two blocks:
125 // parameters and key (parameter block should come first)
126 if !foundKey {
127 // Encode parameters
128 if err := pem.Encode(keyBuilder, derBlock); err != nil {
129 return tls.Certificate{}, err
130 }
131
132 // Key must immediately follow
133 derBlock, bundle = pem.Decode(bundle)
134 if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
135 return tls.Certificate{}, fmt.Errorf("expected elliptic private key to immediately follow EC parameters")
136 }
137 if err := pem.Encode(keyBuilder, derBlock); err != nil {
138 return tls.Certificate{}, err
139 }
140 foundKey = true
141 }
142 } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
143 // RSA key
144 if !foundKey {
145 if err := pem.Encode(keyBuilder, derBlock); err != nil {
146 return tls.Certificate{}, err
147 }
148 foundKey = true
149 }
150 } else {
151 return tls.Certificate{}, fmt.Errorf("unrecognized PEM block type: %s", derBlock.Type)
152 }
153 }
154
155 certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes()
156 if len(certPEMBytes) == 0 {
157 return tls.Certificate{}, fmt.Errorf("failed to parse PEM data")
158 }
159 if len(keyPEMBytes) == 0 {
160 return tls.Certificate{}, fmt.Errorf("no private key block found")
161 }
162
163 // if the start of the key file looks like an encrypted private key,
164 // reject it with a helpful error message
165 if strings.HasPrefix(string(keyPEMBytes[:40]), "ENCRYPTED") {
166 return tls.Certificate{}, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first")
167 }
168
169 cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes)
170 if err != nil {
171 return tls.Certificate{}, fmt.Errorf("making X509 key pair: %v", err)
172 }
173
174 return cert, nil
175 }
176
177 var (
178 _ CertificateLoader = FolderLoader(nil)
179 _ caddy.Provisioner = FolderLoader(nil)
180 )