03a329f274
This change adds a new method to tls.Config, SetSessionTicketKeys, that changes the key used to encrypt session tickets while the server is running. Additional keys may be provided that will be used to maintain continuity while rotating keys. If a ticket encrypted with an old key is provided by the client, the server will resume the session and provide the client with a ticket encrypted using the new key. Fixes #9994 Change-Id: Idbc16b10ff39616109a51ed39a6fa208faad5b4e Reviewed-on: https://go-review.googlesource.com/9072 Reviewed-by: Jonathan Rudenberg <jonathan@titanous.com> Reviewed-by: Adam Langley <agl@golang.org>
205 lines
4.7 KiB
Go
205 lines
4.7 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package tls
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// sessionState contains the information that is serialized into a session
|
|
// ticket in order to later resume a connection.
|
|
type sessionState struct {
|
|
vers uint16
|
|
cipherSuite uint16
|
|
masterSecret []byte
|
|
certificates [][]byte
|
|
// usedOldKey is true if the ticket from which this session came from
|
|
// was encrypted with an older key and thus should be refreshed.
|
|
usedOldKey bool
|
|
}
|
|
|
|
func (s *sessionState) equal(i interface{}) bool {
|
|
s1, ok := i.(*sessionState)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if s.vers != s1.vers ||
|
|
s.cipherSuite != s1.cipherSuite ||
|
|
!bytes.Equal(s.masterSecret, s1.masterSecret) {
|
|
return false
|
|
}
|
|
|
|
if len(s.certificates) != len(s1.certificates) {
|
|
return false
|
|
}
|
|
|
|
for i := range s.certificates {
|
|
if !bytes.Equal(s.certificates[i], s1.certificates[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (s *sessionState) marshal() []byte {
|
|
length := 2 + 2 + 2 + len(s.masterSecret) + 2
|
|
for _, cert := range s.certificates {
|
|
length += 4 + len(cert)
|
|
}
|
|
|
|
ret := make([]byte, length)
|
|
x := ret
|
|
x[0] = byte(s.vers >> 8)
|
|
x[1] = byte(s.vers)
|
|
x[2] = byte(s.cipherSuite >> 8)
|
|
x[3] = byte(s.cipherSuite)
|
|
x[4] = byte(len(s.masterSecret) >> 8)
|
|
x[5] = byte(len(s.masterSecret))
|
|
x = x[6:]
|
|
copy(x, s.masterSecret)
|
|
x = x[len(s.masterSecret):]
|
|
|
|
x[0] = byte(len(s.certificates) >> 8)
|
|
x[1] = byte(len(s.certificates))
|
|
x = x[2:]
|
|
|
|
for _, cert := range s.certificates {
|
|
x[0] = byte(len(cert) >> 24)
|
|
x[1] = byte(len(cert) >> 16)
|
|
x[2] = byte(len(cert) >> 8)
|
|
x[3] = byte(len(cert))
|
|
copy(x[4:], cert)
|
|
x = x[4+len(cert):]
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (s *sessionState) unmarshal(data []byte) bool {
|
|
if len(data) < 8 {
|
|
return false
|
|
}
|
|
|
|
s.vers = uint16(data[0])<<8 | uint16(data[1])
|
|
s.cipherSuite = uint16(data[2])<<8 | uint16(data[3])
|
|
masterSecretLen := int(data[4])<<8 | int(data[5])
|
|
data = data[6:]
|
|
if len(data) < masterSecretLen {
|
|
return false
|
|
}
|
|
|
|
s.masterSecret = data[:masterSecretLen]
|
|
data = data[masterSecretLen:]
|
|
|
|
if len(data) < 2 {
|
|
return false
|
|
}
|
|
|
|
numCerts := int(data[0])<<8 | int(data[1])
|
|
data = data[2:]
|
|
|
|
s.certificates = make([][]byte, numCerts)
|
|
for i := range s.certificates {
|
|
if len(data) < 4 {
|
|
return false
|
|
}
|
|
certLen := int(data[0])<<24 | int(data[1])<<16 | int(data[2])<<8 | int(data[3])
|
|
data = data[4:]
|
|
if certLen < 0 {
|
|
return false
|
|
}
|
|
if len(data) < certLen {
|
|
return false
|
|
}
|
|
s.certificates[i] = data[:certLen]
|
|
data = data[certLen:]
|
|
}
|
|
|
|
if len(data) > 0 {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (c *Conn) encryptTicket(state *sessionState) ([]byte, error) {
|
|
serialized := state.marshal()
|
|
encrypted := make([]byte, ticketKeyNameLen+aes.BlockSize+len(serialized)+sha256.Size)
|
|
keyName := encrypted[:ticketKeyNameLen]
|
|
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
|
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
|
|
|
if _, err := io.ReadFull(c.config.rand(), iv); err != nil {
|
|
return nil, err
|
|
}
|
|
key := c.config.ticketKeys()[0]
|
|
copy(keyName, key.keyName[:])
|
|
block, err := aes.NewCipher(key.aesKey[:])
|
|
if err != nil {
|
|
return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error())
|
|
}
|
|
cipher.NewCTR(block, iv).XORKeyStream(encrypted[ticketKeyNameLen+aes.BlockSize:], serialized)
|
|
|
|
mac := hmac.New(sha256.New, key.hmacKey[:])
|
|
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
|
mac.Sum(macBytes[:0])
|
|
|
|
return encrypted, nil
|
|
}
|
|
|
|
func (c *Conn) decryptTicket(encrypted []byte) (*sessionState, bool) {
|
|
if c.config.SessionTicketsDisabled ||
|
|
len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
|
|
return nil, false
|
|
}
|
|
|
|
keyName := encrypted[:ticketKeyNameLen]
|
|
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
|
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
|
|
|
keys := c.config.ticketKeys()
|
|
keyIndex := -1
|
|
for i, candidateKey := range keys {
|
|
if bytes.Equal(keyName, candidateKey.keyName[:]) {
|
|
keyIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if keyIndex == -1 {
|
|
return nil, false
|
|
}
|
|
key := &keys[keyIndex]
|
|
|
|
mac := hmac.New(sha256.New, key.hmacKey[:])
|
|
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
|
expected := mac.Sum(nil)
|
|
|
|
if subtle.ConstantTimeCompare(macBytes, expected) != 1 {
|
|
return nil, false
|
|
}
|
|
|
|
block, err := aes.NewCipher(key.aesKey[:])
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size]
|
|
plaintext := ciphertext
|
|
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
|
|
|
|
state := &sessionState{usedOldKey: keyIndex > 0}
|
|
ok := state.unmarshal(plaintext)
|
|
return state, ok
|
|
}
|