Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
caddy
Commits
b149a86b
Commit
b149a86b
authored
Apr 12, 2016
by
W. Mark Kubacki
Committed by
Matt Holt
Apr 12, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
server: Rotate TLS ticket "keys" (#742)
parent
ac80f6ed
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
139 additions
and
0 deletions
+139
-0
server/server.go
server/server.go
+79
-0
server/server_test.go
server/server_test.go
+60
-0
No files found.
server/server.go
View file @
b149a86b
...
...
@@ -4,9 +4,11 @@
package
server
import
(
"crypto/rand"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"net"
...
...
@@ -18,6 +20,11 @@ import (
"time"
)
const
(
tlsNewTicketEvery
=
time
.
Hour
*
10
// generate a new ticket for TLS PFS encryption every so often
tlsNumTickets
=
4
// hold and consider that many tickets to decrypt TLS sessions
)
// Server represents an instance of a server, which serves
// HTTP requests at a particular address (host and port). A
// server is capable of serving numerous virtual hosts on
...
...
@@ -28,6 +35,7 @@ type Server struct {
HTTP2
bool
// whether to enable HTTP/2
tls
bool
// whether this server is serving all HTTPS hosts or not
OnDemandTLS
bool
// whether this server supports on-demand TLS (load certs at handshake-time)
tlsGovChan
chan
struct
{}
// close to stop the TLS maintenance goroutine
vhosts
map
[
string
]
virtualHost
// virtual hosts keyed by their address
listener
ListenerFile
// the listener which is bound to the socket
listenerMu
sync
.
Mutex
// protects listener
...
...
@@ -216,6 +224,11 @@ func serveTLS(s *Server, ln net.Listener, tlsConfigs []TLSConfig) error {
return
err
}
// Setup any goroutines governing over TLS settings
s
.
tlsGovChan
=
make
(
chan
struct
{})
timer
:=
time
.
NewTicker
(
tlsNewTicketEvery
)
go
runTLSTicketKeyRotation
(
s
.
TLSConfig
,
timer
,
s
.
tlsGovChan
)
// Create TLS listener - note that we do not replace s.listener
// with this TLS listener; tls.listener is unexported and does
// not implement the File() method we need for graceful restarts
...
...
@@ -258,6 +271,11 @@ func (s *Server) Stop() (err error) {
}
s
.
listenerMu
.
Unlock
()
// Closing this signals any TLS governor goroutines to exit
if
s
.
tlsGovChan
!=
nil
{
close
(
s
.
tlsGovChan
)
}
return
}
...
...
@@ -378,6 +396,67 @@ func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
return
nil
}
var
runTLSTicketKeyRotation
=
standaloneTLSTicketKeyRotation
var
setSessionTicketKeysTestHook
=
func
(
keys
[][
32
]
byte
)
[][
32
]
byte
{
return
keys
}
// standaloneTLSTicketKeyRotation governs over the array of TLS ticket keys used to de/crypt TLS tickets.
// It periodically sets a new ticket key as the first one, used to encrypt (and decrypt),
// pushing any old ticket keys to the back, where they are considered for decryption only.
//
// Lack of entropy for the very first ticket key results in the feature being disabled (as does Go),
// later lack of entropy temporarily disables ticket key rotation.
// Old ticket keys are still phased out, though.
//
// Stops the timer when returning.
func
standaloneTLSTicketKeyRotation
(
c
*
tls
.
Config
,
timer
*
time
.
Ticker
,
exitChan
chan
struct
{})
{
defer
timer
.
Stop
()
// The entire page should be marked as sticky, but Go cannot do that
// without resorting to syscall#Mlock. And, we don't have madvise (for NODUMP), too. ☹
keys
:=
make
([][
32
]
byte
,
1
,
tlsNumTickets
)
rng
:=
c
.
Rand
if
rng
==
nil
{
rng
=
rand
.
Reader
}
if
_
,
err
:=
io
.
ReadFull
(
rng
,
keys
[
0
][
:
]);
err
!=
nil
{
c
.
SessionTicketsDisabled
=
true
// bail if we don't have the entropy for the first one
return
}
c
.
SetSessionTicketKeys
(
setSessionTicketKeysTestHook
(
keys
))
for
{
select
{
case
_
,
isOpen
:=
<-
exitChan
:
if
!
isOpen
{
return
}
case
<-
timer
.
C
:
rng
=
c
.
Rand
// could've changed since the start
if
rng
==
nil
{
rng
=
rand
.
Reader
}
var
newTicketKey
[
32
]
byte
_
,
err
:=
io
.
ReadFull
(
rng
,
newTicketKey
[
:
])
if
len
(
keys
)
<
tlsNumTickets
{
keys
=
append
(
keys
,
keys
[
0
])
// manipulates the internal length
}
for
idx
:=
len
(
keys
)
-
1
;
idx
>=
1
;
idx
--
{
keys
[
idx
]
=
keys
[
idx
-
1
]
// yes, this makes copies
}
if
err
==
nil
{
keys
[
0
]
=
newTicketKey
}
// pushes the last key out, doesn't matter that we don't have a new one
c
.
SetSessionTicketKeys
(
setSessionTicketKeysTestHook
(
keys
))
}
}
}
// RunFirstStartupFuncs runs all of the server's FirstStartup
// callback functions unless one of them returns an error first.
// It is the caller's responsibility to call this only once and
...
...
server/server_test.go
0 → 100644
View file @
b149a86b
package
server
import
(
"crypto/tls"
"testing"
"time"
)
func
TestStandaloneTLSTicketKeyRotation
(
t
*
testing
.
T
)
{
tlsGovChan
:=
make
(
chan
struct
{})
defer
close
(
tlsGovChan
)
callSync
:=
make
(
chan
bool
,
1
)
defer
close
(
callSync
)
oldHook
:=
setSessionTicketKeysTestHook
defer
func
()
{
setSessionTicketKeysTestHook
=
oldHook
}()
var
keysInUse
[][
32
]
byte
setSessionTicketKeysTestHook
=
func
(
keys
[][
32
]
byte
)
[][
32
]
byte
{
keysInUse
=
keys
callSync
<-
true
return
keys
}
c
:=
new
(
tls
.
Config
)
timer
:=
time
.
NewTicker
(
time
.
Millisecond
*
1
)
go
standaloneTLSTicketKeyRotation
(
c
,
timer
,
tlsGovChan
)
rounds
:=
0
var
lastTicketKey
[
32
]
byte
for
{
select
{
case
<-
callSync
:
if
lastTicketKey
==
keysInUse
[
0
]
{
close
(
tlsGovChan
)
t
.
Errorf
(
"The same TLS ticket key has been used again (not rotated): %x."
,
lastTicketKey
)
return
}
lastTicketKey
=
keysInUse
[
0
]
rounds
++
if
rounds
<=
tlsNumTickets
&&
len
(
keysInUse
)
!=
rounds
{
close
(
tlsGovChan
)
t
.
Errorf
(
"Expected TLS ticket keys in use: %d; Got instead: %d."
,
rounds
,
len
(
keysInUse
))
return
}
if
c
.
SessionTicketsDisabled
==
true
{
t
.
Error
(
"Session tickets have been disabled unexpectedly."
)
return
}
if
rounds
>=
tlsNumTickets
+
1
{
return
}
case
<-
time
.
After
(
time
.
Second
*
1
)
:
t
.
Errorf
(
"Timeout after %d rounds."
,
rounds
)
return
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment