package tls
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"crypto/x509"
"errors"
"io"
"golang.org/x/crypto/cryptobyte"
)
type SessionState struct {
Extra [][]byte
EarlyData bool
version uint16
isClient bool
cipherSuite uint16
createdAt uint64
secret []byte
extMasterSecret bool
peerCertificates []*x509 .Certificate
activeCertHandles []*activeCert
ocspResponse []byte
scts [][]byte
verifiedChains [][]*x509 .Certificate
alpnProtocol string
useBy uint64
ageAdd uint32
}
func (s *SessionState ) Bytes () ([]byte , error ) {
var b cryptobyte .Builder
b .AddUint16 (s .version )
if s .isClient {
b .AddUint8 (2 )
} else {
b .AddUint8 (1 )
}
b .AddUint16 (s .cipherSuite )
addUint64 (&b , s .createdAt )
b .AddUint8LengthPrefixed (func (b *cryptobyte .Builder ) {
b .AddBytes (s .secret )
})
b .AddUint24LengthPrefixed (func (b *cryptobyte .Builder ) {
for _ , extra := range s .Extra {
b .AddUint24LengthPrefixed (func (b *cryptobyte .Builder ) {
b .AddBytes (extra )
})
}
})
if s .extMasterSecret {
b .AddUint8 (1 )
} else {
b .AddUint8 (0 )
}
if s .EarlyData {
b .AddUint8 (1 )
} else {
b .AddUint8 (0 )
}
marshalCertificate (&b , Certificate {
Certificate : certificatesToBytesSlice (s .peerCertificates ),
OCSPStaple : s .ocspResponse ,
SignedCertificateTimestamps : s .scts ,
})
b .AddUint24LengthPrefixed (func (b *cryptobyte .Builder ) {
for _ , chain := range s .verifiedChains {
b .AddUint24LengthPrefixed (func (b *cryptobyte .Builder ) {
if len (chain ) == 0 {
b .SetError (errors .New ("tls: internal error: empty verified chain" ))
return
}
for _ , cert := range chain [1 :] {
b .AddUint24LengthPrefixed (func (b *cryptobyte .Builder ) {
b .AddBytes (cert .Raw )
})
}
})
}
})
if s .EarlyData {
b .AddUint8LengthPrefixed (func (b *cryptobyte .Builder ) {
b .AddBytes ([]byte (s .alpnProtocol ))
})
}
if s .isClient {
if s .version >= VersionTLS13 {
addUint64 (&b , s .useBy )
b .AddUint32 (s .ageAdd )
}
}
return b .Bytes ()
}
func certificatesToBytesSlice (certs []*x509 .Certificate ) [][]byte {
s := make ([][]byte , 0 , len (certs ))
for _ , c := range certs {
s = append (s , c .Raw )
}
return s
}
func ParseSessionState (data []byte ) (*SessionState , error ) {
ss := &SessionState {}
s := cryptobyte .String (data )
var typ , extMasterSecret , earlyData uint8
var cert Certificate
var extra cryptobyte .String
if !s .ReadUint16 (&ss .version ) ||
!s .ReadUint8 (&typ ) ||
(typ != 1 && typ != 2 ) ||
!s .ReadUint16 (&ss .cipherSuite ) ||
!readUint64 (&s , &ss .createdAt ) ||
!readUint8LengthPrefixed (&s , &ss .secret ) ||
!s .ReadUint24LengthPrefixed (&extra ) ||
!s .ReadUint8 (&extMasterSecret ) ||
!s .ReadUint8 (&earlyData ) ||
len (ss .secret ) == 0 ||
!unmarshalCertificate (&s , &cert ) {
return nil , errors .New ("tls: invalid session encoding" )
}
for !extra .Empty () {
var e []byte
if !readUint24LengthPrefixed (&extra , &e ) {
return nil , errors .New ("tls: invalid session encoding" )
}
ss .Extra = append (ss .Extra , e )
}
switch extMasterSecret {
case 0 :
ss .extMasterSecret = false
case 1 :
ss .extMasterSecret = true
default :
return nil , errors .New ("tls: invalid session encoding" )
}
switch earlyData {
case 0 :
ss .EarlyData = false
case 1 :
ss .EarlyData = true
default :
return nil , errors .New ("tls: invalid session encoding" )
}
for _ , cert := range cert .Certificate {
c , err := globalCertCache .newCert (cert )
if err != nil {
return nil , err
}
ss .activeCertHandles = append (ss .activeCertHandles , c )
ss .peerCertificates = append (ss .peerCertificates , c .cert )
}
ss .ocspResponse = cert .OCSPStaple
ss .scts = cert .SignedCertificateTimestamps
var chainList cryptobyte .String
if !s .ReadUint24LengthPrefixed (&chainList ) {
return nil , errors .New ("tls: invalid session encoding" )
}
for !chainList .Empty () {
var certList cryptobyte .String
if !chainList .ReadUint24LengthPrefixed (&certList ) {
return nil , errors .New ("tls: invalid session encoding" )
}
var chain []*x509 .Certificate
if len (ss .peerCertificates ) == 0 {
return nil , errors .New ("tls: invalid session encoding" )
}
chain = append (chain , ss .peerCertificates [0 ])
for !certList .Empty () {
var cert []byte
if !readUint24LengthPrefixed (&certList , &cert ) {
return nil , errors .New ("tls: invalid session encoding" )
}
c , err := globalCertCache .newCert (cert )
if err != nil {
return nil , err
}
ss .activeCertHandles = append (ss .activeCertHandles , c )
chain = append (chain , c .cert )
}
ss .verifiedChains = append (ss .verifiedChains , chain )
}
if ss .EarlyData {
var alpn []byte
if !readUint8LengthPrefixed (&s , &alpn ) {
return nil , errors .New ("tls: invalid session encoding" )
}
ss .alpnProtocol = string (alpn )
}
if isClient := typ == 2 ; !isClient {
if !s .Empty () {
return nil , errors .New ("tls: invalid session encoding" )
}
return ss , nil
}
ss .isClient = true
if len (ss .peerCertificates ) == 0 {
return nil , errors .New ("tls: no server certificates in client session" )
}
if ss .version < VersionTLS13 {
if !s .Empty () {
return nil , errors .New ("tls: invalid session encoding" )
}
return ss , nil
}
if !s .ReadUint64 (&ss .useBy ) || !s .ReadUint32 (&ss .ageAdd ) || !s .Empty () {
return nil , errors .New ("tls: invalid session encoding" )
}
return ss , nil
}
func (c *Conn ) sessionState () (*SessionState , error ) {
return &SessionState {
version : c .vers ,
cipherSuite : c .cipherSuite ,
createdAt : uint64 (c .config .time ().Unix ()),
alpnProtocol : c .clientProtocol ,
peerCertificates : c .peerCertificates ,
activeCertHandles : c .activeCertHandles ,
ocspResponse : c .ocspResponse ,
scts : c .scts ,
isClient : c .isClient ,
extMasterSecret : c .extMasterSecret ,
verifiedChains : c .verifiedChains ,
}, nil
}
func (c *Config ) EncryptTicket (cs ConnectionState , ss *SessionState ) ([]byte , error ) {
ticketKeys := c .ticketKeys (nil )
stateBytes , err := ss .Bytes ()
if err != nil {
return nil , err
}
return c .encryptTicket (stateBytes , ticketKeys )
}
func (c *Config ) encryptTicket (state []byte , ticketKeys []ticketKey ) ([]byte , error ) {
if len (ticketKeys ) == 0 {
return nil , errors .New ("tls: internal error: session ticket keys unavailable" )
}
encrypted := make ([]byte , aes .BlockSize +len (state )+sha256 .Size )
iv := encrypted [:aes .BlockSize ]
ciphertext := encrypted [aes .BlockSize : len (encrypted )-sha256 .Size ]
authenticated := encrypted [:len (encrypted )-sha256 .Size ]
macBytes := encrypted [len (encrypted )-sha256 .Size :]
if _ , err := io .ReadFull (c .rand (), iv ); err != nil {
return nil , err
}
key := ticketKeys [0 ]
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 (ciphertext , state )
mac := hmac .New (sha256 .New , key .hmacKey [:])
mac .Write (authenticated )
mac .Sum (macBytes [:0 ])
return encrypted , nil
}
func (c *Config ) DecryptTicket (identity []byte , cs ConnectionState ) (*SessionState , error ) {
ticketKeys := c .ticketKeys (nil )
stateBytes := c .decryptTicket (identity , ticketKeys )
if stateBytes == nil {
return nil , nil
}
s , err := ParseSessionState (stateBytes )
if err != nil {
return nil , nil
}
return s , nil
}
func (c *Config ) decryptTicket (encrypted []byte , ticketKeys []ticketKey ) []byte {
if len (encrypted ) < aes .BlockSize +sha256 .Size {
return nil
}
iv := encrypted [:aes .BlockSize ]
ciphertext := encrypted [aes .BlockSize : len (encrypted )-sha256 .Size ]
authenticated := encrypted [:len (encrypted )-sha256 .Size ]
macBytes := encrypted [len (encrypted )-sha256 .Size :]
for _ , key := range ticketKeys {
mac := hmac .New (sha256 .New , key .hmacKey [:])
mac .Write (authenticated )
expected := mac .Sum (nil )
if subtle .ConstantTimeCompare (macBytes , expected ) != 1 {
continue
}
block , err := aes .NewCipher (key .aesKey [:])
if err != nil {
return nil
}
plaintext := make ([]byte , len (ciphertext ))
cipher .NewCTR (block , iv ).XORKeyStream (plaintext , ciphertext )
return plaintext
}
return nil
}
type ClientSessionState struct {
ticket []byte
session *SessionState
}
func (cs *ClientSessionState ) ResumptionState () (ticket []byte , state *SessionState , err error ) {
return cs .ticket , cs .session , nil
}
func NewResumptionState (ticket []byte , state *SessionState ) (*ClientSessionState , error ) {
return &ClientSessionState {
ticket : ticket , session : state ,
}, nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .