Ver código fonte

crypto/tls: implement TLS 1.3 server PSK

tls13
Filippo Valsorda 8 anos atrás
committed by Peter Wu
pai
commit
ee3048cfd2
12 arquivos alterados com 249 adições e 74 exclusões
  1. +6
    -5
      .travis.yml
  2. +187
    -49
      13.go
  3. +13
    -0
      _dev/bogo/bogo-client.go
  4. +6
    -1
      _dev/boring/run.sh
  5. +3
    -2
      _dev/interop.sh
  6. +11
    -0
      _dev/mint/mint-client.go
  7. +4
    -3
      _dev/tris-localserver/Dockerfile
  8. +5
    -1
      _dev/tris-localserver/server.go
  9. +4
    -1
      _dev/tstclnt/Dockerfile
  10. +1
    -1
      _dev/tstclnt/run.sh
  11. +6
    -5
      handshake_server.go
  12. +3
    -6
      ticket.go

+ 6
- 5
.travis.yml Ver arquivo

@@ -8,21 +8,22 @@ go:
- 1.7

env:
- MODE=gotest
- MODE=interop CLIENT=boring
- MODE=interop CLIENT=boring REVISION=origin/master
- MODE=interop CLIENT=bogo
- MODE=interop CLIENT=bogo REVISION=origin/master
- MODE=interop CLIENT=tstclnt
- MODE=interop CLIENT=tstclnt REVISION=default
- MODE=interop CLIENT=mint
- MODE=gotest
- MODE=interop CLIENT=boring REVISION=origin/master
- MODE=interop CLIENT=bogo REVISION=origin/master
- MODE=interop CLIENT=tstclnt REVISION=default

matrix:
fast_finish: true
allow_failures:
- env: MODE=interop CLIENT=bogo REVISION=origin/master
- env: MODE=interop CLIENT=boring REVISION=origin/master
- env: MODE=interop CLIENT=bogo REVISION=origin/master
- env: MODE=interop CLIENT=tstclnt REVISION=default
- env: MODE=interop CLIENT=mint # broken resumption client

install:
- if [ "$MODE" = "interop" ]; then ./_dev/tris-localserver/start.sh -d && docker ps -a; fi


+ 187
- 49
13.go Ver arquivo

@@ -14,6 +14,7 @@ import (
"io"
"os"
"runtime/debug"
"time"

"golang_org/x/crypto/curve25519"
)
@@ -55,6 +56,12 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
}
hashSize := hash.Size()

earlySecret, isPSK := hs.checkPSK(hash)
if !isPSK {
earlySecret = hkdfExtract(hash, nil, nil)
}
c.didResume = isPSK

ecdheSecret := deriveECDHESecret(ks, privateKey)
if ecdheSecret == nil {
c.sendAlert(alertIllegalParameter)
@@ -69,9 +76,7 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
return err
}

earlySecret := hkdfExtract(hash, nil, nil)
handshakeSecret := hkdfExtract(hash, ecdheSecret, earlySecret)

handshakeCtx := hs.finishedHash.Sum()

cHandshakeTS := hkdfExpandLabel(hash, handshakeSecret, handshakeCtx, "client handshake traffic secret", hashSize)
@@ -91,41 +96,10 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
return err
}

certMsg := &certificateMsg13{
certificates: hs.cert.Certificate,
}
hs.finishedHash.Write(certMsg.marshal())
if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil {
return err
}

sigScheme, err := hs.selectTLS13SignatureScheme()
if err != nil {
c.sendAlert(alertInternalError)
return err
}

sigHash := hashForSignatureScheme(sigScheme)
opts := crypto.SignerOpts(sigHash)
if signatureSchemeIsPSS(sigScheme) {
opts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
}

toSign := prepareDigitallySigned(sigHash, "TLS 1.3, server CertificateVerify", hs.finishedHash.Sum())
signature, err := hs.cert.PrivateKey.(crypto.Signer).Sign(config.rand(), toSign[:], opts)
if err != nil {
c.sendAlert(alertInternalError)
return err
}

verifyMsg := &certificateVerifyMsg{
hasSignatureAndHash: true,
signatureAndHash: sigSchemeToSigAndHash(sigScheme),
signature: signature,
}
hs.finishedHash.Write(verifyMsg.marshal())
if _, err := c.writeRecord(recordTypeHandshake, verifyMsg.marshal()); err != nil {
return err
if !isPSK {
if err := hs.sendCertificate13(); err != nil {
return err
}
}

serverFinishedKey := hkdfExpandLabel(hash, sHandshakeTS, nil, "finished", hashSize)
@@ -142,6 +116,22 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
return err
}

hs.masterSecret = hkdfExtract(hash, nil, handshakeSecret)
handshakeCtx = hs.finishedHash.Sum()

cTrafficSecret0 := hkdfExpandLabel(hash, hs.masterSecret, handshakeCtx, "client application traffic secret", hashSize)
cKey = hkdfExpandLabel(hash, cTrafficSecret0, nil, "key", hs.suite.keyLen)
cIV = hkdfExpandLabel(hash, cTrafficSecret0, nil, "iv", 12)
sTrafficSecret0 := hkdfExpandLabel(hash, hs.masterSecret, handshakeCtx, "server application traffic secret", hashSize)
sKey = hkdfExpandLabel(hash, sTrafficSecret0, nil, "key", hs.suite.keyLen)
sIV = hkdfExpandLabel(hash, sTrafficSecret0, nil, "iv", 12)

serverCipher = hs.suite.aead(sKey, sIV)
c.out.setCipher(c.vers, serverCipher)

// TODO(filippo): here we are ready to send Application Data, but we might want it opt-in.
// It will be refactored anyway for 0-RTT.

if _, err := c.flush(); err != nil {
return err
}
@@ -156,6 +146,7 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(clientFinished, msg)
}

h = hmac.New(hash.New, clientFinishedKey)
h.Write(hs.finishedHash.Sum())
expectedVerifyData := h.Sum(nil)
@@ -164,21 +155,53 @@ func (hs *serverHandshakeState) doTLS13Handshake() error {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: client's Finished message is incorrect")
}

masterSecret := hkdfExtract(hash, nil, handshakeSecret)
handshakeCtx = hs.finishedHash.Sum()

cTrafficSecret0 := hkdfExpandLabel(hash, masterSecret, handshakeCtx, "client application traffic secret", hashSize)
cKey = hkdfExpandLabel(hash, cTrafficSecret0, nil, "key", hs.suite.keyLen)
cIV = hkdfExpandLabel(hash, cTrafficSecret0, nil, "iv", 12)
sTrafficSecret0 := hkdfExpandLabel(hash, masterSecret, handshakeCtx, "server application traffic secret", hashSize)
sKey = hkdfExpandLabel(hash, sTrafficSecret0, nil, "key", hs.suite.keyLen)
sIV = hkdfExpandLabel(hash, sTrafficSecret0, nil, "iv", 12)
hs.finishedHash.Write(clientFinished.marshal())

clientCipher = hs.suite.aead(cKey, cIV)
c.in.setCipher(c.vers, clientCipher)
serverCipher = hs.suite.aead(sKey, sIV)
c.out.setCipher(c.vers, serverCipher)

return hs.sendSessionTicket13(hash)
}

func (hs *serverHandshakeState) sendCertificate13() error {
c := hs.c

certMsg := &certificateMsg13{
certificates: hs.cert.Certificate,
}
hs.finishedHash.Write(certMsg.marshal())
if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil {
return err
}

sigScheme, err := hs.selectTLS13SignatureScheme()
if err != nil {
c.sendAlert(alertInternalError)
return err
}

sigHash := hashForSignatureScheme(sigScheme)
opts := crypto.SignerOpts(sigHash)
if signatureSchemeIsPSS(sigScheme) {
opts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
}

toSign := prepareDigitallySigned(sigHash, "TLS 1.3, server CertificateVerify", hs.finishedHash.Sum())
signature, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), toSign[:], opts)
if err != nil {
c.sendAlert(alertInternalError)
return err
}

verifyMsg := &certificateVerifyMsg{
hasSignatureAndHash: true,
signatureAndHash: sigSchemeToSigAndHash(sigScheme),
signature: signature,
}
hs.finishedHash.Write(verifyMsg.marshal())
if _, err := c.writeRecord(recordTypeHandshake, verifyMsg.marshal()); err != nil {
return err
}

return nil
}
@@ -338,6 +361,121 @@ func hkdfExpandLabel(hash crypto.Hash, secret, hashValue []byte, label string, L
return hkdfExpand(hash, secret, hkdfLabel, L)
}

// Maximum allowed mismatch between the stated age of a ticket
// and the server-observed one. See
// https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8.2.
const ticketAgeSkewAllowance = 10 * time.Second

func (hs *serverHandshakeState) checkPSK(hash crypto.Hash) (earlySecret []byte, ok bool) {
if hs.c.config.SessionTicketsDisabled {
return nil, false
}

foundDHE := false
for _, mode := range hs.clientHello.pskKeyExchangeModes {
if mode == pskDHEKeyExchange {
foundDHE = true
break
}
}
if !foundDHE {
return nil, false
}

hashSize := hash.Size()
for i := range hs.clientHello.psks {
sessionTicket := append([]uint8{}, hs.clientHello.psks[i].identity...)
serializedTicket, _ := hs.c.decryptTicket(sessionTicket)
if serializedTicket == nil {
continue
}
s := &sessionState13{}
if ok := s.unmarshal(serializedTicket); !ok {
continue
}
if s.vers != hs.c.vers {
continue
}
clientAge := time.Duration(hs.clientHello.psks[i].obfTicketAge-s.ageAdd) * time.Millisecond
serverAge := time.Since(time.Unix(int64(s.createdAt), 0))
if clientAge-serverAge > ticketAgeSkewAllowance || clientAge-serverAge < -ticketAgeSkewAllowance {
continue
}
if s.hash != uint16(hash) {
continue
}

earlySecret := hkdfExtract(hash, s.resumptionSecret, nil)
handshakeCtx := hash.New().Sum(nil)
binderKey := hkdfExpandLabel(hash, earlySecret, handshakeCtx, "resumption psk binder key", hashSize)
binderFinishedKey := hkdfExpandLabel(hash, binderKey, nil, "finished", hashSize)
chHash := hash.New()
chHash.Write(hs.clientHello.rawTruncated)
h := hmac.New(hash.New, binderFinishedKey)
h.Write(chHash.Sum(nil))
expectedBinder := h.Sum(nil)

if subtle.ConstantTimeCompare(expectedBinder, hs.clientHello.psks[i].binder) == 1 {
hs.hello13.psk = true
hs.hello13.pskIdentity = uint16(i)
return earlySecret, true
}
}

return nil, false
}

func (hs *serverHandshakeState) sendSessionTicket13(hash crypto.Hash) error {
c := hs.c
if c.config.SessionTicketsDisabled {
return nil
}

foundDHE := false
for _, mode := range hs.clientHello.pskKeyExchangeModes {
if mode == pskDHEKeyExchange {
foundDHE = true
break
}
}
if !foundDHE {
return nil
}

handshakeCtx := hs.finishedHash.Sum()
resumptionSecret := hkdfExpandLabel(hash, hs.masterSecret, handshakeCtx, "resumption master secret", hash.Size())

ageAddBuf := make([]byte, 4)
if _, err := io.ReadFull(c.config.rand(), ageAddBuf); err != nil {
c.sendAlert(alertInternalError)
return err
}
sessionState := &sessionState13{
vers: c.vers,
hash: uint16(hash),
ageAdd: uint32(ageAddBuf[0])<<24 | uint32(ageAddBuf[1])<<16 |
uint32(ageAddBuf[2])<<8 | uint32(ageAddBuf[3]),
createdAt: uint64(time.Now().Unix()),
resumptionSecret: resumptionSecret,
}

ticket, err := c.encryptTicket(sessionState.marshal())
if err != nil {
c.sendAlert(alertInternalError)
return err
}
ticketMsg := &newSessionTicketMsg13{
lifetime: 21600, // TODO(filippo)
ageAdd: sessionState.ageAdd,
ticket: ticket,
}
if _, err := c.writeRecord(recordTypeHandshake, ticketMsg.marshal()); err != nil {
return err
}

return nil
}

// QuietError is an error wrapper that prevents the verbose handshake log
// dump on errors. Exposed for use by GetCertificate.
type QuietError struct {


+ 13
- 0
_dev/bogo/bogo-client.go Ver arquivo

@@ -10,14 +10,18 @@ import (
)

func main() {
sessionCache := runner.NewLRUClientSessionCache(10)
tr := &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
return runner.Dial(network, addr, &runner.Config{
InsecureSkipVerify: true,
ClientSessionCache: sessionCache,
})
},
DisableKeepAlives: true,
}
client := &http.Client{Transport: tr}

resp, err := client.Get("https://" + os.Args[1])
if err != nil {
log.Fatal(err)
@@ -25,4 +29,13 @@ func main() {
if err := resp.Write(os.Stdout); err != nil {
log.Fatal(err)
}

// Resumption
resp, err = client.Get("https://" + os.Args[1])
if err != nil {
log.Fatal(err)
}
if err := resp.Write(os.Stdout); err != nil {
log.Fatal(err)
}
}

+ 6
- 1
_dev/boring/run.sh Ver arquivo

@@ -1,3 +1,8 @@
#! /bin/sh
set -e

/boringssl/build/tool/bssl client -grease -min-version tls1.3 -max-version tls1.3 \
-session-out /session -connect "$@" < /httpreq.txt
exec /boringssl/build/tool/bssl client -grease -min-version tls1.3 -max-version tls1.3 \
-session-in /session -connect "$@" < /httpreq.txt

exec /boringssl/build/tool/bssl s_client -min-version tls1.3 -max-version tls1.3 -connect "$@" < /httpreq.txt

+ 3
- 2
_dev/interop.sh Ver arquivo

@@ -11,7 +11,8 @@ if [ "$1" = "INSTALL" ]; then

elif [ "$1" = "RUN" ]; then
IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' tris-localserver)
docker run -i --rm tls-tris:$2 $IP:$3 | tee output.txt
grep "Hello TLS 1.3" output.txt
docker run --rm tls-tris:$2 $IP:$3 | tee output.txt
grep "Hello TLS 1.3" output.txt | grep -v "resumed"
grep "Hello TLS 1.3" output.txt | grep "resumed"

fi

+ 11
- 0
_dev/mint/mint-client.go Ver arquivo

@@ -14,8 +14,10 @@ func main() {
DialTLS: func(network, addr string) (net.Conn, error) {
return mint.Dial(network, addr, nil)
},
DisableKeepAlives: true,
}
client := &http.Client{Transport: tr}

resp, err := client.Get("https://" + os.Args[1])
if err != nil {
log.Fatal(err)
@@ -23,4 +25,13 @@ func main() {
if err := resp.Write(os.Stdout); err != nil {
log.Fatal(err)
}

// Resumption
resp, err = client.Get("https://" + os.Args[1])
if err != nil {
log.Fatal(err)
}
if err := resp.Write(os.Stdout); err != nil {
log.Fatal(err)
}
}

+ 4
- 3
_dev/tris-localserver/Dockerfile Ver arquivo

@@ -1,10 +1,11 @@
FROM scratch

# GOOS=linux ../go.sh build -v -i .
ADD tris-localserver ./

ENV TLSDEBUG error

EXPOSE 443
EXPOSE 4443

# GOOS=linux ../go.sh build -v -i .
ADD tris-localserver ./

CMD [ "./tris-localserver", "0.0.0.0:443", "0.0.0.0:4443" ]

+ 5
- 1
_dev/tris-localserver/server.go Ver arquivo

@@ -19,7 +19,11 @@ var tlsVersionToName = map[uint16]string{

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<!DOCTYPE html><p>Hello TLS %s _o/\n", tlsVersionToName[r.TLS.Version])
resumed := ""
if r.TLS.DidResume {
resumed = " [resumed]"
}
fmt.Fprintf(w, "<!DOCTYPE html><p>Hello TLS %s%s _o/\n", tlsVersionToName[r.TLS.Version], resumed)
})

http.HandleFunc("/ch", func(w http.ResponseWriter, r *http.Request) {


+ 4
- 1
_dev/tstclnt/Dockerfile Ver arquivo

@@ -15,7 +15,10 @@ ENV USE_64=1 NSS_ENABLE_TLS_1_3=1
# ARG REVISION=3e7b53b18112

# Draft 18
ARG REVISION=b6dfef6d0ff0
# ARG REVISION=b6dfef6d0ff0

# tstclnt resumption
ARG REVISION=460a0a1e009f

RUN cd nss && hg pull
RUN cd nss && hg checkout -C $REVISION


+ 1
- 1
_dev/tstclnt/run.sh Ver arquivo

@@ -5,4 +5,4 @@ shift
HOST="${ADDR[0]}"
PORT="${ADDR[1]}"

exec /dist/OBJ-PATH/bin/tstclnt -D -V tls1.3:tls1.3 -o -O -h $HOST -p $PORT -v "$@" < /httpreq.txt
exec /dist/OBJ-PATH/bin/tstclnt -D -V tls1.3:tls1.3 -o -O -h $HOST -p $PORT -v -A /httpreq.txt -L 2 "$@"

+ 6
- 5
handshake_server.go Ver arquivo

@@ -278,7 +278,7 @@ Curves:
hs.hello.scts = hs.cert.SignedCertificateTimestamps
}

if committer, ok := c.conn.(Committer); ok {
if committer, ok := c.conn.(Committer); ok { // TODO: probably committing too early
err = committer.Commit()
if err != nil {
return false, err
@@ -353,9 +353,10 @@ func (hs *serverHandshakeState) checkForResumption() bool {
return false
}

var ok bool
var sessionTicket = append([]uint8{}, hs.clientHello.sessionTicket...)
if hs.sessionState, ok = c.decryptTicket(sessionTicket); !ok {
sessionTicket := append([]uint8{}, hs.clientHello.sessionTicket...)
serializedState, usedOldKey := c.decryptTicket(sessionTicket)
hs.sessionState = &sessionState{usedOldKey: usedOldKey}
if ok := hs.sessionState.unmarshal(serializedState); !ok {
return false
}

@@ -730,7 +731,7 @@ func (hs *serverHandshakeState) sendSessionTicket() error {
masterSecret: hs.masterSecret,
certificates: hs.certsFromClient,
}
m.ticket, err = c.encryptTicket(&state)
m.ticket, err = c.encryptTicket(state.marshal())
if err != nil {
return err
}


+ 3
- 6
ticket.go Ver arquivo

@@ -193,8 +193,7 @@ func (s *sessionState13) unmarshal(data []byte) bool {
return int(l) == len(s.resumptionSecret)
}

func (c *Conn) encryptTicket(state *sessionState) ([]byte, error) {
serialized := state.marshal()
func (c *Conn) encryptTicket(serialized []byte) ([]byte, error) {
encrypted := make([]byte, ticketKeyNameLen+aes.BlockSize+len(serialized)+sha256.Size)
keyName := encrypted[:ticketKeyNameLen]
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
@@ -218,7 +217,7 @@ func (c *Conn) encryptTicket(state *sessionState) ([]byte, error) {
return encrypted, nil
}

func (c *Conn) decryptTicket(encrypted []byte) (*sessionState, bool) {
func (c *Conn) decryptTicket(encrypted []byte) (serialized []byte, usedOldKey bool) {
if c.config.SessionTicketsDisabled ||
len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
return nil, false
@@ -258,7 +257,5 @@ func (c *Conn) decryptTicket(encrypted []byte) (*sessionState, bool) {
plaintext := ciphertext
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)

state := &sessionState{usedOldKey: keyIndex > 0}
ok := state.unmarshal(plaintext)
return state, ok
return plaintext, keyIndex > 0
}

Carregando…
Cancelar
Salvar