@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 213 lines 6.13 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 "crypto/tls"
19 "crypto/x509"
20 "encoding/json"
21 "fmt"
22 "math/big"
23 "slices"
24
25 "github.com/caddyserver/certmagic"
26
27 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
28 )
29
30 // CustomCertSelectionPolicy represents a policy for selecting the certificate
31 // used to complete a handshake when there may be multiple options. All fields
32 // specified must match the candidate certificate for it to be chosen.
33 // This was needed to solve https://github.com/caddyserver/caddy/issues/2588.
34 type CustomCertSelectionPolicy struct {
35 // The certificate must have one of these serial numbers.
36 SerialNumber []bigInt `json:"serial_number,omitempty"`
37
38 // The certificate must have one of these organization names.
39 SubjectOrganization []string `json:"subject_organization,omitempty"`
40
41 // The certificate must use this public key algorithm.
42 PublicKeyAlgorithm PublicKeyAlgorithm `json:"public_key_algorithm,omitempty"`
43
44 // The certificate must have at least one of the tags in the list.
45 AnyTag []string `json:"any_tag,omitempty"`
46
47 // The certificate must have all of the tags in the list.
48 AllTags []string `json:"all_tags,omitempty"`
49 }
50
51 // SelectCertificate implements certmagic.CertificateSelector. It
52 // only chooses a certificate that at least meets the criteria in
53 // p. It then chooses the first non-expired certificate that is
54 // compatible with the client. If none are valid, it chooses the
55 // first viable candidate anyway.
56 func (p CustomCertSelectionPolicy) SelectCertificate(hello *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) {
57 viable := make([]certmagic.Certificate, 0, len(choices))
58
59 nextChoice:
60 for _, cert := range choices {
61 if len(p.SerialNumber) > 0 {
62 var found bool
63 for _, sn := range p.SerialNumber {
64 snInt := sn.Int // avoid taking address of iteration variable (gosec warning)
65 if cert.Leaf.SerialNumber.Cmp(&snInt) == 0 {
66 found = true
67 break
68 }
69 }
70 if !found {
71 continue
72 }
73 }
74
75 if len(p.SubjectOrganization) > 0 {
76 found := slices.ContainsFunc(p.SubjectOrganization, func(s string) bool {
77 return slices.Contains(cert.Leaf.Subject.Organization, s)
78 })
79 if !found {
80 continue
81 }
82 }
83
84 if p.PublicKeyAlgorithm != PublicKeyAlgorithm(x509.UnknownPublicKeyAlgorithm) &&
85 PublicKeyAlgorithm(cert.Leaf.PublicKeyAlgorithm) != p.PublicKeyAlgorithm {
86 continue
87 }
88
89 if len(p.AnyTag) > 0 {
90 found := slices.ContainsFunc(p.AnyTag, cert.HasTag)
91 if !found {
92 continue
93 }
94 }
95
96 if len(p.AllTags) > 0 {
97 for _, tag := range p.AllTags {
98 if !cert.HasTag(tag) {
99 continue nextChoice
100 }
101 }
102 }
103
104 // this certificate at least meets the policy's requirements,
105 // but we still have to check expiration and compatibility
106 viable = append(viable, cert)
107 }
108
109 if len(viable) == 0 {
110 return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy")
111 }
112
113 return certmagic.DefaultCertificateSelector(hello, viable)
114 }
115
116 // UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
117 //
118 // cert_selection {
119 // all_tags <values...>
120 // any_tag <values...>
121 // public_key_algorithm <dsa|ecdsa|rsa>
122 // serial_number <big_integers...>
123 // subject_organization <values...>
124 // }
125 func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
126 _, wrapper := d.Next(), d.Val() // consume wrapper name
127
128 // No same-line options are supported
129 if d.CountRemainingArgs() > 0 {
130 return d.ArgErr()
131 }
132
133 var hasPublicKeyAlgorithm bool
134 for nesting := d.Nesting(); d.NextBlock(nesting); {
135 optionName := d.Val()
136 switch optionName {
137 case "all_tags":
138 if d.CountRemainingArgs() == 0 {
139 return d.ArgErr()
140 }
141 p.AllTags = append(p.AllTags, d.RemainingArgs()...)
142 case "any_tag":
143 if d.CountRemainingArgs() == 0 {
144 return d.ArgErr()
145 }
146 p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
147 case "public_key_algorithm":
148 if hasPublicKeyAlgorithm {
149 return d.Errf("duplicate %s option '%s'", wrapper, optionName)
150 }
151 if d.CountRemainingArgs() != 1 {
152 return d.ArgErr()
153 }
154 d.NextArg()
155 if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
156 return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
157 }
158 hasPublicKeyAlgorithm = true
159 case "serial_number":
160 if d.CountRemainingArgs() == 0 {
161 return d.ArgErr()
162 }
163 for d.NextArg() {
164 val, bi := d.Val(), bigInt{}
165 _, ok := bi.SetString(val, 10)
166 if !ok {
167 return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
168 }
169 p.SerialNumber = append(p.SerialNumber, bi)
170 }
171 case "subject_organization":
172 if d.CountRemainingArgs() == 0 {
173 return d.ArgErr()
174 }
175 p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
176 default:
177 return d.ArgErr()
178 }
179
180 // No nested blocks are supported
181 if d.NextBlock(nesting + 1) {
182 return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
183 }
184 }
185
186 return nil
187 }
188
189 // bigInt is a big.Int type that interops with JSON encodings as a string.
190 type bigInt struct{ big.Int }
191
192 func (bi bigInt) MarshalJSON() ([]byte, error) {
193 return json.Marshal(bi.String())
194 }
195
196 func (bi *bigInt) UnmarshalJSON(p []byte) error {
197 if string(p) == "null" {
198 return nil
199 }
200 var stringRep string
201 err := json.Unmarshal(p, &stringRep)
202 if err != nil {
203 return err
204 }
205 _, ok := bi.SetString(stringRep, 10)
206 if !ok {
207 return fmt.Errorf("not a valid big integer: %s", p)
208 }
209 return nil
210 }
211
212 // Interface guard
213 var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil)