@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 245 lines 7.23 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/rand"
19 "crypto/tls"
20 "encoding/json"
21 "fmt"
22 "io"
23 "log"
24 "runtime/debug"
25 "sync"
26 "time"
27
28 "github.com/caddyserver/caddy/v2"
29 )
30
31 // SessionTicketService configures and manages TLS session tickets.
32 type SessionTicketService struct {
33 // KeySource is the method by which Caddy produces or obtains
34 // TLS session ticket keys (STEKs). By default, Caddy generates
35 // them internally using a secure pseudorandom source.
36 KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"`
37
38 // How often Caddy rotates STEKs. Default: 12h.
39 RotationInterval caddy.Duration `json:"rotation_interval,omitempty"`
40
41 // The maximum number of keys to keep in rotation. Default: 4.
42 MaxKeys int `json:"max_keys,omitempty"`
43
44 // Disables STEK rotation.
45 DisableRotation bool `json:"disable_rotation,omitempty"`
46
47 // Disables TLS session resumption by tickets.
48 Disabled bool `json:"disabled,omitempty"`
49
50 keySource STEKProvider
51 configs map[*tls.Config]struct{}
52 stopChan chan struct{}
53 currentKeys [][32]byte
54 mu *sync.Mutex
55 }
56
57 func (s *SessionTicketService) provision(ctx caddy.Context) error {
58 s.configs = make(map[*tls.Config]struct{})
59 s.mu = new(sync.Mutex)
60
61 // establish sane defaults
62 if s.RotationInterval == 0 {
63 s.RotationInterval = caddy.Duration(defaultSTEKRotationInterval)
64 }
65 if s.MaxKeys <= 0 {
66 s.MaxKeys = defaultMaxSTEKs
67 }
68 if s.KeySource == nil {
69 s.KeySource = json.RawMessage(`{"provider":"standard"}`)
70 }
71
72 // load the STEK module, which will provide keys
73 val, err := ctx.LoadModule(s, "KeySource")
74 if err != nil {
75 return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err)
76 }
77 s.keySource = val.(STEKProvider)
78
79 // if session tickets or just rotation are
80 // disabled, no need to start service
81 if s.Disabled || s.DisableRotation {
82 return nil
83 }
84
85 // start the STEK module; this ensures we have
86 // a starting key before any config needs one
87 return s.start()
88 }
89
90 // start loads the starting STEKs and spawns a goroutine
91 // which loops to rotate the STEKs, which continues until
92 // stop() is called. If start() was already called, this
93 // is a no-op.
94 func (s *SessionTicketService) start() error {
95 if s.stopChan != nil {
96 return nil
97 }
98 s.stopChan = make(chan struct{})
99
100 // initializing the key source gives us our
101 // initial key(s) to start with; if successful,
102 // we need to be sure to call Next() so that
103 // the key source can know when it is done
104 initialKeys, err := s.keySource.Initialize(s)
105 if err != nil {
106 return fmt.Errorf("setting STEK module configuration: %v", err)
107 }
108
109 s.mu.Lock()
110 s.currentKeys = initialKeys
111 s.mu.Unlock()
112
113 // keep the keys rotated
114 go s.stayUpdated()
115
116 return nil
117 }
118
119 // stayUpdated is a blocking function which rotates
120 // the keys whenever new ones are sent. It reads
121 // from keysChan until s.stop() is called.
122 func (s *SessionTicketService) stayUpdated() {
123 defer func() {
124 if err := recover(); err != nil {
125 log.Printf("[PANIC] session ticket service: %v\n%s", err, debug.Stack())
126 }
127 }()
128
129 // this call is essential when Initialize()
130 // returns without error, because the stop
131 // channel is the only way the key source
132 // will know when to clean up
133 keysChan := s.keySource.Next(s.stopChan)
134
135 for {
136 select {
137 case newKeys := <-keysChan:
138 s.mu.Lock()
139 s.currentKeys = newKeys
140 for cfg := range s.configs {
141 cfg.SetSessionTicketKeys(newKeys)
142 }
143 s.mu.Unlock()
144 case <-s.stopChan:
145 return
146 }
147 }
148 }
149
150 // stop terminates the key rotation goroutine.
151 func (s *SessionTicketService) stop() {
152 if s.stopChan != nil {
153 close(s.stopChan)
154 }
155 }
156
157 // register sets the session ticket keys on cfg
158 // and keeps them updated. Any values registered
159 // must be unregistered, or they will not be
160 // garbage-collected. s.start() must have been
161 // called first. If session tickets are disabled
162 // or if ticket key rotation is disabled, this
163 // function is a no-op.
164 func (s *SessionTicketService) register(cfg *tls.Config) {
165 if s.Disabled || s.DisableRotation {
166 return
167 }
168 s.mu.Lock()
169 cfg.SetSessionTicketKeys(s.currentKeys)
170 s.configs[cfg] = struct{}{}
171 s.mu.Unlock()
172 }
173
174 // unregister stops session key management on cfg and
175 // removes the internal stored reference to cfg. If
176 // session tickets are disabled or if ticket key rotation
177 // is disabled, this function is a no-op.
178 func (s *SessionTicketService) unregister(cfg *tls.Config) {
179 if s.Disabled || s.DisableRotation {
180 return
181 }
182 s.mu.Lock()
183 delete(s.configs, cfg)
184 s.mu.Unlock()
185 }
186
187 // RotateSTEKs rotates the keys in keys by producing a new key and eliding
188 // the oldest one. The new slice of keys is returned.
189 func (s SessionTicketService) RotateSTEKs(keys [][32]byte) ([][32]byte, error) {
190 // produce a new key
191 newKey, err := s.generateSTEK()
192 if err != nil {
193 return nil, fmt.Errorf("generating STEK: %v", err)
194 }
195
196 // we need to prepend this new key to the list of
197 // keys so that it is preferred, but we need to be
198 // careful that we do not grow the slice larger
199 // than MaxKeys, otherwise we'll be storing one
200 // more key in memory than we expect; so be sure
201 // that the slice does not grow beyond the limit
202 // even for a brief period of time, since there's
203 // no guarantee when that extra allocation will
204 // be overwritten; this is why we first trim the
205 // length to one less the max, THEN prepend the
206 // new key
207 if len(keys) >= s.MaxKeys {
208 keys[len(keys)-1] = [32]byte{} // zero-out memory of oldest key
209 keys = keys[:s.MaxKeys-1] // trim length of slice
210 }
211 keys = append([][32]byte{newKey}, keys...) // prepend new key
212
213 return keys, nil
214 }
215
216 // generateSTEK generates key material suitable for use as a
217 // session ticket ephemeral key.
218 func (s *SessionTicketService) generateSTEK() ([32]byte, error) {
219 var newTicketKey [32]byte
220 _, err := io.ReadFull(rand.Reader, newTicketKey[:])
221 return newTicketKey, err
222 }
223
224 // STEKProvider is a type that can provide session ticket ephemeral
225 // keys (STEKs).
226 type STEKProvider interface {
227 // Initialize provides the STEK configuration to the STEK
228 // module so that it can obtain and manage keys accordingly.
229 // It returns the initial key(s) to use. Implementations can
230 // rely on Next() being called if Initialize() returns
231 // without error, so that it may know when it is done.
232 Initialize(config *SessionTicketService) ([][32]byte, error)
233
234 // Next returns the channel through which the next session
235 // ticket keys will be transmitted until doneChan is closed.
236 // Keys should be sent on keysChan as they are updated.
237 // When doneChan is closed, any resources allocated in
238 // Initialize() must be cleaned up.
239 Next(doneChan <-chan struct{}) (keysChan <-chan [][32]byte)
240 }
241
242 const (
243 defaultSTEKRotationInterval = 12 * time.Hour
244 defaultMaxSTEKs = 4
245 )