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
| 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 | ) |