utls/u_server.go
2023-07-16 10:03:08 -06:00

198 lines
5.6 KiB
Go

package tls
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"errors"
"fmt"
"io"
"time"
)
// ServerSessionState contains the information that is serialized into a session
// ticket in order to later resume a connection.
type ServerSessionState struct {
Vers uint16
CipherSuite uint16
CreatedAt uint64
MasterSecret []byte // opaque master_secret<1..2^16-1>;
// struct { opaque certificate<1..2^24-1> } Certificate;
Certificates [][]byte // Certificate certificate_list<0..2^24-1>;
// 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
}
// ForgeServerSessionState allows the creation of a Session (and SessionTicket)
// from a (presumably shared) secret value allowing a client to to
// "re-establish" a non-existent previous connection. With these values a
// ClientSessionState can be created to "resume" a session based on the secret
// value known to both the client and the server.
//
// Warning: you should probably not use this function, unless you are absolutely
// sure this is the functionality you are looking for.
func ForgeServerSessionState(masterSecret []byte, serverConfig *Config, chID ClientHelloID) (*ServerSessionState, error) {
chSpec, err := utlsIdToSpec(chID)
if err != nil {
return nil, err
}
clientVersions := []uint16{}
minVers, maxVers, err := getTLSVers(chSpec.TLSVersMin, chSpec.TLSVersMax, chSpec.Extensions)
if err != nil {
return nil, err
}
clientVersions = makeSupportedVersions(minVers, maxVers)
vers, ok := serverConfig.mutualVersion(roleServer, clientVersions)
if !ok {
return nil, fmt.Errorf("unable to select mutual version")
} else if vers < VersionTLS12 {
return nil, fmt.Errorf("selected mutual version too old")
}
clientCipherSuites := make([]uint16, len(chSpec.CipherSuites))
copy(clientCipherSuites, chSpec.CipherSuites)
chosenCiphersuite, err := pickCipherSuite(clientCipherSuites, vers, serverConfig)
if err != nil {
return nil, err
}
sessionState := &ServerSessionState{
Vers: vers,
CipherSuite: chosenCiphersuite,
CreatedAt: uint64(time.Now().UnixMicro()),
MasterSecret: masterSecret, // TODO
Certificates: nil,
// We are fabricating this session state for the key so it can't be old.
UsedOldKey: false,
}
return sessionState, nil
}
func filterClientCiphers(c []*cipherSuite) []*cipherSuite {
return []*cipherSuite{}
}
// func filterClientCipher(c *cipherSuite) bool {
// if c.flags&suiteECDHE != 0 {
// if !hs.ecdheOk {
// return false
// }
// if c.flags&suiteECSign != 0 {
// if !hs.ecSignOk {
// return false
// }
// } else if !hs.rsaSignOk {
// return false
// }
// } else if !hs.rsaDecryptOk {
// return false
// }
// return true
// }
// Marshal serializes the sessionState object to bytes.
func (ss *ServerSessionState) Marshal() ([]byte, error) {
pss := ss.toPrivate()
if pss == nil {
return nil, nil
}
return pss.marshal()
}
func (ss *ServerSessionState) toPrivate() *sessionState {
if ss == nil {
return nil
}
return &sessionState{
vers: ss.Vers,
cipherSuite: ss.CipherSuite,
createdAt: ss.CreatedAt,
masterSecret: ss.MasterSecret,
certificates: ss.Certificates,
usedOldKey: ss.UsedOldKey,
}
}
// MakeEncryptedTicket creates an encrypted session ticket that a client can
// then use to "re-establish" a non-existent previous connection. The value
// provided as keyBytes should be added to the servers ticketKeys using something
// like SetSessionKeys.
func (ss *ServerSessionState) MakeEncryptedTicket(keyBytes [32]byte, config *Config) ([]byte, error) {
if config == nil {
config = &Config{}
}
key := config.ticketKeyFromBytes(keyBytes)
state, err := ss.Marshal()
if err != nil {
return nil, err
}
encrypted := make([]byte, ticketKeyNameLen+aes.BlockSize+len(state)+sha256.Size)
keyName := encrypted[:ticketKeyNameLen]
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
macBytes := encrypted[len(encrypted)-sha256.Size:]
if _, err := io.ReadFull(config.rand(), iv); err != nil {
return nil, err
}
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:], state)
mac := hmac.New(sha256.New, key.hmacKey[:])
mac.Write(encrypted[:len(encrypted)-sha256.Size])
mac.Sum(macBytes[:0])
return encrypted, nil
}
func pickCipherSuite(clientCipherSuites []uint16, vers uint16, config *Config) (uint16, error) {
preferenceOrder := cipherSuitesPreferenceOrder
if !hasAESGCMHardwareSupport || !aesgcmPreferred(clientCipherSuites) {
preferenceOrder = cipherSuitesPreferenceOrderNoAES
}
configCipherSuites := config.cipherSuites()
preferenceList := make([]uint16, 0, len(configCipherSuites))
for _, suiteID := range preferenceOrder {
for _, id := range configCipherSuites {
if id == suiteID {
preferenceList = append(preferenceList, id)
break
}
}
}
var cipherSuiteOk = func(*cipherSuite) bool {
return true
}
suite := selectCipherSuite(preferenceList, clientCipherSuites, cipherSuiteOk)
if suite == nil {
return 0, errors.New("tls: no cipher suite supported by both client and server")
}
cipherSuite := suite.id
for _, id := range clientCipherSuites {
if id == TLS_FALLBACK_SCSV {
// The client is doing a fallback connection. See RFC 7507.
if vers < config.maxSupportedVersion(roleServer) {
return 0, errors.New("tls: client using inappropriate protocol fallback")
}
break
}
}
return cipherSuite, nil
}