mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-01 19:17:36 +03:00
* crypto/tls: Add hybrid post-quantum key agreement (#13) * import: client-side KEM from cloudflare/go * import: server-side KEM from cloudflare/go * fix: modify test to get rid of CFEvents. Note: uTLS does not promise any server-side functionality, and this change is made to be able to conduct unit tests which requires both side to be able to handle KEM Curves. Co-authored-by: Christopher Wood <caw@heapingbits.net> Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> ---- Based on: * crypto/tls: Add hybrid post-quantum key agreement Adds X25519Kyber512Draft00, X25519Kyber768Draft00, and P256Kyber768Draft00 hybrid post-quantum key agreements with temporary group identifiers. The hybrid post-quantum key exchanges uses plain X{25519,448} instead of HPKE, which we assume will be more likely to be adopted. The order is chosen to match CECPQ2. Not enabled by default. Adds CFEvents to detect `HelloRetryRequest`s and to signal which key agreement was used. Co-authored-by: Christopher Wood <caw@heapingbits.net> [bas, 1.20.1: also adds P256Kyber768Draft00] [pwu, 1.20.4: updated circl to v1.3.3, moved code to cfevent.go] * crypto: add support for CIRCL signature schemes * only partially port the commit from cloudflare/go. We would stick to the official x509 at the cost of incompatibility. Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> Co-Authored-By: Christopher Patton <3453007+cjpatton@users.noreply.github.com> Co-Authored-By: Peter Wu <peter@lekensteyn.nl> * crypto/tls: add new X25519Kyber768Draft00 code point Ported from cloudflare/go to support the upcoming new post-quantum keyshare. ---- * Point tls.X25519Kyber768Draft00 to the new 0x6399 identifier while the old 0xfe31 identifier is available as tls.X25519Kyber768Draft00Old. * Make sure that the kem.PrivateKey can always be mapped to the CurveID that was linked to it. This is needed since we now have two ID aliasing to the same scheme, and clients need to be able to detect whether the key share presented by the server actually matches the key share that the client originally sent. * Update tests, add the new identifier and remove unnecessary code. Link: https://mailarchive.ietf.org/arch/msg/tls/HAWpNpgptl--UZNSYuvsjB-Pc2k/ Link: https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/02/ Co-Authored-By: Peter Wu <peter@lekensteyn.nl> Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> --------- Co-authored-by: Bas Westerbaan <bas@westerbaan.name> Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com> Co-authored-by: Peter Wu <peter@lekensteyn.nl>
292 lines
9 KiB
Go
292 lines
9 KiB
Go
// Copyright 2009 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 (
|
|
"crypto"
|
|
"crypto/hmac"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
)
|
|
|
|
// Split a premaster secret in two as specified in RFC 4346, Section 5.
|
|
func splitPreMasterSecret(secret []byte) (s1, s2 []byte) {
|
|
s1 = secret[0 : (len(secret)+1)/2]
|
|
s2 = secret[len(secret)/2:]
|
|
return
|
|
}
|
|
|
|
// pHash implements the P_hash function, as defined in RFC 4346, Section 5.
|
|
func pHash(result, secret, seed []byte, hash func() hash.Hash) {
|
|
h := hmac.New(hash, secret)
|
|
h.Write(seed)
|
|
a := h.Sum(nil)
|
|
|
|
j := 0
|
|
for j < len(result) {
|
|
h.Reset()
|
|
h.Write(a)
|
|
h.Write(seed)
|
|
b := h.Sum(nil)
|
|
copy(result[j:], b)
|
|
j += len(b)
|
|
|
|
h.Reset()
|
|
h.Write(a)
|
|
a = h.Sum(nil)
|
|
}
|
|
}
|
|
|
|
// prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, Section 5.
|
|
func prf10(result, secret, label, seed []byte) {
|
|
hashSHA1 := sha1.New
|
|
hashMD5 := md5.New
|
|
|
|
labelAndSeed := make([]byte, len(label)+len(seed))
|
|
copy(labelAndSeed, label)
|
|
copy(labelAndSeed[len(label):], seed)
|
|
|
|
s1, s2 := splitPreMasterSecret(secret)
|
|
pHash(result, s1, labelAndSeed, hashMD5)
|
|
result2 := make([]byte, len(result))
|
|
pHash(result2, s2, labelAndSeed, hashSHA1)
|
|
|
|
for i, b := range result2 {
|
|
result[i] ^= b
|
|
}
|
|
}
|
|
|
|
// prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, Section 5.
|
|
func prf12(hashFunc func() hash.Hash) func(result, secret, label, seed []byte) {
|
|
return func(result, secret, label, seed []byte) {
|
|
labelAndSeed := make([]byte, len(label)+len(seed))
|
|
copy(labelAndSeed, label)
|
|
copy(labelAndSeed[len(label):], seed)
|
|
|
|
pHash(result, secret, labelAndSeed, hashFunc)
|
|
}
|
|
}
|
|
|
|
const (
|
|
masterSecretLength = 48 // Length of a master secret in TLS 1.1.
|
|
finishedVerifyLength = 12 // Length of verify_data in a Finished message.
|
|
)
|
|
|
|
var masterSecretLabel = []byte("master secret")
|
|
var extendedMasterSecretLabel = []byte("extended master secret")
|
|
var keyExpansionLabel = []byte("key expansion")
|
|
var clientFinishedLabel = []byte("client finished")
|
|
var serverFinishedLabel = []byte("server finished")
|
|
|
|
func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) {
|
|
switch version {
|
|
case VersionTLS10, VersionTLS11:
|
|
return prf10, crypto.Hash(0)
|
|
case VersionTLS12:
|
|
if suite.flags&suiteSHA384 != 0 {
|
|
return prf12(sha512.New384), crypto.SHA384
|
|
}
|
|
return prf12(sha256.New), crypto.SHA256
|
|
default:
|
|
panic("unknown version")
|
|
}
|
|
}
|
|
|
|
func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) {
|
|
prf, _ := prfAndHashForVersion(version, suite)
|
|
return prf
|
|
}
|
|
|
|
// masterFromPreMasterSecret generates the master secret from the pre-master
|
|
// secret. See RFC 5246, Section 8.1.
|
|
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
|
|
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
|
|
seed = append(seed, clientRandom...)
|
|
seed = append(seed, serverRandom...)
|
|
|
|
masterSecret := make([]byte, masterSecretLength)
|
|
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed)
|
|
return masterSecret
|
|
}
|
|
|
|
// extMasterFromPreMasterSecret generates the extended master secret from the
|
|
// pre-master secret. See RFC 7627.
|
|
func extMasterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, transcript []byte) []byte {
|
|
masterSecret := make([]byte, masterSecretLength)
|
|
prfForVersion(version, suite)(masterSecret, preMasterSecret, extendedMasterSecretLabel, transcript)
|
|
return masterSecret
|
|
}
|
|
|
|
// keysFromMasterSecret generates the connection keys from the master
|
|
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
|
|
// RFC 2246, Section 6.3.
|
|
func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
|
|
seed := make([]byte, 0, len(serverRandom)+len(clientRandom))
|
|
seed = append(seed, serverRandom...)
|
|
seed = append(seed, clientRandom...)
|
|
|
|
n := 2*macLen + 2*keyLen + 2*ivLen
|
|
keyMaterial := make([]byte, n)
|
|
prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed)
|
|
clientMAC = keyMaterial[:macLen]
|
|
keyMaterial = keyMaterial[macLen:]
|
|
serverMAC = keyMaterial[:macLen]
|
|
keyMaterial = keyMaterial[macLen:]
|
|
clientKey = keyMaterial[:keyLen]
|
|
keyMaterial = keyMaterial[keyLen:]
|
|
serverKey = keyMaterial[:keyLen]
|
|
keyMaterial = keyMaterial[keyLen:]
|
|
clientIV = keyMaterial[:ivLen]
|
|
keyMaterial = keyMaterial[ivLen:]
|
|
serverIV = keyMaterial[:ivLen]
|
|
return
|
|
}
|
|
|
|
func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
|
|
var buffer []byte
|
|
if version >= VersionTLS12 {
|
|
buffer = []byte{}
|
|
}
|
|
|
|
prf, hash := prfAndHashForVersion(version, cipherSuite)
|
|
if hash != 0 {
|
|
return finishedHash{hash.New(), hash.New(), nil, nil, buffer, version, prf}
|
|
}
|
|
|
|
return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), buffer, version, prf}
|
|
}
|
|
|
|
// A finishedHash calculates the hash of a set of handshake messages suitable
|
|
// for including in a Finished message.
|
|
type finishedHash struct {
|
|
client hash.Hash
|
|
server hash.Hash
|
|
|
|
// Prior to TLS 1.2, an additional MD5 hash is required.
|
|
clientMD5 hash.Hash
|
|
serverMD5 hash.Hash
|
|
|
|
// In TLS 1.2, a full buffer is sadly required.
|
|
buffer []byte
|
|
|
|
version uint16
|
|
prf func(result, secret, label, seed []byte)
|
|
}
|
|
|
|
func (h *finishedHash) Write(msg []byte) (n int, err error) {
|
|
h.client.Write(msg)
|
|
h.server.Write(msg)
|
|
|
|
if h.version < VersionTLS12 {
|
|
h.clientMD5.Write(msg)
|
|
h.serverMD5.Write(msg)
|
|
}
|
|
|
|
if h.buffer != nil {
|
|
h.buffer = append(h.buffer, msg...)
|
|
}
|
|
|
|
return len(msg), nil
|
|
}
|
|
|
|
func (h finishedHash) Sum() []byte {
|
|
if h.version >= VersionTLS12 {
|
|
return h.client.Sum(nil)
|
|
}
|
|
|
|
out := make([]byte, 0, md5.Size+sha1.Size)
|
|
out = h.clientMD5.Sum(out)
|
|
return h.client.Sum(out)
|
|
}
|
|
|
|
// clientSum returns the contents of the verify_data member of a client's
|
|
// Finished message.
|
|
func (h finishedHash) clientSum(masterSecret []byte) []byte {
|
|
out := make([]byte, finishedVerifyLength)
|
|
h.prf(out, masterSecret, clientFinishedLabel, h.Sum())
|
|
return out
|
|
}
|
|
|
|
// serverSum returns the contents of the verify_data member of a server's
|
|
// Finished message.
|
|
func (h finishedHash) serverSum(masterSecret []byte) []byte {
|
|
out := make([]byte, finishedVerifyLength)
|
|
h.prf(out, masterSecret, serverFinishedLabel, h.Sum())
|
|
return out
|
|
}
|
|
|
|
// hashForClientCertificate returns the handshake messages so far, pre-hashed if
|
|
// necessary, suitable for signing by a TLS client certificate.
|
|
func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte {
|
|
if (h.version >= VersionTLS12 || sigType == signatureEd25519 || circlSchemeBySigType(sigType) != nil) && h.buffer == nil { // [UTLS] ported from cloudflare/go
|
|
panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer")
|
|
}
|
|
|
|
if sigType == signatureEd25519 || circlSchemeBySigType(sigType) != nil { // [UTLS] ported from cloudflare/go
|
|
return h.buffer
|
|
}
|
|
|
|
if h.version >= VersionTLS12 {
|
|
hash := hashAlg.New()
|
|
hash.Write(h.buffer)
|
|
return hash.Sum(nil)
|
|
}
|
|
|
|
if sigType == signatureECDSA {
|
|
return h.server.Sum(nil)
|
|
}
|
|
|
|
return h.Sum()
|
|
}
|
|
|
|
// discardHandshakeBuffer is called when there is no more need to
|
|
// buffer the entirety of the handshake messages.
|
|
func (h *finishedHash) discardHandshakeBuffer() {
|
|
h.buffer = nil
|
|
}
|
|
|
|
// noExportedKeyingMaterial is used as a value of
|
|
// ConnectionState.ekm when renegotiation is enabled and thus
|
|
// we wish to fail all key-material export requests.
|
|
func noExportedKeyingMaterial(label string, context []byte, length int) ([]byte, error) {
|
|
return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when renegotiation is enabled")
|
|
}
|
|
|
|
// ekmFromMasterSecret generates exported keying material as defined in RFC 5705.
|
|
func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte) func(string, []byte, int) ([]byte, error) {
|
|
return func(label string, context []byte, length int) ([]byte, error) {
|
|
switch label {
|
|
case "client finished", "server finished", "master secret", "key expansion":
|
|
// These values are reserved and may not be used.
|
|
return nil, fmt.Errorf("crypto/tls: reserved ExportKeyingMaterial label: %s", label)
|
|
}
|
|
|
|
seedLen := len(serverRandom) + len(clientRandom)
|
|
if context != nil {
|
|
seedLen += 2 + len(context)
|
|
}
|
|
seed := make([]byte, 0, seedLen)
|
|
|
|
seed = append(seed, clientRandom...)
|
|
seed = append(seed, serverRandom...)
|
|
|
|
if context != nil {
|
|
if len(context) >= 1<<16 {
|
|
return nil, fmt.Errorf("crypto/tls: ExportKeyingMaterial context too long")
|
|
}
|
|
seed = append(seed, byte(len(context)>>8), byte(len(context)))
|
|
seed = append(seed, context...)
|
|
}
|
|
|
|
keyMaterial := make([]byte, length)
|
|
prfForVersion(version, suite)(keyMaterial, masterSecret, []byte(label), seed)
|
|
return keyMaterial, nil
|
|
}
|
|
}
|