diff --git a/aes/aes.go b/aes/aes.go new file mode 100644 index 0000000..67e3484 --- /dev/null +++ b/aes/aes.go @@ -0,0 +1,131 @@ +// 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 aes + +import ( + "strconv" + + "github.com/xtls/reality/alias" +) + +// BlockSize is the AES block size in bytes. +const BlockSize = 16 + +// A Block is an instance of AES using a particular key. +// It is safe for concurrent use. +type Block struct { + block +} + +// blockExpanded is the block type used for all architectures except s390x, +// which feeds the raw key directly to its instructions. +type blockExpanded struct { + rounds int + // Round keys, where only the first (rounds + 1) × (128 ÷ 32) words are used. + enc [60]uint32 + dec [60]uint32 +} + +const ( + // AES-128 has 128-bit keys, 10 rounds, and uses 11 128-bit round keys + // (11×128÷32 = 44 32-bit words). + + // AES-192 has 192-bit keys, 12 rounds, and uses 13 128-bit round keys + // (13×128÷32 = 52 32-bit words). + + // AES-256 has 256-bit keys, 14 rounds, and uses 15 128-bit round keys + // (15×128÷32 = 60 32-bit words). + + aes128KeySize = 16 + aes192KeySize = 24 + aes256KeySize = 32 + + aes128Rounds = 10 + aes192Rounds = 12 + aes256Rounds = 14 +) + +// roundKeysSize returns the number of uint32 of c.end or c.dec that are used. +func (b *blockExpanded) roundKeysSize() int { + return (b.rounds + 1) * (128 / 32) +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/aes: invalid key size " + strconv.Itoa(int(k)) +} + +// New creates and returns a new [cipher.Block] implementation. +// The key argument should be the AES key, either 16, 24, or 32 bytes to select +// AES-128, AES-192, or AES-256. +func New(key []byte) (*Block, error) { + // This call is outline to let the allocation happen on the parent stack. + return newOutlined(&Block{}, key) +} + +// newOutlined is marked go:noinline to avoid it inlining into New, and making New +// too complex to inline itself. +// +//go:noinline +func newOutlined(b *Block, key []byte) (*Block, error) { + switch len(key) { + case aes128KeySize, aes192KeySize, aes256KeySize: + default: + return nil, KeySizeError(len(key)) + } + return newBlock(b, key), nil +} + +func newBlockExpanded(c *blockExpanded, key []byte) { + switch len(key) { + case aes128KeySize: + c.rounds = aes128Rounds + case aes192KeySize: + c.rounds = aes192Rounds + case aes256KeySize: + c.rounds = aes256Rounds + } + expandKeyGeneric(c, key) +} + +func (c *Block) BlockSize() int { return BlockSize } + +func (c *Block) Encrypt(dst, src []byte) { + // AES-ECB is not approved in FIPS 140-3 mode. + // fips140.RecordNonApproved() + if len(src) < BlockSize { + panic("crypto/aes: input not full block") + } + if len(dst) < BlockSize { + panic("crypto/aes: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("crypto/aes: invalid buffer overlap") + } + encryptBlock(c, dst, src) +} + +func (c *Block) Decrypt(dst, src []byte) { + // AES-ECB is not approved in FIPS 140-3 mode. + // fips140.RecordNonApproved() + if len(src) < BlockSize { + panic("crypto/aes: input not full block") + } + if len(dst) < BlockSize { + panic("crypto/aes: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("crypto/aes: invalid buffer overlap") + } + decryptBlock(c, dst, src) +} + +// EncryptBlockInternal applies the AES encryption function to one block. +// +// It is an internal function meant only for the gcm package. +func EncryptBlockInternal(c *Block, dst, src []byte) { + encryptBlock(c, dst, src) +} \ No newline at end of file diff --git a/aes/aes_generic.go b/aes/aes_generic.go new file mode 100644 index 0000000..a269ae6 --- /dev/null +++ b/aes/aes_generic.go @@ -0,0 +1,181 @@ +// 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. + +// This Go implementation is derived in part from the reference +// ANSI C implementation, which carries the following notice: +// +// rijndael-alg-fst.c +// +// @version 3.0 (December 2000) +// +// Optimised ANSI C code for the Rijndael cipher (now AES) +// +// @author Vincent Rijmen +// @author Antoon Bosselaers +// @author Paulo Barreto +// +// This code is hereby placed in the public domain. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS +// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// See FIPS 197 for specification, and see Daemen and Rijmen's Rijndael submission +// for implementation details. +// https://csrc.nist.gov/csrc/media/publications/fips/197/final/documents/fips-197.pdf +// https://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf + +package aes + +import "encoding/binary" + +// Encrypt one block from src into dst, using the expanded key xk. +func encryptBlockGeneric(c *blockExpanded, dst, src []byte) { + checkGenericIsExpected() + xk := c.enc[:] + + _ = src[15] // early bounds check + s0 := binary.BigEndian.Uint32(src[0:4]) + s1 := binary.BigEndian.Uint32(src[4:8]) + s2 := binary.BigEndian.Uint32(src[8:12]) + s3 := binary.BigEndian.Uint32(src[12:16]) + + // First round just XORs input with key. + s0 ^= xk[0] + s1 ^= xk[1] + s2 ^= xk[2] + s3 ^= xk[3] + + // Middle rounds shuffle using tables. + k := 4 + var t0, t1, t2, t3 uint32 + for r := 0; r < c.rounds-1; r++ { + t0 = xk[k+0] ^ te0[uint8(s0>>24)] ^ te1[uint8(s1>>16)] ^ te2[uint8(s2>>8)] ^ te3[uint8(s3)] + t1 = xk[k+1] ^ te0[uint8(s1>>24)] ^ te1[uint8(s2>>16)] ^ te2[uint8(s3>>8)] ^ te3[uint8(s0)] + t2 = xk[k+2] ^ te0[uint8(s2>>24)] ^ te1[uint8(s3>>16)] ^ te2[uint8(s0>>8)] ^ te3[uint8(s1)] + t3 = xk[k+3] ^ te0[uint8(s3>>24)] ^ te1[uint8(s0>>16)] ^ te2[uint8(s1>>8)] ^ te3[uint8(s2)] + k += 4 + s0, s1, s2, s3 = t0, t1, t2, t3 + } + + // Last round uses s-box directly and XORs to produce output. + s0 = uint32(sbox0[t0>>24])<<24 | uint32(sbox0[t1>>16&0xff])<<16 | uint32(sbox0[t2>>8&0xff])<<8 | uint32(sbox0[t3&0xff]) + s1 = uint32(sbox0[t1>>24])<<24 | uint32(sbox0[t2>>16&0xff])<<16 | uint32(sbox0[t3>>8&0xff])<<8 | uint32(sbox0[t0&0xff]) + s2 = uint32(sbox0[t2>>24])<<24 | uint32(sbox0[t3>>16&0xff])<<16 | uint32(sbox0[t0>>8&0xff])<<8 | uint32(sbox0[t1&0xff]) + s3 = uint32(sbox0[t3>>24])<<24 | uint32(sbox0[t0>>16&0xff])<<16 | uint32(sbox0[t1>>8&0xff])<<8 | uint32(sbox0[t2&0xff]) + + s0 ^= xk[k+0] + s1 ^= xk[k+1] + s2 ^= xk[k+2] + s3 ^= xk[k+3] + + _ = dst[15] // early bounds check + binary.BigEndian.PutUint32(dst[0:4], s0) + binary.BigEndian.PutUint32(dst[4:8], s1) + binary.BigEndian.PutUint32(dst[8:12], s2) + binary.BigEndian.PutUint32(dst[12:16], s3) +} + +// Decrypt one block from src into dst, using the expanded key xk. +func decryptBlockGeneric(c *blockExpanded, dst, src []byte) { + checkGenericIsExpected() + xk := c.dec[:] + + _ = src[15] // early bounds check + s0 := binary.BigEndian.Uint32(src[0:4]) + s1 := binary.BigEndian.Uint32(src[4:8]) + s2 := binary.BigEndian.Uint32(src[8:12]) + s3 := binary.BigEndian.Uint32(src[12:16]) + + // First round just XORs input with key. + s0 ^= xk[0] + s1 ^= xk[1] + s2 ^= xk[2] + s3 ^= xk[3] + + // Middle rounds shuffle using tables. + k := 4 + var t0, t1, t2, t3 uint32 + for r := 0; r < c.rounds-1; r++ { + t0 = xk[k+0] ^ td0[uint8(s0>>24)] ^ td1[uint8(s3>>16)] ^ td2[uint8(s2>>8)] ^ td3[uint8(s1)] + t1 = xk[k+1] ^ td0[uint8(s1>>24)] ^ td1[uint8(s0>>16)] ^ td2[uint8(s3>>8)] ^ td3[uint8(s2)] + t2 = xk[k+2] ^ td0[uint8(s2>>24)] ^ td1[uint8(s1>>16)] ^ td2[uint8(s0>>8)] ^ td3[uint8(s3)] + t3 = xk[k+3] ^ td0[uint8(s3>>24)] ^ td1[uint8(s2>>16)] ^ td2[uint8(s1>>8)] ^ td3[uint8(s0)] + k += 4 + s0, s1, s2, s3 = t0, t1, t2, t3 + } + + // Last round uses s-box directly and XORs to produce output. + s0 = uint32(sbox1[t0>>24])<<24 | uint32(sbox1[t3>>16&0xff])<<16 | uint32(sbox1[t2>>8&0xff])<<8 | uint32(sbox1[t1&0xff]) + s1 = uint32(sbox1[t1>>24])<<24 | uint32(sbox1[t0>>16&0xff])<<16 | uint32(sbox1[t3>>8&0xff])<<8 | uint32(sbox1[t2&0xff]) + s2 = uint32(sbox1[t2>>24])<<24 | uint32(sbox1[t1>>16&0xff])<<16 | uint32(sbox1[t0>>8&0xff])<<8 | uint32(sbox1[t3&0xff]) + s3 = uint32(sbox1[t3>>24])<<24 | uint32(sbox1[t2>>16&0xff])<<16 | uint32(sbox1[t1>>8&0xff])<<8 | uint32(sbox1[t0&0xff]) + + s0 ^= xk[k+0] + s1 ^= xk[k+1] + s2 ^= xk[k+2] + s3 ^= xk[k+3] + + _ = dst[15] // early bounds check + binary.BigEndian.PutUint32(dst[0:4], s0) + binary.BigEndian.PutUint32(dst[4:8], s1) + binary.BigEndian.PutUint32(dst[8:12], s2) + binary.BigEndian.PutUint32(dst[12:16], s3) +} + +// Apply sbox0 to each byte in w. +func subw(w uint32) uint32 { + return uint32(sbox0[w>>24])<<24 | + uint32(sbox0[w>>16&0xff])<<16 | + uint32(sbox0[w>>8&0xff])<<8 | + uint32(sbox0[w&0xff]) +} + +// Rotate +func rotw(w uint32) uint32 { return w<<8 | w>>24 } + +// Key expansion algorithm. See FIPS-197, Figure 11. +// Their rcon[i] is our powx[i-1] << 24. +func expandKeyGeneric(c *blockExpanded, key []byte) { + checkGenericIsExpected() + + // Encryption key setup. + var i int + nk := len(key) / 4 + for i = 0; i < nk; i++ { + c.enc[i] = binary.BigEndian.Uint32(key[4*i:]) + } + for ; i < c.roundKeysSize(); i++ { + t := c.enc[i-1] + if i%nk == 0 { + t = subw(rotw(t)) ^ (uint32(powx[i/nk-1]) << 24) + } else if nk > 6 && i%nk == 4 { + t = subw(t) + } + c.enc[i] = c.enc[i-nk] ^ t + } + + // Derive decryption key from encryption key. + // Reverse the 4-word round key sets from enc to produce dec. + // All sets but the first and last get the MixColumn transform applied. + n := c.roundKeysSize() + for i := 0; i < n; i += 4 { + ei := n - i - 4 + for j := 0; j < 4; j++ { + x := c.enc[ei+j] + if i > 0 && i+4 < n { + x = td0[sbox0[x>>24]] ^ td1[sbox0[x>>16&0xff]] ^ td2[sbox0[x>>8&0xff]] ^ td3[sbox0[x&0xff]] + } + c.dec[i+j] = x + } + } +} \ No newline at end of file diff --git a/aes/aes_noasm.go b/aes/aes_noasm.go new file mode 100644 index 0000000..b3990d1 --- /dev/null +++ b/aes/aes_noasm.go @@ -0,0 +1,24 @@ +// Copyright 2024 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 aes + +type block struct { + blockExpanded +} + +func newBlock(c *Block, key []byte) *Block { + newBlockExpanded(&c.blockExpanded, key) + return c +} + +func encryptBlock(c *Block, dst, src []byte) { + encryptBlockGeneric(&c.blockExpanded, dst, src) +} + +func decryptBlock(c *Block, dst, src []byte) { + decryptBlockGeneric(&c.blockExpanded, dst, src) +} + +func checkGenericIsExpected() {} \ No newline at end of file diff --git a/aes/const.go b/aes/const.go new file mode 100644 index 0000000..dee9d40 --- /dev/null +++ b/aes/const.go @@ -0,0 +1,356 @@ +// 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 aes + +// This file contains AES constants - 8720 bytes of initialized data. + +// https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + +// AES is based on the mathematical behavior of binary polynomials +// (polynomials over GF(2)) modulo the irreducible polynomial x⁸ + x⁴ + x³ + x + 1. +// Addition of these binary polynomials corresponds to binary xor. +// Reducing mod poly corresponds to binary xor with poly every +// time a 0x100 bit appears. +const poly = 1<<8 | 1<<4 | 1<<3 | 1<<1 | 1<<0 // x⁸ + x⁴ + x³ + x + 1 + +// Powers of x mod poly in GF(2). +var powx = [16]byte{ + 0x01, + 0x02, + 0x04, + 0x08, + 0x10, + 0x20, + 0x40, + 0x80, + 0x1b, + 0x36, + 0x6c, + 0xd8, + 0xab, + 0x4d, + 0x9a, + 0x2f, +} + +// FIPS-197 Figure 7. S-box substitution values in hexadecimal format. +var sbox0 = [256]byte{ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, +} + +// FIPS-197 Figure 14. Inverse S-box substitution values in hexadecimal format. +var sbox1 = [256]byte{ + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, +} + +// Lookup tables for encryption. +// These can be recomputed by adapting the tests in aes_test.go. + +var te0 = [256]uint32{ + 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, + 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, +} +var te1 = [256]uint32{ + 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, + 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, + 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, + 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, + 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, + 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, + 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, + 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, + 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, + 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, + 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, + 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, + 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, + 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, + 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, + 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, + 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, + 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, + 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, + 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, + 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, + 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, +} +var te2 = [256]uint32{ + 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, + 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, + 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, + 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, + 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, + 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, + 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, + 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, + 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, + 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, + 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, + 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, + 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, + 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, + 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, + 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, + 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, + 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, + 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, + 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, + 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, + 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, +} +var te3 = [256]uint32{ + 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, + 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, + 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, + 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, + 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, + 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, + 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, + 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, + 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, + 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, + 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, + 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, + 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, + 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, + 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, + 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, + 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, + 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, + 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, + 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, + 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, + 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, +} + +// Lookup tables for decryption. +// These can be recomputed by adapting the tests in aes_test.go. + +var td0 = [256]uint32{ + 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, + 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, + 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, + 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, + 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, + 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, + 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, + 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, + 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, + 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, + 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, + 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, + 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, + 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, + 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, + 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, + 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, + 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, + 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, + 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, +} +var td1 = [256]uint32{ + 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, + 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, + 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, + 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, + 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, + 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, + 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, + 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, + 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, + 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, + 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, + 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, + 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, + 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, + 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, + 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, + 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, + 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, + 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, + 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, + 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, + 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, +} +var td2 = [256]uint32{ + 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, + 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, + 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, + 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, + 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, + 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, + 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, + 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, + 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, + 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, + 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, + 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, + 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, + 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, + 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, + 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, + 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, + 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, + 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, + 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, +} +var td3 = [256]uint32{ + 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, + 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, + 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, + 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, + 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, + 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, + 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, + 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, + 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, + 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, + 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, + 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, + 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, + 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, + 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, + 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, + 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, + 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, + 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, + 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, + 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, + 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, +} \ No newline at end of file diff --git a/alias/alias.go b/alias/alias.go new file mode 100644 index 0000000..7b35dd9 --- /dev/null +++ b/alias/alias.go @@ -0,0 +1,30 @@ +// Copyright 2018 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 alias implements memory aliasing tests. +// This code also exists as golang.org/x/crypto/internal/alias. +package alias + +import "unsafe" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + return len(x) > 0 && len(y) > 0 && + uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) && + uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1])) +} + +// InexactOverlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// InexactOverlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +func InexactOverlap(x, y []byte) bool { + if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { + return false + } + return AnyOverlap(x, y) +} \ No newline at end of file diff --git a/auth.go b/auth.go index d595925..e8630f0 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,7 @@ import ( "fmt" "hash" "io" + "slices" ) // verifyHandshakeSignature verifies a signature against pre-hashed @@ -167,9 +168,6 @@ var rsaSignatureSchemes = []struct { // signatureSchemesForCertificate returns the list of supported SignatureSchemes // for a given certificate, based on the public key and the protocol version, // and optionally filtered by its explicit SupportedSignatureAlgorithms. -// -// This function must be kept in sync with supportedSignatureAlgorithms. -// FIPS filtering is applied in the caller, selectSignatureScheme. func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme { priv, ok := cert.PrivateKey.(crypto.Signer) if !ok { @@ -215,14 +213,18 @@ func signatureSchemesForCertificate(version uint16, cert *Certificate) []Signatu } if cert.SupportedSignatureAlgorithms != nil { - var filteredSigAlgs []SignatureScheme - for _, sigAlg := range sigAlgs { - if isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) { - filteredSigAlgs = append(filteredSigAlgs, sigAlg) - } - } - return filteredSigAlgs + sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool { + return !isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) + }) } + + // Filter out any unsupported signature algorithms, for example due to + // FIPS 140-3 policy, or any downstream changes to defaults.go. + supportedAlgs := supportedSignatureAlgorithms() + sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool { + return !isSupportedSignatureAlgorithm(sigAlg, supportedAlgs) + }) + return sigAlgs } @@ -242,9 +244,6 @@ func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureSche // Pick signature scheme in the peer's preference order, as our // preference order is not configurable. for _, preferredAlg := range peerAlgs { - if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { - continue - } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { return preferredAlg, nil } diff --git a/cache.go b/cache.go index e91711d..d98ff85 100644 --- a/cache.go +++ b/cache.go @@ -43,15 +43,15 @@ var globalCertCache = new(certCache) // activeCert is a handle to a certificate held in the cache. Once there are // no alive activeCerts for a given certificate, the certificate is removed -// from the cache by a finalizer. +// from the cache by a cleanup. type activeCert struct { cert *x509.Certificate } // active increments the number of references to the entry, wraps the -// certificate in the entry in an activeCert, and sets the finalizer. +// certificate in the entry in an activeCert, and sets the cleanup. // -// Note that there is a race between active and the finalizer set on the +// Note that there is a race between active and the cleanup set on the // returned activeCert, triggered if active is called after the ref count is // decremented such that refs may be > 0 when evict is called. We consider this // safe, since the caller holding an activeCert for an entry that is no longer @@ -60,11 +60,11 @@ type activeCert struct { func (cc *certCache) active(e *cacheEntry) *activeCert { e.refs.Add(1) a := &activeCert{e.cert} - runtime.SetFinalizer(a, func(_ *activeCert) { - if e.refs.Add(-1) == 0 { - cc.evict(e) + runtime.AddCleanup(a, func(ce *cacheEntry) { + if ce.refs.Add(-1) == 0 { + cc.evict(ce) } - }) + }, e) return a } diff --git a/cipher_suites.go b/cipher_suites.go index 3ce93a8..57ce64c 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -20,6 +20,9 @@ import ( "golang.org/x/crypto/chacha20poly1305" "golang.org/x/sys/cpu" + + "github.com/xtls/reality/gcm" + fipsaes "github.com/xtls/reality/aes" ) // CipherSuite is a TLS cipher suite. Note that most functions in this package @@ -75,8 +78,8 @@ func CipherSuites() []*CipherSuite { // Most applications should not use the cipher suites in this list, and should // only use those returned by [CipherSuites]. func InsecureCipherSuites() []*CipherSuite { - // This list includes RC4, CBC_SHA256, and 3DES cipher suites. See - // cipherSuitesPreferenceOrder for details. + // This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher + // suites. See cipherSuitesPreferenceOrder for details. return []*CipherSuite{ {TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true}, {TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true}, @@ -232,7 +235,7 @@ var cipherSuitesTLS13 = []*cipherSuiteTLS13{ // TODO: replace with a map. // - Anything else comes before CBC_SHA256 // // SHA-256 variants of the CBC ciphersuites don't implement any Lucky13 -// countermeasures. See http://www.isg.rhul.ac.uk/tls/Lucky13.html and +// countermeasures. See https://www.isg.rhul.ac.uk/tls/Lucky13.html and // https://www.imperialviolet.org/2013/02/04/luckythirteen.html. // // - Anything else comes before 3DES @@ -364,15 +367,13 @@ var tdesCiphers = map[uint16]bool{ } var ( - hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + // Keep in sync with crypto/internal/fips/aes/gcm.supportsAESGCM. + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3 hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && - (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH + hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" - hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 || - runtime.GOARCH == "arm64" && hasGCMAsmARM64 || - runtime.GOARCH == "s390x" && hasGCMAsmS390X + hasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64 ) var aesgcmCiphers = map[uint16]bool{ @@ -386,9 +387,13 @@ var aesgcmCiphers = map[uint16]bool{ TLS_AES_256_GCM_SHA384: true, } -// aesgcmPreferred returns whether the first known cipher in the preference list -// is an AES-GCM cipher, implying the peer has hardware support for it. -func aesgcmPreferred(ciphers []uint16) bool { +// isAESGCMPreferred returns whether we have hardware support for AES-GCM, and the +// first known cipher in the peer's preference list is an AES-GCM cipher, +// implying the peer also has hardware support for it. +func isAESGCMPreferred(ciphers []uint16) bool { + if !hasAESGCMHardwareSupport { + return false + } for _, cID := range ciphers { if c := cipherSuiteByID(cID); c != nil { return aesgcmCiphers[cID] @@ -511,7 +516,7 @@ func aeadAESGCM(key, noncePrefix []byte) aead { if err != nil { panic(err) } - aead, err := cipher.NewGCM(aes) + aead, err := gcm.NewGCMForTLS12(aes.(*fipsaes.Block)) if err != nil { panic(err) } @@ -539,7 +544,7 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead { if err != nil { panic(err) } - aead, err := cipher.NewGCM(aes) + aead, err := gcm.NewGCMForTLS13(aes.(*fipsaes.Block)) if err != nil { panic(err) } diff --git a/common.go b/common.go index 895ee85..ed987ac 100644 --- a/common.go +++ b/common.go @@ -25,6 +25,8 @@ import ( "sync" "time" _ "unsafe" // for linkname + + "github.com/xtls/reality/fips140tls" ) const ( @@ -143,17 +145,21 @@ const ( type CurveID uint16 const ( - CurveP256 CurveID = 23 - CurveP384 CurveID = 24 - CurveP521 CurveID = 25 - X25519 CurveID = 29 - - // Experimental codepoint for X25519Kyber768Draft00, specified in - // draft-tls-westerbaan-xyber768d00-03. Not exported, as support might be - // removed in the future. - x25519Kyber768Draft00 CurveID = 0x6399 // X25519Kyber768Draft00 + CurveP256 CurveID = 23 + CurveP384 CurveID = 24 + CurveP521 CurveID = 25 + X25519 CurveID = 29 + X25519MLKEM768 CurveID = 4588 ) +func isTLS13OnlyKeyExchange(curve CurveID) bool { + return curve == X25519MLKEM768 +} + +func isPQKeyExchange(curve CurveID) bool { + return curve == X25519MLKEM768 +} + // TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. type keyShare struct { group CurveID @@ -241,6 +247,11 @@ type ConnectionState struct { // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_AES_128_GCM_SHA256). CipherSuite uint16 + // CurveID is the key exchange mechanism used for the connection. The name + // refers to elliptic curves for legacy reasons, see [CurveID]. If a legacy + // RSA key exchange is used, this value is zero. + CurveID CurveID + // NegotiatedProtocol is the application protocol negotiated with ALPN. NegotiatedProtocol string @@ -298,10 +309,6 @@ type ConnectionState struct { // testingOnlyDidHRR is true if a HelloRetryRequest was sent/received. testingOnlyDidHRR bool - - // testingOnlyCurveID is the selected CurveID, or zero if an RSA exchanges - // is performed. - testingOnlyCurveID CurveID } // ExportKeyingMaterial returns length bytes of exported key material in a new @@ -417,9 +424,12 @@ type ClientHelloInfo struct { // client is using SNI (see RFC 4366, Section 3.1). ServerName string - // SupportedCurves lists the elliptic curves supported by the client. - // SupportedCurves is set only if the Supported Elliptic Curves - // Extension is being used (see RFC 4492, Section 5.1.1). + // SupportedCurves lists the key exchange mechanisms supported by the + // client. It was renamed to "supported groups" in TLS 1.3, see RFC 8446, + // Section 4.2.7 and [CurveID]. + // + // SupportedCurves may be nil in TLS 1.2 and lower if the Supported Elliptic + // Curves Extension is not being used (see RFC 4492, Section 5.1.1). SupportedCurves []CurveID // SupportedPoints lists the point formats supported by the client. @@ -447,7 +457,7 @@ type ClientHelloInfo struct { SupportedVersions []uint16 // Extensions lists the IDs of the extensions presented by the client - // in the client hello. + // in the ClientHello. Extensions []uint16 // Conn is the underlying net.Conn for the connection. Do not read @@ -773,14 +783,15 @@ type Config struct { // which is currently TLS 1.3. MaxVersion uint16 - // CurvePreferences contains the elliptic curves that will be used in - // an ECDHE handshake, in preference order. If empty, the default will - // be used. The client will use the first preference as the type for - // its key share in TLS 1.3. This may change in the future. + // CurvePreferences contains a set of supported key exchange mechanisms. + // The name refers to elliptic curves for legacy reasons, see [CurveID]. + // The order of the list is ignored, and key exchange mechanisms are chosen + // from this list using an internal preference order. If empty, the default + // will be used. // - // From Go 1.23, the default includes the X25519Kyber768Draft00 hybrid + // From Go 1.24, the default includes the [X25519MLKEM768] hybrid // post-quantum key exchange. To disable it, set CurvePreferences explicitly - // or use the GODEBUG=tlskyber=0 environment variable. + // or use the GODEBUG=tlsmlkem=0 environment variable. CurvePreferences []CurveID // DynamicRecordSizingDisabled disables adaptive sizing of TLS records. @@ -803,8 +814,10 @@ type Config struct { // EncryptedClientHelloConfigList is a serialized ECHConfigList. If // provided, clients will attempt to connect to servers using Encrypted - // Client Hello (ECH) using one of the provided ECHConfigs. Servers - // currently ignore this field. + // Client Hello (ECH) using one of the provided ECHConfigs. + // + // Servers do not use this field. In order to configure ECH for servers, see + // the EncryptedClientHelloKeys field. // // If the list contains no valid ECH configs, the handshake will fail // and return an error. @@ -813,7 +826,7 @@ type Config struct { // be VersionTLS13. // // When EncryptedClientHelloConfigList is set, the handshake will only - // succeed if ECH is sucessfully negotiated. If the server rejects ECH, + // succeed if ECH is successfully negotiated. If the server rejects ECH, // an ECHRejectionError error will be returned, which may contain a new // ECHConfigList that the server suggests using. // @@ -822,9 +835,11 @@ type Config struct { EncryptedClientHelloConfigList []byte // EncryptedClientHelloRejectionVerify, if not nil, is called when ECH is - // rejected, in order to verify the ECH provider certificate in the outer - // Client Hello. If it returns a non-nil error, the handshake is aborted and - // that error results. + // rejected by the remote server, in order to verify the ECH provider + // certificate in the outer ClientHello. If it returns a non-nil error, the + // handshake is aborted and that error results. + // + // On the server side this field is not used. // // Unlike VerifyPeerCertificate and VerifyConnection, normal certificate // verification will not be performed before calling @@ -836,6 +851,20 @@ type Config struct { // when ECH is rejected, even if set, and InsecureSkipVerify is ignored. EncryptedClientHelloRejectionVerify func(ConnectionState) error + // EncryptedClientHelloKeys are the ECH keys to use when a client + // attempts ECH. + // + // If EncryptedClientHelloKeys is set, MinVersion, if set, must be + // VersionTLS13. + // + // If a client attempts ECH, but it is rejected by the server, the server + // will send a list of configs to retry based on the set of + // EncryptedClientHelloKeys which have the SendAsRetry field set. + // + // On the client side, this field is ignored. In order to configure ECH for + // clients, see the EncryptedClientHelloConfigList field. + EncryptedClientHelloKeys []EncryptedClientHelloKey + // mutex protects sessionTicketKeys and autoSessionTicketKeys. mutex sync.RWMutex // sessionTicketKeys contains zero or more ticket keys. If set, it means @@ -849,6 +878,25 @@ type Config struct { autoSessionTicketKeys []ticketKey } +// EncryptedClientHelloKey holds a private key that is associated +// with a specific ECH config known to a client. +type EncryptedClientHelloKey struct { + // Config should be a marshalled ECHConfig associated with PrivateKey. This + // must match the config provided to clients byte-for-byte. The config + // should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the + // HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs: + // AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003). + Config []byte + // PrivateKey should be a marshalled private key. Currently, we expect + // this to be the output of [ecdh.PrivateKey.Bytes]. + PrivateKey []byte + // SendAsRetry indicates if Config should be sent as part of the list of + // retry configs when ECH is requested by the client but rejected by the + // server. + SendAsRetry bool +} + + const ( // ticketKeyLifetime is how long a ticket key remains valid and can be used to // resume a client connection. @@ -936,6 +984,7 @@ func (c *Config) Clone() *Config { KeyLogWriter: c.KeyLogWriter, EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList, EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify, + EncryptedClientHelloKeys: c.EncryptedClientHelloKeys, sessionTicketKeys: c.sessionTicketKeys, autoSessionTicketKeys: c.autoSessionTicketKeys, } @@ -990,6 +1039,7 @@ func (c *Config) ticketKeys(configForClient *Config) []ticketKey { if configForClient != nil { configForClient.mutex.RLock() if configForClient.SessionTicketsDisabled { + configForClient.mutex.RUnlock() return nil } configForClient.initLegacySessionTicketKeyRLocked() @@ -1083,20 +1133,28 @@ func (c *Config) time() time.Time { return t() } -func (c *Config) cipherSuites() []uint16 { +func (c *Config) cipherSuites(aesGCMPreferred bool) []uint16 { + var cipherSuites []uint16 if c.CipherSuites == nil { - if needFIPS() { - return defaultCipherSuitesFIPS - } - return defaultCipherSuites() - } - if needFIPS() { - cipherSuites := slices.Clone(c.CipherSuites) - return slices.DeleteFunc(cipherSuites, func(id uint16) bool { - return !slices.Contains(defaultCipherSuitesFIPS, id) + cipherSuites = defaultCipherSuites(aesGCMPreferred) + } else { + cipherSuites = supportedCipherSuites(aesGCMPreferred) + cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool { + return !slices.Contains(c.CipherSuites, id) }) } - return c.CipherSuites + if fips140tls.Required() { + cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool { + return !slices.Contains(allowedCipherSuitesFIPS, id) + }) + } + return cipherSuites +} + +// supportedCipherSuites returns the supported TLS 1.0–1.2 cipher suites in an +// undefined order. For preference ordering, use [Config.cipherSuites]. +func (c *Config) supportedCipherSuites() []uint16 { + return c.cipherSuites(false) } var supportedVersions = []uint16{ @@ -1114,7 +1172,7 @@ const roleServer = false func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { - if needFIPS() && !slices.Contains(defaultSupportedVersionsFIPS, v) { + if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { @@ -1157,45 +1215,34 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { } func (c *Config) curvePreferences(version uint16) []CurveID { - var curvePreferences []CurveID + curvePreferences := defaultCurvePreferences() + if fips140tls.Required() { + curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { + return !slices.Contains(allowedCurvePreferencesFIPS, x) + }) + } if c != nil && len(c.CurvePreferences) != 0 { - curvePreferences = slices.Clone(c.CurvePreferences) - if needFIPS() { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return !slices.Contains(defaultCurvePreferencesFIPS, c) - }) - } - } else if needFIPS() { - curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) - } else { - curvePreferences = defaultCurvePreferences() + curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { + return !slices.Contains(c.CurvePreferences, x) + }) } if version < VersionTLS13 { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return c == x25519Kyber768Draft00 - }) + curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange) } return curvePreferences } func (c *Config) supportsCurve(version uint16, curve CurveID) bool { - for _, cc := range c.curvePreferences(version) { - if cc == curve { - return true - } - } - return false + return slices.Contains(c.curvePreferences(version), curve) } // mutualVersion returns the protocol version to use given the advertised // versions of the peer. Priority is given to the peer preference order. func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) { supportedVersions := c.supportedVersions(isClient) - for _, peerVersion := range peerVersions { - for _, v := range supportedVersions { - if v == peerVersion { - return v, true - } + for _, v := range peerVersions { + if slices.Contains(supportedVersions, v) { + return v, true } } return 0, false @@ -1316,7 +1363,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { } // Finally, there needs to be a mutual cipher suite that uses the static // RSA key exchange instead of ECDHE. - rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool { + rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool { if c.flags&suiteECDHE != 0 { return false } @@ -1347,7 +1394,11 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { } // The only signed key exchange we support is ECDHE. - if !supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints) { + ecdheSupported, err := supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints) + if err != nil { + return err + } + if !ecdheSupported { return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange")) } @@ -1393,7 +1444,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { // Make sure that there is a mutually supported cipher suite that works with // this certificate. Cipher suite selection will then apply the logic in // reverse to pick it. See also serverHandshakeState.cipherSuiteOk. - cipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool { + cipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool { if c.flags&suiteECDHE == 0 { return false } @@ -1637,19 +1688,14 @@ func unexpectedMessageError(wanted, got any) error { // supportedSignatureAlgorithms returns the supported signature algorithms. func supportedSignatureAlgorithms() []SignatureScheme { - if !needFIPS() { - return defaultSupportedSignatureAlgorithms + if fips140tls.Required() { + return allowedSupportedSignatureAlgorithmsFIPS } - return defaultSupportedSignatureAlgorithmsFIPS + return defaultSupportedSignatureAlgorithms } func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlgorithms []SignatureScheme) bool { - for _, s := range supportedSignatureAlgorithms { - if s == sigAlg { - return true - } - } - return false + return slices.Contains(supportedSignatureAlgorithms, sigAlg) } // CertificateVerificationError is returned when certificate verification fails during the handshake. @@ -1666,3 +1712,42 @@ func (e *CertificateVerificationError) Error() string { func (e *CertificateVerificationError) Unwrap() error { return e.Err } + +// fipsAllowedChains returns chains that are allowed to be used in a TLS connection +// based on the current fips140tls enforcement setting. +// +// If fips140tls is not required, the chains are returned as-is with no processing. +// Otherwise, the returned chains are filtered to only those allowed by FIPS 140-3. +// If this results in no chains it returns an error. +func fipsAllowedChains(chains [][]*x509.Certificate) ([][]*x509.Certificate, error) { + if !fips140tls.Required() { + return chains, nil + } + + permittedChains := make([][]*x509.Certificate, 0, len(chains)) + for _, chain := range chains { + if fipsAllowChain(chain) { + permittedChains = append(permittedChains, chain) + } + } + + if len(permittedChains) == 0 { + return nil, errors.New("tls: no FIPS compatible certificate chains found") + } + + return permittedChains, nil +} + +func fipsAllowChain(chain []*x509.Certificate) bool { + if len(chain) == 0 { + return false + } + + for _, cert := range chain { + if !isCertificateAllowedFIPS(cert) { + return false + } + } + + return true +} \ No newline at end of file diff --git a/common_string.go b/common_string.go index 4ac388b..b644d35 100644 --- a/common_string.go +++ b/common_string.go @@ -71,13 +71,13 @@ func _() { _ = x[CurveP384-24] _ = x[CurveP521-25] _ = x[X25519-29] - _ = x[x25519Kyber768Draft00-25497] + _ = x[X25519MLKEM768-4588] } const ( _CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_1 = "X25519" - _CurveID_name_2 = "X25519Kyber768Draft00" + _CurveID_name_2 = "X25519MLKEM768" ) var ( @@ -91,7 +91,7 @@ func (i CurveID) String() string { return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]] case i == 29: return _CurveID_name_1 - case i == 25497: + case i == 4588: return _CurveID_name_2 default: return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")" diff --git a/conn.go b/conn.go index 3db488d..14522a0 100644 --- a/conn.go +++ b/conn.go @@ -758,6 +758,15 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(io.EOF) } if c.vers == VersionTLS13 { + // TLS 1.3 removed warning-level alerts except for alertUserCanceled + // (RFC 8446, § 6.1). Since at least one major implementation + // (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert, + // many TLS stacks now ignore it outright when seen in a TLS 1.3 + // handshake (e.g. BoringSSL, NSS, Rustls). + if alert(data[1]) == alertUserCanceled { + // Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry. + return c.retryReadRecord(expectChangeCipherSpec) + } return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])}) } switch data[0] { @@ -1685,8 +1694,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { state.NegotiatedProtocol = c.clientProtocol state.DidResume = c.didResume state.testingOnlyDidHRR = c.didHRR - // c.curveID is not set on TLS 1.0–1.2 resumptions. Fix that before exposing it. - state.testingOnlyCurveID = c.curveID + state.CurveID = c.curveID state.NegotiatedProtocolIsMutual = true state.ServerName = c.serverName state.CipherSuite = c.cipherSuite diff --git a/defaults.go b/defaults.go index 19dc10e..e65af2a 100644 --- a/defaults.go +++ b/defaults.go @@ -12,14 +12,15 @@ import ( // Defaults are collected in this file to allow distributions to more easily patch // them to apply local policies. -//var tlskyber = godebug.New("tlskyber") +//var tlsmlkem = godebug.New("tlsmlkem") +// defaultCurvePreferences is the default set of supported key exchanges, as +// well as the preference order. func defaultCurvePreferences() []CurveID { if false { return []CurveID{X25519, CurveP256, CurveP384, CurveP521} } - // For now, x25519Kyber768Draft00 must always be followed by X25519. - return []CurveID{x25519Kyber768Draft00, X25519, CurveP256, CurveP384, CurveP521} + return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521} } // defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that @@ -44,9 +45,17 @@ var defaultSupportedSignatureAlgorithms = []SignatureScheme{ //var tlsrsakex = godebug.New("tlsrsakex") //var tls3des = godebug.New("tls3des") -func defaultCipherSuites() []uint16 { - suites := slices.Clone(cipherSuitesPreferenceOrder) - return slices.DeleteFunc(suites, func(c uint16) bool { +func supportedCipherSuites(aesGCMPreferred bool) []uint16 { + if aesGCMPreferred { + return slices.Clone(cipherSuitesPreferenceOrder) + } else { + return slices.Clone(cipherSuitesPreferenceOrderNoAES) + } +} + +func defaultCipherSuites(aesGCMPreferred bool) []uint16 { + cipherSuites := supportedCipherSuites(aesGCMPreferred) + return slices.DeleteFunc(cipherSuites, func(c uint16) bool { return disabledCipherSuites[c] || rsaKexCiphers[c] || tdesCiphers[c] @@ -87,42 +96,4 @@ var defaultCipherSuitesTLS13NoAES = []uint16{ TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, -} - -// The FIPS-only policies below match BoringSSL's ssl_policy_fips_202205. - -var defaultSupportedVersionsFIPS = []uint16{ - VersionTLS12, - VersionTLS13, -} - -// defaultCurvePreferencesFIPS are the FIPS-allowed curves, -// in preference order (most preferable first). -var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384} - -// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of -// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1. -var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{ - PSSWithSHA256, - PSSWithSHA384, - PSSWithSHA512, - PKCS1WithSHA256, - ECDSAWithP256AndSHA256, - PKCS1WithSHA384, - ECDSAWithP384AndSHA384, - PKCS1WithSHA512, -} - -// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites. -var defaultCipherSuitesFIPS = []uint16{ - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, -} - -// defaultCipherSuitesTLS13FIPS are the FIPS-allowed cipher suites for TLS 1.3. -var defaultCipherSuitesTLS13FIPS = []uint16{ - TLS_AES_128_GCM_SHA256, - TLS_AES_256_GCM_SHA384, } \ No newline at end of file diff --git a/defaults_boring.go b/defaults_boring.go new file mode 100644 index 0000000..c953408 --- /dev/null +++ b/defaults_boring.go @@ -0,0 +1,69 @@ +// Copyright 2025 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. + +//go:build boringcrypto + +package reality + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/x509" +) + +// These Go+BoringCrypto policies mostly match BoringSSL's +// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2. +// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa +// +// P-521 is allowed per https://go.dev/issue/71757. +// +// They are applied when crypto/tls/fipsonly is imported with GOEXPERIMENT=boringcrypto. + +var ( + allowedSupportedVersionsFIPS = []uint16{ + VersionTLS12, + VersionTLS13, + } + allowedCurvePreferencesFIPS = []CurveID{ + CurveP256, + CurveP384, + CurveP521, + } + allowedSupportedSignatureAlgorithmsFIPS = []SignatureScheme{ + PSSWithSHA256, + PSSWithSHA384, + PSSWithSHA512, + PKCS1WithSHA256, + ECDSAWithP256AndSHA256, + PKCS1WithSHA384, + ECDSAWithP384AndSHA384, + PKCS1WithSHA512, + ECDSAWithP521AndSHA512, + } + allowedCipherSuitesFIPS = []uint16{ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } + allowedCipherSuitesTLS13FIPS = []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + } +) + +func isCertificateAllowedFIPS(c *x509.Certificate) bool { + // The key must be RSA 2048, RSA 3072, RSA 4096, + // or ECDSA P-256, P-384, P-521. + switch k := c.PublicKey.(type) { + case *rsa.PublicKey: + size := k.N.BitLen() + return size == 2048 || size == 3072 || size == 4096 + case *ecdsa.PublicKey: + return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521() + } + + return false +} \ No newline at end of file diff --git a/defaults_fips140.go b/defaults_fips140.go new file mode 100644 index 0000000..a0606b8 --- /dev/null +++ b/defaults_fips140.go @@ -0,0 +1,76 @@ +// Copyright 2025 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. + +//go:build !boringcrypto + +package reality + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" + "crypto/x509" +) + +// These FIPS 140-3 policies allow anything approved by SP 800-140C +// and SP 800-140D, and tested as part of the Go Cryptographic Module. +// +// Notably, not SHA-1, 3DES, RC4, ChaCha20Poly1305, RSA PKCS #1 v1.5 key +// transport, or TLS 1.0—1.1 (because we don't test its KDF). +// +// These are not default lists, but filters to apply to the default or +// configured lists. Missing items are treated as if they were not implemented. +// +// They are applied when the fips140 GODEBUG is "on" or "only". + +var ( + allowedSupportedVersionsFIPS = []uint16{ + VersionTLS12, + VersionTLS13, + } + allowedCurvePreferencesFIPS = []CurveID{ + X25519MLKEM768, + CurveP256, + CurveP384, + CurveP521, + } + allowedSupportedSignatureAlgorithmsFIPS = []SignatureScheme{ + PSSWithSHA256, + ECDSAWithP256AndSHA256, + Ed25519, + PSSWithSHA384, + PSSWithSHA512, + PKCS1WithSHA256, + PKCS1WithSHA384, + PKCS1WithSHA512, + ECDSAWithP384AndSHA384, + ECDSAWithP521AndSHA512, + } + allowedCipherSuitesFIPS = []uint16{ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + } + allowedCipherSuitesTLS13FIPS = []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + } +) + +func isCertificateAllowedFIPS(c *x509.Certificate) bool { + switch k := c.PublicKey.(type) { + case *rsa.PublicKey: + return k.N.BitLen() >= 2048 + case *ecdsa.PublicKey: + return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521() + case ed25519.PublicKey: + return true + default: + return false + } +} \ No newline at end of file diff --git a/ech.go b/ech.go index 56477fb..e2bf0a7 100644 --- a/ech.go +++ b/ech.go @@ -5,7 +5,10 @@ package reality import ( + "bytes" "errors" + "fmt" + "slices" "strings" "golang.org/x/crypto/cryptobyte" @@ -13,6 +16,18 @@ import ( "github.com/xtls/reality/hpke" ) +// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS. +// We need this so that when we insert them into ECHConfigs the ordering +// is stable. +var sortedSupportedAEADs []uint16 + +func init() { + for aeadID := range hpke.SupportedAEADs { + sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID) + } + slices.Sort(sortedSupportedAEADs) +} + type echCipher struct { KDFID uint16 AEADID uint16 @@ -39,86 +54,111 @@ type echConfig struct { Extensions []echExtension } -var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList") +var errMalformedECHConfigList = errors.New("tls: malformed ECHConfigList") + +type echConfigErr struct { + field string +} + +func (e *echConfigErr) Error() string { + if e.field == "" { + return "tls: malformed ECHConfig" + } + return fmt.Sprintf("tls: malformed ECHConfig, invalid %s field", e.field) +} + +func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) { + s := cryptobyte.String(enc) + ec.raw = []byte(enc) + if !s.ReadUint16(&ec.Version) { + return false, echConfig{}, &echConfigErr{"version"} + } + if !s.ReadUint16(&ec.Length) { + return false, echConfig{}, &echConfigErr{"length"} + } + if len(ec.raw) < int(ec.Length)+4 { + return false, echConfig{}, &echConfigErr{"length"} + } + ec.raw = ec.raw[:ec.Length+4] + if ec.Version != extensionEncryptedClientHello { + s.Skip(int(ec.Length)) + return true, echConfig{}, nil + } + if !s.ReadUint8(&ec.ConfigID) { + return false, echConfig{}, &echConfigErr{"config_id"} + } + if !s.ReadUint16(&ec.KemID) { + return false, echConfig{}, &echConfigErr{"kem_id"} + } + if !readUint16LengthPrefixed(&s, &ec.PublicKey) { + return false, echConfig{}, &echConfigErr{"public_key"} + } + var cipherSuites cryptobyte.String + if !s.ReadUint16LengthPrefixed(&cipherSuites) { + return false, echConfig{}, &echConfigErr{"cipher_suites"} + } + for !cipherSuites.Empty() { + var c echCipher + if !cipherSuites.ReadUint16(&c.KDFID) { + return false, echConfig{}, &echConfigErr{"cipher_suites kdf_id"} + } + if !cipherSuites.ReadUint16(&c.AEADID) { + return false, echConfig{}, &echConfigErr{"cipher_suites aead_id"} + } + ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) + } + if !s.ReadUint8(&ec.MaxNameLength) { + return false, echConfig{}, &echConfigErr{"maximum_name_length"} + } + var publicName cryptobyte.String + if !s.ReadUint8LengthPrefixed(&publicName) { + return false, echConfig{}, &echConfigErr{"public_name"} + } + ec.PublicName = publicName + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) { + return false, echConfig{}, &echConfigErr{"extensions"} + } + for !extensions.Empty() { + var e echExtension + if !extensions.ReadUint16(&e.Type) { + return false, echConfig{}, &echConfigErr{"extensions type"} + } + if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { + return false, echConfig{}, &echConfigErr{"extensions data"} + } + ec.Extensions = append(ec.Extensions, e) + } + + return false, ec, nil +} // parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a // slice of parsed ECHConfigs, in the same order they were parsed, or an error // if the list is malformed. func parseECHConfigList(data []byte) ([]echConfig, error) { s := cryptobyte.String(data) - // Skip the length prefix var length uint16 if !s.ReadUint16(&length) { - return nil, errMalformedECHConfig + return nil, errMalformedECHConfigList } if length != uint16(len(data)-2) { - return nil, errMalformedECHConfig + return nil, errMalformedECHConfigList } var configs []echConfig for len(s) > 0 { - var ec echConfig - ec.raw = []byte(s) - if !s.ReadUint16(&ec.Version) { - return nil, errMalformedECHConfig + if len(s) < 4 { + return nil, errors.New("tls: malformed ECHConfig") } - if !s.ReadUint16(&ec.Length) { - return nil, errMalformedECHConfig + configLen := uint16(s[2])<<8 | uint16(s[3]) + skip, ec, err := parseECHConfig(s) + if err != nil { + return nil, err } - if len(ec.raw) < int(ec.Length)+4 { - return nil, errMalformedECHConfig + s = s[configLen+4:] + if !skip { + configs = append(configs, ec) } - ec.raw = ec.raw[:ec.Length+4] - if ec.Version != extensionEncryptedClientHello { - s.Skip(int(ec.Length)) - continue - } - if !s.ReadUint8(&ec.ConfigID) { - return nil, errMalformedECHConfig - } - if !s.ReadUint16(&ec.KemID) { - return nil, errMalformedECHConfig - } - if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&ec.PublicKey)) { - return nil, errMalformedECHConfig - } - var cipherSuites cryptobyte.String - if !s.ReadUint16LengthPrefixed(&cipherSuites) { - return nil, errMalformedECHConfig - } - for !cipherSuites.Empty() { - var c echCipher - if !cipherSuites.ReadUint16(&c.KDFID) { - return nil, errMalformedECHConfig - } - if !cipherSuites.ReadUint16(&c.AEADID) { - return nil, errMalformedECHConfig - } - ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) - } - if !s.ReadUint8(&ec.MaxNameLength) { - return nil, errMalformedECHConfig - } - var publicName cryptobyte.String - if !s.ReadUint8LengthPrefixed(&publicName) { - return nil, errMalformedECHConfig - } - ec.PublicName = publicName - var extensions cryptobyte.String - if !s.ReadUint16LengthPrefixed(&extensions) { - return nil, errMalformedECHConfig - } - for !extensions.Empty() { - var e echExtension - if !extensions.ReadUint16(&e.Type) { - return nil, errMalformedECHConfig - } - if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { - return nil, errMalformedECHConfig - } - ec.Extensions = append(ec.Extensions, e) - } - - configs = append(configs, ec) } return configs, nil } @@ -196,6 +236,195 @@ func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, e return append(h, make([]byte, paddingLen)...), nil } +func skipUint8LengthPrefixed(s *cryptobyte.String) bool { + var skip uint8 + if !s.ReadUint8(&skip) { + return false + } + return s.Skip(int(skip)) +} + +func skipUint16LengthPrefixed(s *cryptobyte.String) bool { + var skip uint16 + if !s.ReadUint16(&skip) { + return false + } + return s.Skip(int(skip)) +} + +type rawExtension struct { + extType uint16 + data []byte +} + +func extractRawExtensions(hello *clientHelloMsg) ([]rawExtension, error) { + s := cryptobyte.String(hello.original) + if !s.Skip(4+2+32) || // header, version, random + !skipUint8LengthPrefixed(&s) || // session ID + !skipUint16LengthPrefixed(&s) || // cipher suites + !skipUint8LengthPrefixed(&s) { // compression methods + return nil, errors.New("tls: malformed outer client hello") + } + var rawExtensions []rawExtension + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) { + return nil, errors.New("tls: malformed outer client hello") + } + + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return nil, errors.New("tls: invalid inner client hello") + } + rawExtensions = append(rawExtensions, rawExtension{extension, extData}) + } + return rawExtensions, nil +} + +func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHelloMsg, error) { + // Reconstructing the inner client hello from its encoded form is somewhat + // complicated. It is missing its header (message type and length), session + // ID, and the extensions may be compressed. Since we need to put the + // extensions back in the same order as they were in the raw outer hello, + // and since we don't store the raw extensions, or the order we parsed them + // in, we need to reparse the raw extensions from the outer hello in order + // to properly insert them into the inner hello. This _should_ result in raw + // bytes which match the hello as it was generated by the client. + innerReader := cryptobyte.String(encoded) + var versionAndRandom, sessionID, cipherSuites, compressionMethods []byte + var extensions cryptobyte.String + if !innerReader.ReadBytes(&versionAndRandom, 2+32) || + !readUint8LengthPrefixed(&innerReader, &sessionID) || + len(sessionID) != 0 || + !readUint16LengthPrefixed(&innerReader, &cipherSuites) || + !readUint8LengthPrefixed(&innerReader, &compressionMethods) || + !innerReader.ReadUint16LengthPrefixed(&extensions) { + return nil, errors.New("tls: invalid inner client hello") + } + + // The specification says we must verify that the trailing padding is all + // zeros. This is kind of weird for TLS messages, where we generally just + // throw away any trailing garbage. + for _, p := range innerReader { + if p != 0 { + return nil, errors.New("tls: invalid inner client hello") + } + } + + rawOuterExts, err := extractRawExtensions(outer) + if err != nil { + return nil, err + } + + recon := cryptobyte.NewBuilder(nil) + recon.AddUint8(typeClientHello) + recon.AddUint24LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(versionAndRandom) + recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(outer.sessionId) + }) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(cipherSuites) + }) + recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(compressionMethods) + }) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + if extension == extensionECHOuterExtensions { + if !extData.ReadUint8LengthPrefixed(&extData) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + var i int + for !extData.Empty() { + var extType uint16 + if !extData.ReadUint16(&extType) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + if extType == extensionEncryptedClientHello { + recon.SetError(errors.New("tls: invalid outer extensions")) + return + } + for ; i <= len(rawOuterExts); i++ { + if i == len(rawOuterExts) { + recon.SetError(errors.New("tls: invalid outer extensions")) + return + } + if rawOuterExts[i].extType == extType { + break + } + } + recon.AddUint16(rawOuterExts[i].extType) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(rawOuterExts[i].data) + }) + } + } else { + recon.AddUint16(extension) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(extData) + }) + } + } + }) + }) + + reconBytes, err := recon.Bytes() + if err != nil { + return nil, err + } + inner := &clientHelloMsg{} + if !inner.unmarshal(reconBytes) { + return nil, errors.New("tls: invalid reconstructed inner client hello") + } + + if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) { + return nil, errInvalidECHExt + } + + hasTLS13 := false + for _, v := range inner.supportedVersions { + // Skip GREASE values (values of the form 0x?A0A). + // GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism used by + // browsers like Chrome to ensure TLS implementations correctly ignore unknown values. + // GREASE values follow a specific pattern: 0x?A0A, where ? can be any hex digit. + // These values should be ignored when processing supported TLS versions. + if v&0x0F0F == 0x0A0A && v&0xff == v>>8 { + continue + } + + // Ensure at least TLS 1.3 is offered. + if v == VersionTLS13 { + hasTLS13 = true + } else if v < VersionTLS13 { + // Reject if any non-GREASE value is below TLS 1.3, as ECH requires TLS 1.3+. + return nil, errors.New("tls: client sent encrypted_client_hello extension with unsupported versions") + } + } + + if !hasTLS13 { + return nil, errors.New("tls: client sent encrypted_client_hello extension but did not offer TLS 1.3") + } + + return inner, nil +} + +func decryptECHPayload(context *hpke.Recipient, hello, payload []byte) ([]byte, error) { + outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1) + return context.Open(outerAAD, payload) +} + func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) { var b cryptobyte.Builder b.AddUint8(0) // outer @@ -207,7 +436,7 @@ func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payl return b.Bytes() } -func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echContext, useKey bool) error { +func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error { var encapKey []byte if useKey { encapKey = ech.encapsulatedKey @@ -281,4 +510,160 @@ type ECHRejectionError struct { func (e *ECHRejectionError) Error() string { return "tls: server rejected ECH" +} + +var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension") +var errInvalidECHExt = errors.New("tls: client sent invalid encrypted_client_hello extension") + +type echExtType uint8 + +const ( + innerECHExt echExtType = 1 + outerECHExt echExtType = 0 +) + +func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) { + data := make([]byte, len(ext)) + copy(data, ext) + s := cryptobyte.String(data) + var echInt uint8 + if !s.ReadUint8(&echInt) { + err = errMalformedECHExt + return + } + echType = echExtType(echInt) + if echType == innerECHExt { + if !s.Empty() { + err = errMalformedECHExt + return + } + return echType, cs, 0, nil, nil, nil + } + if echType != outerECHExt { + err = errInvalidECHExt + return + } + if !s.ReadUint16(&cs.KDFID) { + err = errMalformedECHExt + return + } + if !s.ReadUint16(&cs.AEADID) { + err = errMalformedECHExt + return + } + if !s.ReadUint8(&configID) { + err = errMalformedECHExt + return + } + if !readUint16LengthPrefixed(&s, &encap) { + err = errMalformedECHExt + return + } + if !readUint16LengthPrefixed(&s, &payload) { + err = errMalformedECHExt + return + } + + // NOTE: clone encap and payload so that mutating them does not mutate the + // raw extension bytes. + return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil +} + +func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) { + builder := cryptobyte.NewBuilder(nil) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + for _, c := range configs { + builder.AddBytes(c.Config) + } + }) + return builder.Bytes() +} + +func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) { + echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello) + if err != nil { + if errors.Is(err, errInvalidECHExt) { + c.sendAlert(alertIllegalParameter) + } else { + c.sendAlert(alertDecodeError) + } + + return nil, nil, errInvalidECHExt + } + + if echType == innerECHExt { + return outer, &echServerContext{inner: true}, nil + } + + if len(c.config.EncryptedClientHelloKeys) == 0 { + return outer, nil, nil + } + + for _, echKey := range c.config.EncryptedClientHelloKeys { + skip, config, err := parseECHConfig(echKey.Config) + if err != nil || skip { + c.sendAlert(alertInternalError) + return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err) + } + if skip { + continue + } + echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey) + if err != nil { + c.sendAlert(alertInternalError) + return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err) + } + info := append([]byte("tls ech\x00"), echKey.Config...) + hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap) + if err != nil { + // attempt next trial decryption + continue + } + + encodedInner, err := decryptECHPayload(hpkeContext, outer.original, payload) + if err != nil { + // attempt next trial decryption + continue + } + + // NOTE: we do not enforce that the sent server_name matches the ECH + // configs PublicName, since this is not particularly important, and + // the client already had to know what it was in order to properly + // encrypt the payload. This is only a MAY in the spec, so we're not + // doing anything revolutionary. + + echInner, err := decodeInnerClientHello(outer, encodedInner) + if err != nil { + c.sendAlert(alertIllegalParameter) + return nil, nil, errInvalidECHExt + } + + c.echAccepted = true + + return echInner, &echServerContext{ + hpkeContext: hpkeContext, + configID: configID, + ciphersuite: echCiphersuite, + }, nil + } + + return outer, nil, nil +} + +func buildRetryConfigList(keys []EncryptedClientHelloKey) ([]byte, error) { + var atLeastOneRetryConfig bool + var retryBuilder cryptobyte.Builder + retryBuilder.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, c := range keys { + if !c.SendAsRetry { + continue + } + atLeastOneRetryConfig = true + b.AddBytes(c.Config) + } + }) + if !atLeastOneRetryConfig { + return nil, nil + } + return retryBuilder.Bytes() } \ No newline at end of file diff --git a/fips140tls/fipstls.go b/fips140tls/fipstls.go new file mode 100644 index 0000000..3cf9b0f --- /dev/null +++ b/fips140tls/fipstls.go @@ -0,0 +1,37 @@ +// Copyright 2024 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 fips140tls controls whether crypto/tls requires FIPS-approved settings. +package fips140tls + +import ( + "crypto/fips140" + "sync/atomic" +) + +var required atomic.Bool + +func init() { + if fips140.Enabled() { + Force() + } +} + +// Force forces crypto/tls to restrict TLS configurations to FIPS-approved settings. +// By design, this call is impossible to undo (except in tests). +func Force() { + required.Store(true) +} + +// Required reports whether FIPS-approved settings are required. +// +// Required is true if FIPS 140-3 mode is enabled with GODEBUG=fips140=on, or if +// the crypto/tls/fipsonly package is imported by a Go+BoringCrypto build. +func Required() bool { + return required.Load() +} + +func TestingOnlyAbandon() { + required.Store(false) +} \ No newline at end of file diff --git a/gcm/gcm.go b/gcm/gcm.go new file mode 100644 index 0000000..439779d --- /dev/null +++ b/gcm/gcm.go @@ -0,0 +1,143 @@ +// Copyright 2013 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 gcm + +import ( + "errors" + + "github.com/xtls/reality/aes" + "github.com/xtls/reality/alias" +) + +// GCM represents a Galois Counter Mode with a specific key. +type GCM struct { + cipher aes.Block + nonceSize int + tagSize int + gcmPlatformData +} + +func New(cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) { + // This function is outlined to let the allocation happen on the parent stack. + return newGCM(&GCM{}, cipher, nonceSize, tagSize) +} + +// newGCM is marked go:noinline to avoid it inlining into New, and making New +// too complex to inline itself. +// +//go:noinline +func newGCM(g *GCM, cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) { + if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { + return nil, errors.New("cipher: incorrect tag size given to GCM") + } + if nonceSize <= 0 { + return nil, errors.New("cipher: the nonce can't have zero length") + } + if cipher.BlockSize() != gcmBlockSize { + return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") + } + g.cipher = *cipher + g.nonceSize = nonceSize + g.tagSize = tagSize + initGCM(g) + return g, nil +} + +const ( + gcmBlockSize = 16 + gcmTagSize = 16 + gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. + gcmStandardNonceSize = 12 +) + +func (g *GCM) NonceSize() int { + return g.nonceSize +} + +func (g *GCM) Overhead() int { + return g.tagSize +} + +func (g *GCM) Seal(dst, nonce, plaintext, data []byte) []byte { + // fips140.RecordNonApproved() + return g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCM) sealAfterIndicator(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != g.nonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + if g.nonceSize == 0 { + panic("crypto/cipher: incorrect GCM nonce size") + } + if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { + panic("crypto/cipher: message too large for GCM") + } + + ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) + if alias.InexactOverlap(out, plaintext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, data) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + + seal(out, g, nonce, plaintext, data) + return ret +} + +var errOpen = errors.New("cipher: message authentication failed") + +func (g *GCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + if len(nonce) != g.nonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + // Sanity check to prevent the authentication from always succeeding if an + // implementation leaves tagSize uninitialized, for example. + if g.tagSize < gcmMinimumTagSize { + panic("crypto/cipher: incorrect GCM tag size") + } + + if len(ciphertext) < g.tagSize { + return nil, errOpen + } + if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) { + return nil, errOpen + } + + ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize) + if alias.InexactOverlap(out, ciphertext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, data) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + + // fips140.RecordApproved() + if err := open(out, g, nonce, ciphertext, data); err != nil { + // We sometimes decrypt and authenticate concurrently, so we overwrite + // dst in the event of a tag mismatch. To be consistent across platforms + // and to avoid releasing unauthenticated plaintext, we clear the buffer + // in the event of an error. + clear(out) + return nil, err + } + return ret, nil +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} \ No newline at end of file diff --git a/gcm/gcm_generic.go b/gcm/gcm_generic.go new file mode 100644 index 0000000..cd76068 --- /dev/null +++ b/gcm/gcm_generic.go @@ -0,0 +1,106 @@ +// Copyright 2024 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 gcm + +import ( + "crypto/subtle" + "encoding/binary" + + "github.com/xtls/reality/aes" +) + +func sealGeneric(out []byte, g *GCM, nonce, plaintext, additionalData []byte) { + var H, counter, tagMask [gcmBlockSize]byte + aes.EncryptBlockInternal(&g.cipher, H[:], H[:]) + deriveCounterGeneric(&H, &counter, nonce) + gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter) + + gcmCounterCryptGeneric(&g.cipher, out, plaintext, &counter) + + var tag [gcmTagSize]byte + gcmAuthGeneric(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData) + copy(out[len(plaintext):], tag[:]) +} + +func openGeneric(out []byte, g *GCM, nonce, ciphertext, additionalData []byte) error { + var H, counter, tagMask [gcmBlockSize]byte + aes.EncryptBlockInternal(&g.cipher, H[:], H[:]) + deriveCounterGeneric(&H, &counter, nonce) + gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter) + + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + var expectedTag [gcmTagSize]byte + gcmAuthGeneric(expectedTag[:], &H, &tagMask, ciphertext, additionalData) + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + + gcmCounterCryptGeneric(&g.cipher, out, ciphertext, &counter) + + return nil +} + +// deriveCounterGeneric computes the initial GCM counter state from the given nonce. +// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with +// zeros on entry. +func deriveCounterGeneric(H, counter *[gcmBlockSize]byte, nonce []byte) { + // GCM has two modes of operation with respect to the initial counter + // state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" + // for nonces of other lengths. For a 96-bit nonce, the nonce, along + // with a four-byte big-endian counter starting at one, is used + // directly as the starting counter. For other nonce sizes, the counter + // is computed by passing it through the GHASH function. + if len(nonce) == gcmStandardNonceSize { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + lenBlock := make([]byte, 16) + binary.BigEndian.PutUint64(lenBlock[8:], uint64(len(nonce))*8) + ghash(counter, H, nonce, lenBlock) + } +} + +// gcmCounterCryptGeneric encrypts src using AES in counter mode with 32-bit +// wrapping (which is different from AES-CTR) and places the result into out. +// counter is the initial value and will be updated with the next value. +func gcmCounterCryptGeneric(b *aes.Block, out, src []byte, counter *[gcmBlockSize]byte) { + var mask [gcmBlockSize]byte + + for len(src) >= gcmBlockSize { + aes.EncryptBlockInternal(b, mask[:], counter[:]) + gcmInc32(counter) + + subtle.XORBytes(out, src, mask[:]) + out = out[gcmBlockSize:] + src = src[gcmBlockSize:] + } + + if len(src) > 0 { + aes.EncryptBlockInternal(b, mask[:], counter[:]) + gcmInc32(counter) + subtle.XORBytes(out, src, mask[:]) + } +} + +// gcmInc32 treats the final four bytes of counterBlock as a big-endian value +// and increments it. +func gcmInc32(counterBlock *[gcmBlockSize]byte) { + ctr := counterBlock[len(counterBlock)-4:] + binary.BigEndian.PutUint32(ctr, binary.BigEndian.Uint32(ctr)+1) +} + +// gcmAuthGeneric calculates GHASH(additionalData, ciphertext), masks the result +// with tagMask and writes the result to out. +func gcmAuthGeneric(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { + checkGenericIsExpected() + lenBlock := make([]byte, 16) + binary.BigEndian.PutUint64(lenBlock[:8], uint64(len(additionalData))*8) + binary.BigEndian.PutUint64(lenBlock[8:], uint64(len(ciphertext))*8) + var S [gcmBlockSize]byte + ghash(&S, H, additionalData, ciphertext, lenBlock) + subtle.XORBytes(out, S[:], tagMask[:]) +} \ No newline at end of file diff --git a/gcm/gcm_noasm.go b/gcm/gcm_noasm.go new file mode 100644 index 0000000..06dc292 --- /dev/null +++ b/gcm/gcm_noasm.go @@ -0,0 +1,19 @@ +// Copyright 2024 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 gcm + +func checkGenericIsExpected() {} + +type gcmPlatformData struct{} + +func initGCM(g *GCM) {} + +func seal(out []byte, g *GCM, nonce, plaintext, data []byte) { + sealGeneric(out, g, nonce, plaintext, data) +} + +func open(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + return openGeneric(out, g, nonce, ciphertext, data) +} \ No newline at end of file diff --git a/gcm/gcm_nonces.go b/gcm/gcm_nonces.go new file mode 100644 index 0000000..e408760 --- /dev/null +++ b/gcm/gcm_nonces.go @@ -0,0 +1,255 @@ +// Copyright 2024 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 gcm + +import ( + "encoding/binary" + "math" + + "github.com/xtls/reality/aes" +) + +// SealWithRandomNonce encrypts plaintext to out, and writes a random nonce to +// nonce. nonce must be 12 bytes, and out must be 16 bytes longer than plaintext. +// out and plaintext may overlap exactly or not at all. additionalData and out +// must not overlap. +// +// This complies with FIPS 140-3 IG C.H Scenario 2. +// +// Note that this is NOT a [cipher.AEAD].Seal method. +// func SealWithRandomNonce(g *GCM, nonce, out, plaintext, additionalData []byte) { +// if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { +// panic("crypto/cipher: message too large for GCM") +// } +// if len(nonce) != gcmStandardNonceSize { +// panic("crypto/cipher: incorrect nonce length given to GCMWithRandomNonce") +// } +// if len(out) != len(plaintext)+gcmTagSize { +// panic("crypto/cipher: incorrect output length given to GCMWithRandomNonce") +// } +// if alias.InexactOverlap(out, plaintext) { +// panic("crypto/cipher: invalid buffer overlap of output and input") +// } +// if alias.AnyOverlap(out, additionalData) { +// panic("crypto/cipher: invalid buffer overlap of output and additional data") +// } +// // fips140.RecordApproved() +// drbg.Read(nonce) +// seal(out, g, nonce, plaintext, additionalData) +// } + +// NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces +// the construction of deterministic nonces. The nonce must be 96 bits, the +// first 32 bits must be an encoding of the module name, and the last 64 bits +// must be a counter. +// +// This complies with FIPS 140-3 IG C.H Scenario 3. +func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMWithCounterNonce{g: *g}, nil +} + +type GCMWithCounterNonce struct { + g GCM + ready bool + fixedName uint32 + start uint64 + next uint64 +} + +func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize } + +func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) + if !g.ready { + // The first invocation sets the fixed name encoding and start counter. + g.ready = true + g.start = counter + g.fixedName = binary.BigEndian.Uint32(nonce[:4]) + } + if g.fixedName != binary.BigEndian.Uint32(nonce[:4]) { + panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce") + } + counter -= g.start + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + // fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMWithCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + // fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325, +// Section 7.2.1. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.a. +func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForTLS12{g: *g}, nil +} + +type GCMForTLS12 struct { + g GCM + next uint64 +} + +func (g *GCMForTLS12) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForTLS12) Overhead() int { return gcmTagSize } + +func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + // fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForTLS12) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + // fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 8446, Section 5.3. +func NewGCMForTLS13(cipher *aes.Block) (*GCMForTLS13, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForTLS13{g: *g}, nil +} + +type GCMForTLS13 struct { + g GCM + ready bool + mask uint64 + next uint64 +} + +func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForTLS13) Overhead() int { return gcmTagSize } + +func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) + if !g.ready { + // In the first call, the counter is zero, so we learn the XOR mask. + g.ready = true + g.mask = counter + } + counter ^= g.mask + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + // fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + // fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5647. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.d. +func NewGCMForSSH(cipher *aes.Block) (*GCMForSSH, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForSSH{g: *g}, nil +} + +type GCMForSSH struct { + g GCM + ready bool + start uint64 + next uint64 +} + +func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForSSH) Overhead() int { return gcmTagSize } + +func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) + if !g.ready { + // In the first call we learn the start value. + g.ready = true + g.start = counter + } + counter -= g.start + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + // fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForSSH) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + // fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} \ No newline at end of file diff --git a/gcm/ghash.go b/gcm/ghash.go new file mode 100644 index 0000000..7c0ffd0 --- /dev/null +++ b/gcm/ghash.go @@ -0,0 +1,162 @@ +// Copyright 2024 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 gcm + +import ( + "encoding/binary" +) + +// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM +// standard and make binary.BigEndian suitable for marshaling these values, the +// bits are stored in big endian order. For example: +// +// the coefficient of x⁰ can be obtained by v.low >> 63. +// the coefficient of x⁶³ can be obtained by v.low & 1. +// the coefficient of x⁶⁴ can be obtained by v.high >> 63. +// the coefficient of x¹²⁷ can be obtained by v.high & 1. +type gcmFieldElement struct { + low, high uint64 +} + +// GHASH is exposed to allow crypto/cipher to implement non-AES GCM modes. +// It is not allowed as a stand-alone operation in FIPS mode because it +// is not ACVP tested. +func GHASH(key *[16]byte, inputs ...[]byte) []byte { + // fips140.RecordNonApproved() + var out [gcmBlockSize]byte + ghash(&out, key, inputs...) + return out[:] +} + +// ghash is a variable-time generic implementation of GHASH, which shouldn't +// be used on any architecture with hardware support for AES-GCM. +// +// Each input is zero-padded to 128-bit before being absorbed. +func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) { + // productTable contains the first sixteen powers of the key, H. + // However, they are in bit reversed order. + var productTable [16]gcmFieldElement + + // We precompute 16 multiples of H. However, when we do lookups + // into this table we'll be using bits from a field element and + // therefore the bits will be in the reverse order. So normally one + // would expect, say, 4*H to be in index 4 of the table but due to + // this bit ordering it will actually be in index 0010 (base 2) = 2. + x := gcmFieldElement{ + binary.BigEndian.Uint64(H[:8]), + binary.BigEndian.Uint64(H[8:]), + } + productTable[reverseBits(1)] = x + + for i := 2; i < 16; i += 2 { + productTable[reverseBits(i)] = ghashDouble(&productTable[reverseBits(i/2)]) + productTable[reverseBits(i+1)] = ghashAdd(&productTable[reverseBits(i)], &x) + } + + var y gcmFieldElement + for _, input := range inputs { + ghashUpdate(&productTable, &y, input) + } + + binary.BigEndian.PutUint64(out[:], y.low) + binary.BigEndian.PutUint64(out[8:], y.high) +} + +// reverseBits reverses the order of the bits of 4-bit number in i. +func reverseBits(i int) int { + i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) + i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) + return i +} + +// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum. +func ghashAdd(x, y *gcmFieldElement) gcmFieldElement { + // Addition in a characteristic 2 field is just XOR. + return gcmFieldElement{x.low ^ y.low, x.high ^ y.high} +} + +// ghashDouble returns the result of doubling an element of GF(2¹²⁸). +func ghashDouble(x *gcmFieldElement) (double gcmFieldElement) { + msbSet := x.high&1 == 1 + + // Because of the bit-ordering, doubling is actually a right shift. + double.high = x.high >> 1 + double.high |= x.low << 63 + double.low = x.low >> 1 + + // If the most-significant bit was set before shifting then it, + // conceptually, becomes a term of x^128. This is greater than the + // irreducible polynomial so the result has to be reduced. The + // irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to + // eliminate the term at x^128 which also means subtracting the other + // four terms. In characteristic 2 fields, subtraction == addition == + // XOR. + if msbSet { + double.low ^= 0xe100000000000000 + } + + return +} + +var ghashReductionTable = []uint16{ + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, +} + +// ghashMul sets y to y*H, where H is the GCM key, fixed during New. +func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) { + var z gcmFieldElement + + for i := 0; i < 2; i++ { + word := y.high + if i == 1 { + word = y.low + } + + // Multiplication works by multiplying z by 16 and adding in + // one of the precomputed multiples of H. + for j := 0; j < 64; j += 4 { + msw := z.high & 0xf + z.high >>= 4 + z.high |= z.low << 60 + z.low >>= 4 + z.low ^= uint64(ghashReductionTable[msw]) << 48 + + // the values in |table| are ordered for little-endian bit + // positions. See the comment in New. + t := productTable[word&0xf] + + z.low ^= t.low + z.high ^= t.high + word >>= 4 + } + } + + *y = z +} + +// updateBlocks extends y with more polynomial terms from blocks, based on +// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks. +func updateBlocks(productTable *[16]gcmFieldElement, y *gcmFieldElement, blocks []byte) { + for len(blocks) > 0 { + y.low ^= binary.BigEndian.Uint64(blocks) + y.high ^= binary.BigEndian.Uint64(blocks[8:]) + ghashMul(productTable, y) + blocks = blocks[gcmBlockSize:] + } +} + +// ghashUpdate extends y with more polynomial terms from data. If data is not a +// multiple of gcmBlockSize bytes long then the remainder is zero padded. +func ghashUpdate(productTable *[16]gcmFieldElement, y *gcmFieldElement, data []byte) { + fullBlocks := (len(data) >> 4) << 4 + updateBlocks(productTable, y, data[:fullBlocks]) + + if len(data) != fullBlocks { + var partialBlock [gcmBlockSize]byte + copy(partialBlock[:], data[fullBlocks:]) + updateBlocks(productTable, y, partialBlock[:]) + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index f3e384f..b19ccbf 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/xtls/reality -go 1.23 +go 1.24 require ( - github.com/pires/go-proxyproto v0.7.0 - golang.org/x/crypto v0.26.0 - golang.org/x/sys v0.24.0 + github.com/pires/go-proxyproto v0.8.1 + golang.org/x/crypto v0.38.0 + golang.org/x/sys v0.33.0 ) diff --git a/go.sum b/go.sum index 68bcfd9..6f4f082 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,16 @@ github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/handshake_client.go b/handshake_client.go index 0b1172a..de5c420 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -10,6 +10,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/mlkem" "crypto/rsa" "crypto/subtle" "crypto/x509" @@ -19,11 +20,13 @@ import ( "hash" "io" "net" + "slices" "strings" "time" + "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/hpke" - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" ) type clientHandshakeState struct { @@ -40,7 +43,7 @@ type clientHandshakeState struct { var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme -func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echContext, error) { +func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) { config := c.config if len(config.ServerName) == 0 && !config.InsecureSkipVerify { return nil, nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") @@ -91,24 +94,12 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon hello.secureRenegotiation = c.clientFinished[:] } - preferenceOrder := cipherSuitesPreferenceOrder - if !hasAESGCMHardwareSupport { - preferenceOrder = cipherSuitesPreferenceOrderNoAES - } - configCipherSuites := config.cipherSuites() - hello.cipherSuites = make([]uint16, 0, len(configCipherSuites)) - - for _, suiteId := range preferenceOrder { - suite := mutualCipherSuite(configCipherSuites, suiteId) - if suite == nil { - continue - } - // Don't advertise TLS 1.2-only cipher suites unless - // we're attempting TLS 1.2. - if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 { - continue - } - hello.cipherSuites = append(hello.cipherSuites, suiteId) + hello.cipherSuites = config.cipherSuites(hasAESGCMHardwareSupport) + // Don't advertise TLS 1.2-only cipher suites unless we're attempting TLS 1.2. + if maxVersion < VersionTLS12 { + hello.cipherSuites = slices.DeleteFunc(hello.cipherSuites, func(id uint16) bool { + return cipherSuiteByID(id).flags&suiteTLS12 != 0 + }) } _, err := io.ReadFull(config.rand(), hello.random) @@ -141,8 +132,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if len(hello.supportedVersions) == 1 { hello.cipherSuites = nil } - if needFIPS() { - hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) + if fips140tls.Required() { + hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...) } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) } else { @@ -154,27 +145,31 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon } curveID := hello.supportedCurves[0] keyShareKeys = &keySharePrivateKeys{curveID: curveID} - if curveID == x25519Kyber768Draft00 { + // Note that if X25519MLKEM768 is supported, it will be first because + // the preference order is fixed. + if curveID == X25519MLKEM768 { keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519) if err != nil { return nil, nil, nil, err } - seed := make([]byte, mlkem768.SeedSize) + seed := make([]byte, mlkem.SeedSize) if _, err := io.ReadFull(config.rand(), seed); err != nil { return nil, nil, nil, err } - keyShareKeys.kyber, err = mlkem768.NewKeyFromSeed(seed) + keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed) if err != nil { return nil, nil, nil, err } - // For draft-tls-westerbaan-xyber768d00-03, we send both a hybrid - // and a standard X25519 key share, since most servers will only - // support the latter. We reuse the same X25519 ephemeral key for - // both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. + mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes() + x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes() hello.keyShares = []keyShare{ - {group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(), - keyShareKeys.kyber.EncapsulationKey()...)}, - {group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()}, + {group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)}, + } + // If both X25519MLKEM768 and X25519 are supported, we send both key + // shares (as a fallback) and we reuse the same X25519 ephemeral + // key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. + if slices.Contains(hello.supportedCurves, X25519) { + hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey}) } } else { if _, ok := curveForCurveID(curveID); !ok { @@ -199,7 +194,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon hello.quicTransportParameters = p } - var ech *echContext + var ech *echClientContext if c.config.EncryptedClientHelloConfigList != nil { if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 { return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated") @@ -215,7 +210,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if echConfig == nil { return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs") } - ech = &echContext{config: echConfig} + ech = &echClientContext{config: echConfig} hello.encryptedClientHello = []byte{1} // indicate inner hello // We need to explicitly set these 1.2 fields to nil, as we do not // marshal them when encoding the inner hello, otherwise transcripts @@ -244,7 +239,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon return hello, keyShareKeys, ech, nil } -type echContext struct { +type echClientContext struct { config *echConfig hpkeContext *hpke.Sender encapsulatedKey []byte @@ -253,6 +248,7 @@ type echContext struct { kdfID uint16 aeadID uint16 echRejected bool + retryConfigs []byte } func (c *Conn) clientHandshake(ctx context.Context) (err error) { @@ -263,6 +259,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // This may be a renegotiation handshake, in which case some fields // need to be reset. c.didResume = false + c.curveID = 0 hello, keyShareKeys, ech, err := c.makeClientHello() if err != nil { @@ -325,7 +322,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { if err := transcriptMsg(hello, transcript); err != nil { return err } - earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript) + earlyTrafficSecret := earlySecret.ClientEarlyTrafficSecret(transcript) c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret) } @@ -384,7 +381,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { } func (c *Conn) loadSession(hello *clientHelloMsg) ( - session *SessionState, earlySecret, binderKey []byte, err error) { + session *SessionState, earlySecret *tls13.EarlySecret, binderKey []byte, err error) { if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil { return nil, nil, nil, nil } @@ -456,6 +453,11 @@ func (c *Conn) loadSession(hello *clientHelloMsg) ( return nil, nil, nil, nil } + // FIPS 140-3 requires the use of Extended Master Secret. + if !session.extMasterSecret && fips140tls.Required() { + return nil, nil, nil, nil + } + hello.sessionTicket = session.ticket return } @@ -511,8 +513,8 @@ func (c *Conn) loadSession(hello *clientHelloMsg) ( hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())} // Compute the PSK binders. See RFC 8446, Section 4.2.11.2. - earlySecret = cipherSuite.extract(session.secret, nil) - binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil) + earlySecret = tls13.NewEarlySecret(cipherSuite.hash.New, session.secret) + binderKey = earlySecret.ResumptionBinderKey() transcript := cipherSuite.hash.New() if err := computeAndUpdatePSK(hello, binderKey, transcript, cipherSuite.finishedHash); err != nil { return nil, nil, nil, err @@ -546,6 +548,19 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error { func (hs *clientHandshakeState) handshake() error { c := hs.c + // If we did not load a session (hs.session == nil), but we did set a + // session ID in the transmitted client hello (hs.hello.sessionId != nil), + // it means we tried to negotiate TLS 1.3 and sent a random session ID as a + // compatibility measure (see RFC 8446, Section 4.1.2). + // + // Since we're now handshaking for TLS 1.2, if the server echoed the + // transmitted ID back to us, we know mischief is afoot: the session ID + // was random and can't possibly be recognized by the server. + if hs.session == nil && hs.hello.sessionId != nil && bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: server echoed TLS 1.3 compatibility session ID in TLS 1.2") + } + isResume, err := hs.processServerHello() if err != nil { return err @@ -702,7 +717,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { if ok { err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx) if err != nil { - c.sendAlert(alertUnexpectedMessage) + c.sendAlert(alertIllegalParameter) return err } if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { @@ -766,6 +781,10 @@ func (hs *clientHandshakeState) doFullHandshake() error { hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash.Sum()) } else { + if fips140tls.Required() { + c.sendAlert(alertHandshakeFailure) + return errors.New("tls: FIPS 140-3 requires the use of Extended Master Secret") + } hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random) } @@ -863,10 +882,23 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { } if hs.serverHello.compressionMethod != compressionNone { - c.sendAlert(alertUnexpectedMessage) + c.sendAlert(alertIllegalParameter) return false, errors.New("tls: server selected unsupported compression format") } + supportsPointFormat := false + offeredNonCompressedFormat := false + for _, format := range hs.serverHello.supportedPoints { + if format == pointFormatUncompressed { + supportsPointFormat = true + } else { + offeredNonCompressedFormat = true + } + } + if !supportsPointFormat && offeredNonCompressedFormat { + return false, errors.New("tls: server offered only incompatible point formats") + } + if c.handshakes == 0 && hs.serverHello.secureRenegotiationSupported { c.secureRenegotiation = true if len(hs.serverHello.secureRenegotiation) != 0 { @@ -921,16 +953,17 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { c.verifiedChains = hs.session.verifiedChains c.ocspResponse = hs.session.ocspResponse // Let the ServerHello SCTs override the session SCTs from the original - // connection, if any are provided + // connection, if any are provided. if len(c.scts) == 0 && len(hs.session.scts) != 0 { c.scts = hs.session.scts } + c.curveID = hs.session.curveID return true, nil } // checkALPN ensure that the server's choice of ALPN protocol is compatible with -// the protocols that we advertised in the Client Hello. +// the protocols that we advertised in the ClientHello. func checkALPN(clientProtos []string, serverProto string, quic bool) error { if serverProto == "" { if quic && len(clientProtos) > 0 { @@ -1072,7 +1105,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for i, asn1Data := range certificates { cert, err := globalCertCache.newCert(asn1Data) if err != nil { - c.sendAlert(alertBadCertificate) + c.sendAlert(alertDecodeError) return errors.New("tls: failed to parse certificate from server: " + err.Error()) } if cert.cert.PublicKeyAlgorithm == x509.RSA { @@ -1104,8 +1137,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - var err error - c.verifiedChains, err = certs[0].Verify(opts) + chains, err := certs[0].Verify(opts) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + + c.verifiedChains, err = fipsAllowedChains(chains) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} @@ -1122,8 +1160,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - var err error - c.verifiedChains, err = certs[0].Verify(opts) + chains, err := certs[0].Verify(opts) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + + c.verifiedChains, err = fipsAllowedChains(chains) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 15efbd1..df14c3e 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -8,7 +8,9 @@ import ( "bytes" "context" "crypto" + "crypto/hkdf" "crypto/hmac" + "crypto/mlkem" "crypto/rsa" "crypto/subtle" "errors" @@ -16,7 +18,7 @@ import ( "slices" "time" - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" ) type clientHandshakeStateTLS13 struct { @@ -27,7 +29,7 @@ type clientHandshakeStateTLS13 struct { keyShareKeys *keySharePrivateKeys session *SessionState - earlySecret []byte + earlySecret *tls13.EarlySecret binderKey []byte certReq *certificateRequestMsgTLS13 @@ -35,9 +37,9 @@ type clientHandshakeStateTLS13 struct { sentDummyCCS bool suite *cipherSuiteTLS13 transcript hash.Hash - masterSecret []byte + masterSecret *tls13.MasterSecret trafficSecret []byte // client_application_traffic_secret_0 - echContext *echContext + echContext *echClientContext } // handshake requires hs.c, hs.hello, hs.serverHello, hs.keyShareKeys, and, @@ -83,18 +85,18 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } } - var echRetryConfigList []byte if hs.echContext != nil { confTranscript := cloneHash(hs.echContext.innerTranscript, hs.suite.hash) confTranscript.Write(hs.serverHello.original[:30]) confTranscript.Write(make([]byte, 8)) confTranscript.Write(hs.serverHello.original[38:]) - acceptConfirmation := hs.suite.expandLabel( - hs.suite.extract(hs.echContext.innerHello.random, nil), - "ech accept confirmation", - confTranscript.Sum(nil), - 8, - ) + h := hs.suite.hash.New + prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + acceptConfirmation := tls13.ExpandLabel(h, prk, "ech accept confirmation", confTranscript.Sum(nil), 8) if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.random[len(hs.serverHello.random)-8:]) == 1 { hs.hello = hs.echContext.innerHello c.serverName = c.config.ServerName @@ -103,7 +105,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { if hs.serverHello.encryptedClientHello != nil { c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: unexpected encrypted_client_hello extension in server hello despite ECH being accepted") + return errors.New("tls: unexpected encrypted client hello extension in server hello despite ECH being accepted") } if hs.hello.serverName == "" && hs.serverHello.serverNameAck { @@ -112,9 +114,6 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } } else { hs.echContext.echRejected = true - // If the server sent us retry configs, we'll return these to - // the user so they can update their Config. - echRetryConfigList = hs.serverHello.encryptedClientHello } } @@ -153,7 +152,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { if hs.echContext != nil && hs.echContext.echRejected { c.sendAlert(alertECHRequired) - return &ECHRejectionError{echRetryConfigList} + return &ECHRejectionError{hs.echContext.retryConfigs} } c.isHandshakeComplete.Store(true) @@ -198,8 +197,8 @@ func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error { } if hs.serverHello.compressionMethod != compressionNone { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: server selected unsupported compression format") + c.sendAlert(alertDecodeError) + return errors.New("tls: server sent non-zero legacy TLS compression method") } selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite) @@ -266,12 +265,13 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { copy(hrrHello, hs.serverHello.original) hrrHello = bytes.Replace(hrrHello, hs.serverHello.encryptedClientHello, make([]byte, 8), 1) confTranscript.Write(hrrHello) - acceptConfirmation := hs.suite.expandLabel( - hs.suite.extract(hs.echContext.innerHello.random, nil), - "hrr ech accept confirmation", - confTranscript.Sum(nil), - 8, - ) + h := hs.suite.hash.New + prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + acceptConfirmation := tls13.ExpandLabel(h, prk, "hrr ech accept confirmation", confTranscript.Sum(nil), 8) if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.encryptedClientHello) == 1 { hello = hs.echContext.innerHello c.serverName = c.config.ServerName @@ -286,7 +286,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { } else if hs.serverHello.encryptedClientHello != nil { // Unsolicited ECH extension should be rejected c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: unexpected ECH extension in serverHello") + return errors.New("tls: unexpected encrypted client hello extension in serverHello") } // The only HelloRetryRequest extensions we support are key_share and @@ -320,12 +320,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share") } - // Note: we don't support selecting X25519Kyber768Draft00 in a HRR, - // because we currently only support it at all when CurvePreferences is - // empty, which will cause us to also send a key share for it. + // Note: we don't support selecting X25519MLKEM768 in a HRR, because it + // is currently first in preference order, so if it's enabled we'll + // always send a key share for it. // - // This will have to change once we support selecting hybrid KEMs - // without sending key shares for them. + // This will have to change once we support multiple hybrid KEMs. if _, ok := curveForCurveID(curveID); !ok { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") @@ -478,12 +477,12 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c := hs.c ecdhePeerData := hs.serverHello.serverShare.data - if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize { + if hs.serverHello.serverShare.group == X25519MLKEM768 { + if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid server key share") + return errors.New("tls: invalid server X25519MLKEM768 key share") } - ecdhePeerData = hs.serverHello.serverShare.data[:x25519PublicKeySize] + ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:] } peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData) if err != nil { @@ -495,33 +494,30 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid server key share") } - if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if hs.keyShareKeys.kyber == nil { + if hs.serverHello.serverShare.group == X25519MLKEM768 { + if hs.keyShareKeys.mlkem == nil { return c.sendAlert(alertInternalError) } - ciphertext := hs.serverHello.serverShare.data[x25519PublicKeySize:] - kyberShared, err := kyberDecapsulate(hs.keyShareKeys.kyber, ciphertext) + ciphertext := hs.serverHello.serverShare.data[:mlkem.CiphertextSize768] + mlkemShared, err := hs.keyShareKeys.mlkem.Decapsulate(ciphertext) if err != nil { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber server key share") + return errors.New("tls: invalid X25519MLKEM768 server key share") } - sharedKey = append(sharedKey, kyberShared...) + sharedKey = append(mlkemShared, sharedKey...) } c.curveID = hs.serverHello.serverShare.group earlySecret := hs.earlySecret if !hs.usingPSK { - earlySecret = hs.suite.extract(nil, nil) + earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil) } - handshakeSecret := hs.suite.extract(sharedKey, - hs.suite.deriveSecret(earlySecret, "derived", nil)) + handshakeSecret := earlySecret.HandshakeSecret(sharedKey) - clientSecret := hs.suite.deriveSecret(handshakeSecret, - clientHandshakeTrafficLabel, hs.transcript) + clientSecret := handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret) - serverSecret := hs.suite.deriveSecret(handshakeSecret, - serverHandshakeTrafficLabel, hs.transcript) + serverSecret := handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret) if c.quic != nil { @@ -543,8 +539,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { return err } - hs.masterSecret = hs.suite.extract(nil, - hs.suite.deriveSecret(handshakeSecret, "derived", nil)) + hs.masterSecret = handshakeSecret.MasterSecret() return nil } @@ -604,9 +599,13 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error { return errors.New("tls: server accepted 0-RTT with the wrong ALPN") } } - if hs.echContext != nil && !hs.echContext.echRejected && encryptedExtensions.echRetryConfigs != nil { - c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: server sent ECH retry configs after accepting ECH") + if hs.echContext != nil { + if hs.echContext.echRejected { + hs.echContext.retryConfigs = encryptedExtensions.echRetryConfigs + } else if encryptedExtensions.echRetryConfigs != nil { + c.sendAlert(alertUnsupportedExtension) + return errors.New("tls: server sent encrypted client hello retry configs after accepting encrypted client hello") + } } return nil @@ -732,10 +731,8 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error { // Derive secrets that take context through the server Finished. - hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret, - clientApplicationTrafficLabel, hs.transcript) - serverSecret := hs.suite.deriveSecret(hs.masterSecret, - serverApplicationTrafficLabel, hs.transcript) + hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript) + serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret) err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret) @@ -842,8 +839,7 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error { c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret) if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil { - c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret, - resumptionLabel, hs.transcript) + c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript) } if c.quic != nil { @@ -876,6 +872,11 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error { return errors.New("tls: received a session ticket with invalid lifetime") } + if len(msg.label) == 0 { + c.sendAlert(alertDecodeError) + return errors.New("tls: received a session ticket with empty opaque ticket label") + } + // RFC 9001, Section 4.6.1 if c.quic != nil && msg.maxEarlyData != 0 && msg.maxEarlyData != 0xffffffff { c.sendAlert(alertIllegalParameter) @@ -887,7 +888,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error { return c.sendAlert(alertInternalError) } - psk := cipherSuite.expandLabel(c.resumptionSecret, "resumption", + psk := tls13.ExpandLabel(cipherSuite.hash.New, c.resumptionSecret, "resumption", msg.nonce, cipherSuite.hash.Size()) session := c.sessionState() diff --git a/handshake_messages.go b/handshake_messages.go index 7ae20ea..bcd9a83 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -662,6 +662,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } m.pskBinders = append(m.pskBinders, binder) } + case extensionEncryptedClientHello: + if !extData.ReadBytes(&m.encryptedClientHello, len(extData)) { + return false + } default: // Ignore unknown extensions. continue diff --git a/handshake_server.go b/handshake_server.go index dbc0551..7f98cb8 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -18,6 +18,8 @@ import ( "hash" "io" "time" + + "github.com/xtls/reality/fips140tls" ) // serverHandshakeState contains details of a server handshake in progress. @@ -40,7 +42,7 @@ type serverHandshakeState struct { // serverHandshake performs a TLS handshake as a server. func (c *Conn) serverHandshake(ctx context.Context) error { - clientHello, err := c.readClientHello(ctx) + clientHello, ech, err := c.readClientHello(ctx) if err != nil { return err } @@ -50,6 +52,7 @@ func (c *Conn) serverHandshake(ctx context.Context) error { c: c, ctx: ctx, clientHello: clientHello, + echContext: ech, } return hs.handshake() } @@ -130,17 +133,27 @@ func (hs *serverHandshakeState) handshake() error { } // readClientHello reads a ClientHello message and selects the protocol version. -func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { +func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServerContext, error) { // clientHelloMsg is included in the transcript, but we haven't initialized // it yet. The respective handshake functions will record it themselves. msg, err := c.readHandshake(nil) if err != nil { - return nil, err + return nil, nil, err } clientHello, ok := msg.(*clientHelloMsg) if !ok { c.sendAlert(alertUnexpectedMessage) - return nil, unexpectedMessageError(clientHello, msg) + return nil, nil, unexpectedMessageError(clientHello, msg) + } + + // ECH processing has to be done before we do any other negotiation based on + // the contents of the client hello, since we may swap it out completely. + var ech *echServerContext + if len(clientHello.encryptedClientHello) != 0 { + clientHello, ech, err = c.processECHClientHello(clientHello) + if err != nil { + return nil, nil, err + } } var configForClient *Config @@ -149,7 +162,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { chi := clientHelloInfo(ctx, c, clientHello) if configForClient, err = c.config.GetConfigForClient(chi); err != nil { c.sendAlert(alertInternalError) - return nil, err + return nil, nil, err } else if configForClient != nil { c.config = configForClient } @@ -157,19 +170,39 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { c.ticketKeys = originalConfig.ticketKeys(configForClient) clientVersions := clientHello.supportedVersions - if len(clientHello.supportedVersions) == 0 { + if clientHello.vers >= VersionTLS13 && len(clientVersions) == 0 { + // RFC 8446 4.2.1 indicates when the supported_versions extension is not sent, + // compatible servers MUST negotiate TLS 1.2 or earlier if supported, even + // if the client legacy version is TLS 1.3 or later. + // + // Since we reject empty extensionSupportedVersions in the client hello unmarshal + // finding the supportedVersions empty indicates the extension was not present. + clientVersions = supportedVersionsFromMax(VersionTLS12) + } else if len(clientVersions) == 0 { clientVersions = supportedVersionsFromMax(clientHello.vers) } c.vers, ok = c.config.mutualVersion(roleServer, clientVersions) if !ok { c.sendAlert(alertProtocolVersion) - return nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) + return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) } c.haveVers = true c.in.version = c.vers c.out.version = c.vers - return clientHello, nil + // This check reflects some odd specification implied behavior. Client-facing servers + // are supposed to reject hellos with outer ECH and inner ECH that offers 1.2, but + // backend servers are allowed to accept hellos with inner ECH that offer 1.2, since + // they cannot expect client-facing servers to behave properly. Since we act as both + // a client-facing and backend server, we only enforce 1.3 being negotiated if we + // saw a hello with outer ECH first. The spec probably should've made this an error, + // but it didn't, and this matches the boringssl behavior. + if c.vers != VersionTLS13 && (ech != nil && !ech.inner) { + c.sendAlert(alertIllegalParameter) + return nil, nil, errors.New("tls: Encrypted Client Hello cannot be used pre-TLS 1.3") + } + + return clientHello, ech, nil } func (hs *serverHandshakeState) processClientHello() error { @@ -243,7 +276,11 @@ func (hs *serverHandshakeState) processClientHello() error { hs.hello.scts = hs.cert.SignedCertificateTimestamps } - hs.ecdheOk = supportsECDHE(c.config, c.vers, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints) + hs.ecdheOk, err = supportsECDHE(c.config, c.vers, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints) + if err != nil { + c.sendAlert(alertMissingExtension) + return err + } if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 { // Although omitting the ec_point_formats extension is permitted, some @@ -314,7 +351,7 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro // supportsECDHE returns whether ECDHE key exchanges can be used with this // pre-TLS 1.3 client. -func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, supportedPoints []uint8) bool { +func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, supportedPoints []uint8) (bool, error) { supportsCurve := false for _, curve := range supportedCurves { if c.supportsCurve(version, curve) { @@ -324,10 +361,12 @@ func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, support } supportsPointFormat := false + offeredNonCompressedFormat := false for _, pointFormat := range supportedPoints { if pointFormat == pointFormatUncompressed { supportsPointFormat = true - break + } else { + offeredNonCompressedFormat = true } } // Per RFC 8422, Section 5.1.2, if the Supported Point Formats extension is @@ -336,34 +375,23 @@ func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, support // the parser. See https://go.dev/issue/49126. if len(supportedPoints) == 0 { supportsPointFormat = true + } else if offeredNonCompressedFormat && !supportsPointFormat { + return false, errors.New("tls: client offered only incompatible point formats") } - return supportsCurve && supportsPointFormat + return supportsCurve && supportsPointFormat, nil } func (hs *serverHandshakeState) pickCipherSuite() error { c := hs.c - preferenceOrder := cipherSuitesPreferenceOrder - if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { - preferenceOrder = cipherSuitesPreferenceOrderNoAES - } - - configCipherSuites := c.config.cipherSuites() - preferenceList := make([]uint16, 0, len(configCipherSuites)) - for _, suiteID := range preferenceOrder { - for _, id := range configCipherSuites { - if id == suiteID { - preferenceList = append(preferenceList, id) - break - } - } - } + preferenceList := c.config.cipherSuites(isAESGCMPreferred(hs.clientHello.cipherSuites)) hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk) if hs.suite == nil { c.sendAlert(alertHandshakeFailure) - return errors.New("tls: no cipher suite supported by both client and server") + return fmt.Errorf("tls: no cipher suite supported by both client and server; client offered: %x", + hs.clientHello.cipherSuites) } c.cipherSuite = hs.suite.id @@ -459,7 +487,7 @@ func (hs *serverHandshakeState) checkForResumption() error { // Check that we also support the ciphersuite from the session. suite := selectCipherSuite([]uint16{sessionState.cipherSuite}, - c.config.cipherSuites(), hs.cipherSuiteOk) + c.config.supportedCipherSuites(), hs.cipherSuiteOk) if suite == nil { return nil } @@ -489,6 +517,10 @@ func (hs *serverHandshakeState) checkForResumption() error { // weird downgrade in client capabilities. return errors.New("tls: session supported extended_master_secret but client does not") } + if !sessionState.extMasterSecret && fips140tls.Required() { + // FIPS 140-3 requires the use of Extended Master Secret. + return nil + } c.peerCertificates = sessionState.peerCertificates c.ocspResponse = sessionState.ocspResponse @@ -497,6 +529,7 @@ func (hs *serverHandshakeState) checkForResumption() error { c.extMasterSecret = sessionState.extMasterSecret hs.sessionState = sessionState hs.suite = suite + c.curveID = sessionState.curveID c.didResume = true return nil } @@ -667,7 +700,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers) if err != nil { - c.sendAlert(alertHandshakeFailure) + c.sendAlert(alertIllegalParameter) return err } if hs.hello.extendedMasterSecret { @@ -675,6 +708,10 @@ func (hs *serverHandshakeState) doFullHandshake() error { hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash.Sum()) } else { + if fips140tls.Required() { + c.sendAlert(alertHandshakeFailure) + return errors.New("tls: FIPS 140-3 requires the use of Extended Master Secret") + } hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random) } @@ -856,14 +893,14 @@ func (hs *serverHandshakeState) sendFinished(out []byte) error { } // processCertsFromClient takes a chain of client certificates either from a -// Certificates message and verifies them. +// certificateMsg message or a certificateMsgTLS13 message and verifies them. func (c *Conn) processCertsFromClient(certificate Certificate) error { certificates := certificate.Certificate certs := make([]*x509.Certificate, len(certificates)) var err error for i, asn1Data := range certificates { if certs[i], err = x509.ParseCertificate(asn1Data); err != nil { - c.sendAlert(alertBadCertificate) + c.sendAlert(alertDecodeError) return errors.New("tls: failed to parse client certificate: " + err.Error()) } if certs[i].PublicKeyAlgorithm == x509.RSA { @@ -879,7 +916,7 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { if c.vers == VersionTLS13 { c.sendAlert(alertCertificateRequired) } else { - c.sendAlert(alertBadCertificate) + c.sendAlert(alertHandshakeFailure) } return errors.New("tls: client didn't provide a certificate") } @@ -909,7 +946,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } - c.verifiedChains = chains + c.verifiedChains, err = fipsAllowedChains(chains) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } } c.peerCertificates = certs diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 72ed000..2242e5c 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -9,20 +9,26 @@ import ( "context" "crypto" "crypto/ed25519" + "crypto/hkdf" "crypto/hmac" + "crypto/mlkem" "crypto/rand" "crypto/rsa" "crypto/sha512" "crypto/x509" "encoding/binary" "errors" + "fmt" "hash" "io" "math/big" "slices" + "sort" "time" - - "github.com/xtls/reality/mlkem768" + + "github.com/xtls/reality/fips140tls" + "github.com/xtls/reality/hpke" + "github.com/xtls/reality/tls13" ) // maxClientPSKIdentities is the number of client PSK identities the server will @@ -30,6 +36,18 @@ import ( // messages cause too much work in session ticket decryption attempts. const maxClientPSKIdentities = 5 +type echServerContext struct { + hpkeContext *hpke.Recipient + configID uint8 + ciphersuite echCipher + transcript hash.Hash + // inner indicates that the initial client_hello we recieved contained an + // encrypted_client_hello extension that indicated it was an "inner" hello. + // We don't do any additional processing of the hello in this case, so all + // fields above are unset. + inner bool +} + type serverHandshakeStateTLS13 struct { c *Conn ctx context.Context @@ -41,13 +59,14 @@ type serverHandshakeStateTLS13 struct { suite *cipherSuiteTLS13 cert *Certificate sigAlg SignatureScheme - earlySecret []byte + earlySecret *tls13.EarlySecret sharedKey []byte - handshakeSecret []byte - masterSecret []byte + handshakeSecret *tls13.HandshakeSecret + masterSecret *tls13.MasterSecret trafficSecret []byte // client_application_traffic_secret_0 transcript hash.Hash clientFinished []byte + echContext *echServerContext } var ( @@ -211,11 +230,11 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { hs.hello.compressionMethod = compressionNone preferenceList := defaultCipherSuitesTLS13 - if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { + if !hasAESGCMHardwareSupport || !isAESGCMPreferred(hs.clientHello.cipherSuites) { preferenceList = defaultCipherSuitesTLS13NoAES } - if needFIPS() { - preferenceList = defaultCipherSuitesTLS13FIPS + if fips140tls.Required() { + preferenceList = allowedCipherSuitesTLS13FIPS } for _, suiteID := range preferenceList { hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID) @@ -225,43 +244,52 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { } if hs.suite == nil { c.sendAlert(alertHandshakeFailure) - return errors.New("tls: no cipher suite supported by both client and server") + return fmt.Errorf("tls: no cipher suite supported by both client and server; client offered: %x", + hs.clientHello.cipherSuites) } c.cipherSuite = hs.suite.id hs.hello.cipherSuite = hs.suite.id hs.transcript = hs.suite.hash.New() - // Pick the key exchange method in server preference order, but give - // priority to key shares, to avoid a HelloRetryRequest round-trip. - var selectedGroup CurveID - var clientKeyShare *keyShare + // First, if a post-quantum key exchange is available, use one. See + // draft-ietf-tls-key-share-prediction-01, Section 4 for why this must be + // first. + // + // Second, if the client sent a key share for a group we support, use that, + // to avoid a HelloRetryRequest round-trip. + // + // Finally, pick in our fixed preference order. preferredGroups := c.config.curvePreferences(c.vers) - for _, preferredGroup := range preferredGroups { - ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool { - return ks.group == preferredGroup - }) - if ki != -1 { - clientKeyShare = &hs.clientHello.keyShares[ki] - selectedGroup = clientKeyShare.group - if !slices.Contains(hs.clientHello.supportedCurves, selectedGroup) { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: client sent key share for group it does not support") + preferredGroups = slices.DeleteFunc(preferredGroups, func(group CurveID) bool { + return !slices.Contains(hs.clientHello.supportedCurves, group) + }) + if len(preferredGroups) == 0 { + c.sendAlert(alertHandshakeFailure) + return errors.New("tls: no key exchanges supported by both client and server") + } + hasKeyShare := func(group CurveID) bool { + for _, ks := range hs.clientHello.keyShares { + if ks.group == group { + return true } + } + return false + } + sort.SliceStable(preferredGroups, func(i, j int) bool { + return hasKeyShare(preferredGroups[i]) && !hasKeyShare(preferredGroups[j]) + }) + sort.SliceStable(preferredGroups, func(i, j int) bool { + return isPQKeyExchange(preferredGroups[i]) && !isPQKeyExchange(preferredGroups[j]) + }) + selectedGroup := preferredGroups[0] + + var clientKeyShare *keyShare + for _, ks := range hs.clientHello.keyShares { + if ks.group == selectedGroup { + clientKeyShare = &ks break } } - if selectedGroup == 0 { - for _, preferredGroup := range preferredGroups { - if slices.Contains(hs.clientHello.supportedCurves, preferredGroup) { - selectedGroup = preferredGroup - break - } - } - } - if selectedGroup == 0 { - c.sendAlert(alertHandshakeFailure) - return errors.New("tls: no ECDHE curve supported by both client and server") - } if clientKeyShare == nil { ks, err := hs.doHelloRetryRequest(selectedGroup) if err != nil { @@ -273,13 +301,13 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ecdhGroup := selectedGroup ecdhData := clientKeyShare.data - if selectedGroup == x25519Kyber768Draft00 { + if selectedGroup == X25519MLKEM768 { ecdhGroup = X25519 - if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize { + if len(ecdhData) != mlkem.EncapsulationKeySize768+x25519PublicKeySize { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber client key share") + return errors.New("tls: invalid X25519MLKEM768 client key share") } - ecdhData = ecdhData[:x25519PublicKeySize] + ecdhData = ecdhData[mlkem.EncapsulationKeySize768:] } if _, ok := curveForCurveID(ecdhGroup); !ok { c.sendAlert(alertInternalError) @@ -301,14 +329,24 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } - if selectedGroup == x25519Kyber768Draft00 { - ciphertext, kyberShared, err := kyberEncapsulate(clientKeyShare.data[x25519PublicKeySize:]) + if selectedGroup == X25519MLKEM768 { + k, err := mlkem.NewEncapsulationKey768(clientKeyShare.data[:mlkem.EncapsulationKeySize768]) if err != nil { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber client key share") + return errors.New("tls: invalid X25519MLKEM768 client key share") } - hs.sharedKey = append(hs.sharedKey, kyberShared...) - hs.hello.serverShare.data = append(hs.hello.serverShare.data, ciphertext...) + mlkemSharedSecret, ciphertext := k.Encapsulate() + // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.3: "For + // X25519MLKEM768, the shared secret is the concatenation of the ML-KEM + // shared secret and the X25519 shared secret. The shared secret is 64 + // bytes (32 bytes for each part)." + hs.sharedKey = append(mlkemSharedSecret, hs.sharedKey...) + // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.2: "When the + // X25519MLKEM768 group is negotiated, the server's key exchange value + // is the concatenation of an ML-KEM ciphertext returned from + // encapsulation to the client's encapsulation key, and the server's + // ephemeral X25519 share." + hs.hello.serverShare.data = append(ciphertext, hs.hello.serverShare.data...) } selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) @@ -435,8 +473,8 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error { } } - hs.earlySecret = hs.suite.extract(sessionState.secret, nil) - binderKey := hs.suite.deriveSecret(hs.earlySecret, resumptionBinderLabel, nil) + hs.earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, sessionState.secret) + binderKey := hs.earlySecret.ResumptionBinderKey() // Clone the transcript in case a HelloRetryRequest was recorded. transcript := cloneHash(hs.transcript, hs.suite.hash) if transcript == nil { @@ -464,7 +502,7 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error { if err := transcriptMsg(hs.clientHello, transcript); err != nil { return err } - earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript) + earlyTrafficSecret := hs.earlySecret.ClientEarlyTrafficSecret(transcript) c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret) } @@ -582,6 +620,23 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) selectedGroup: selectedGroup, } + if hs.echContext != nil { + // Compute the acceptance message. + helloRetryRequest.encryptedClientHello = make([]byte, 8) + confTranscript := cloneHash(hs.transcript, hs.suite.hash) + if err := transcriptMsg(helloRetryRequest, confTranscript); err != nil { + return nil, err + } + h := hs.suite.hash.New + prf, err := hkdf.Extract(h, hs.clientHello.random, nil) + if err != nil { + c.sendAlert(alertInternalError) + return nil, err + } + acceptConfirmation := tls13.ExpandLabel(h, prf, "hrr ech accept confirmation", confTranscript.Sum(nil), 8) + helloRetryRequest.encryptedClientHello = acceptConfirmation + } + if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil { return nil, err } @@ -602,6 +657,45 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) return nil, unexpectedMessageError(clientHello, msg) } + if hs.echContext != nil { + if len(clientHello.encryptedClientHello) == 0 { + c.sendAlert(alertMissingExtension) + return nil, errors.New("tls: second client hello missing encrypted client hello extension") + } + + echType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello) + if err != nil { + c.sendAlert(alertDecodeError) + return nil, errors.New("tls: client sent invalid encrypted client hello extension") + } + + if echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner { + c.sendAlert(alertDecodeError) + return nil, errors.New("tls: unexpected switch in encrypted client hello extension type") + } + + if echType == outerECHExt { + if echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("tls: second client hello encrypted client hello extension does not match") + } + + encodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload) + if err != nil { + c.sendAlert(alertDecryptError) + return nil, errors.New("tls: failed to decrypt second client hello encrypted client hello extension payload") + } + + echInner, err := decodeInnerClientHello(clientHello, encodedInner) + if err != nil { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("tls: client sent invalid encrypted client hello extension") + } + + clientHello = echInner + } + } + if len(clientHello.keyShares) != 1 { c.sendAlert(alertIllegalParameter) return nil, errors.New("tls: client didn't send one key share in second ClientHello") @@ -689,9 +783,28 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { func (hs *serverHandshakeStateTLS13) sendServerParameters() error { c := hs.c + if hs.echContext != nil { + copy(hs.hello.random[32-8:], make([]byte, 8)) + echTranscript := cloneHash(hs.transcript, hs.suite.hash) + echTranscript.Write(hs.clientHello.original) + if err := transcriptMsg(hs.hello, echTranscript); err != nil { + return err + } + // compute the acceptance message + h := hs.suite.hash.New + prk, err := hkdf.Extract(h, hs.clientHello.random, nil) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + acceptConfirmation := tls13.ExpandLabel(h, prk, "ech accept confirmation", echTranscript.Sum(nil), 8) + copy(hs.hello.random[32-8:], acceptConfirmation) + } + if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil { return err } + if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil { return err } @@ -702,16 +815,13 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { earlySecret := hs.earlySecret if earlySecret == nil { - earlySecret = hs.suite.extract(nil, nil) + earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil) } - hs.handshakeSecret = hs.suite.extract(hs.sharedKey, - hs.suite.deriveSecret(earlySecret, "derived", nil)) + hs.handshakeSecret = earlySecret.HandshakeSecret(hs.sharedKey) - clientSecret := hs.suite.deriveSecret(hs.handshakeSecret, - clientHandshakeTrafficLabel, hs.transcript) + clientSecret := hs.handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret) - serverSecret := hs.suite.deriveSecret(hs.handshakeSecret, - serverHandshakeTrafficLabel, hs.transcript) + serverSecret := hs.handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret) if c.quic != nil { @@ -745,6 +855,16 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { encryptedExtensions.earlyData = hs.earlyData } + // If client sent ECH extension, but we didn't accept it, + // send retry configs, if available. + if len(hs.c.config.EncryptedClientHelloKeys) > 0 && len(hs.clientHello.encryptedClientHello) > 0 && hs.echContext == nil { + encryptedExtensions.echRetryConfigs, err = buildRetryConfigList(hs.c.config.EncryptedClientHelloKeys) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + } + if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil { return err } @@ -836,13 +956,10 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error { // Derive secrets that take context through the server Finished. - hs.masterSecret = hs.suite.extract(nil, - hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil)) + hs.masterSecret = hs.handshakeSecret.MasterSecret() - hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret, - clientApplicationTrafficLabel, hs.transcript) - serverSecret := hs.suite.deriveSecret(hs.masterSecret, - serverApplicationTrafficLabel, hs.transcript) + hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript) + serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret) if c.quic != nil { @@ -889,12 +1006,7 @@ func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool { } // Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9. - for _, pskMode := range hs.clientHello.pskModes { - if pskMode == pskModeDHE { - return true - } - } - return false + return slices.Contains(hs.clientHello.pskModes, pskModeDHE) } func (hs *serverHandshakeStateTLS13) sendSessionTickets() error { @@ -908,8 +1020,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error { return err } - c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret, - resumptionLabel, hs.transcript) + c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript) if !hs.shouldSendSessionTickets() { return nil @@ -924,7 +1035,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error { } // ticket_nonce, which must be unique per connection, is always left at // zero because we only ever send one ticket per connection. - psk := suite.expandLabel(c.resumptionSecret, "resumption", + psk := tls13.ExpandLabel(suite.hash.New, c.resumptionSecret, "resumption", nil, suite.hash.Size()) m := new(newSessionTicketMsgTLS13) diff --git a/hpke/hpye.go b/hpke/hpye.go index 611c89a..9ef26be 100644 --- a/hpke/hpye.go +++ b/hpke/hpye.go @@ -9,13 +9,13 @@ import ( "crypto/aes" "crypto/cipher" "crypto/ecdh" + "crypto/hkdf" "crypto/rand" "encoding/binary" "errors" "math/bits" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/hkdf" ) // testingOnlyGenerateKey is only used during testing, to provide @@ -26,28 +26,23 @@ type hkdfKDF struct { hash crypto.Hash } -func (kdf *hkdfKDF) LabeledExtract(suiteID []byte, salt []byte, label string, inputKey []byte) []byte { - labeledIKM := make([]byte, 0, 7+len(suiteID)+len(label)+len(inputKey)) +func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputKey []byte) ([]byte, error) { + labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey)) labeledIKM = append(labeledIKM, []byte("HPKE-v1")...) - labeledIKM = append(labeledIKM, suiteID...) + labeledIKM = append(labeledIKM, sid...) labeledIKM = append(labeledIKM, label...) labeledIKM = append(labeledIKM, inputKey...) return hkdf.Extract(kdf.hash.New, labeledIKM, salt) } -func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) []byte { +func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) ([]byte, error) { labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info)) labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length) labeledInfo = append(labeledInfo, []byte("HPKE-v1")...) labeledInfo = append(labeledInfo, suiteID...) labeledInfo = append(labeledInfo, label...) labeledInfo = append(labeledInfo, info...) - out := make([]byte, length) - n, err := hkdf.Expand(kdf.hash.New, randomKey, labeledInfo).Read(out) - if err != nil || n != int(length) { - panic("hpke: LabeledExpand failed unexpectedly") - } - return out + return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length)) } // dhKEM implements the KEM specified in RFC 9180, Section 4.1. @@ -59,13 +54,17 @@ type dhKEM struct { nSecret uint16 } +type KemID uint16 + +const DHKEM_X25519_HKDF_SHA256 = 0x0020 + var SupportedKEMs = map[uint16]struct { curve ecdh.Curve hash crypto.Hash nSecret uint16 }{ // RFC 9180 Section 7.1 - 0x0020: {ecdh.X25519(), crypto.SHA256, 32}, + DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32}, } func newDHKem(kemID uint16) (*dhKEM, error) { @@ -81,8 +80,11 @@ func newDHKem(kemID uint16) (*dhKEM, error) { }, nil } -func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) []byte { - eaePRK := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey) +func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) ([]byte, error) { + eaePRK, err := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey) + if err != nil { + return nil, err + } return dh.kdf.LabeledExpand(dh.suiteID[:], eaePRK, "shared_secret", kemContext, dh.nSecret) } @@ -104,13 +106,28 @@ func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encap encPubRecip := pubRecipient.Bytes() kemContext := append(encPubEph, encPubRecip...) - - return dh.ExtractAndExpand(dhVal, kemContext), encPubEph, nil + sharedSecret, err = dh.ExtractAndExpand(dhVal, kemContext) + if err != nil { + return nil, nil, err + } + return sharedSecret, encPubEph, nil } -type Sender struct { +func (dh *dhKEM) Decap(encPubEph []byte, secRecipient *ecdh.PrivateKey) ([]byte, error) { + pubEph, err := dh.dh.NewPublicKey(encPubEph) + if err != nil { + return nil, err + } + dhVal, err := secRecipient.ECDH(pubEph) + if err != nil { + return nil, err + } + kemContext := append(encPubEph, secRecipient.PublicKey().Bytes()...) + return dh.ExtractAndExpand(dhVal, kemContext) +} + +type context struct { aead cipher.AEAD - kem *dhKEM sharedSecret []byte @@ -123,6 +140,14 @@ type Sender struct { seqNum uint128 } +type Sender struct { + *context +} + +type Recipient struct { + *context +} + var aesGCMNew = func(key []byte) (cipher.AEAD, error) { block, err := aes.NewCipher(key) if err != nil { @@ -131,97 +156,160 @@ var aesGCMNew = func(key []byte) (cipher.AEAD, error) { return cipher.NewGCM(block) } +type AEADID uint16 + +const ( + AEAD_AES_128_GCM = 0x0001 + AEAD_AES_256_GCM = 0x0002 + AEAD_ChaCha20Poly1305 = 0x0003 +) + var SupportedAEADs = map[uint16]struct { keySize int nonceSize int aead func([]byte) (cipher.AEAD, error) }{ // RFC 9180, Section 7.3 - 0x0001: {keySize: 16, nonceSize: 12, aead: aesGCMNew}, - 0x0002: {keySize: 32, nonceSize: 12, aead: aesGCMNew}, - 0x0003: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New}, + AEAD_AES_128_GCM: {keySize: 16, nonceSize: 12, aead: aesGCMNew}, + AEAD_AES_256_GCM: {keySize: 32, nonceSize: 12, aead: aesGCMNew}, + AEAD_ChaCha20Poly1305: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New}, } +type KDFID uint16 + +const KDF_HKDF_SHA256 = 0x0001 + var SupportedKDFs = map[uint16]func() *hkdfKDF{ // RFC 9180, Section 7.2 - 0x0001: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} }, + KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} }, } -func SetupSender(kemID, kdfID, aeadID uint16, pub crypto.PublicKey, info []byte) ([]byte, *Sender, error) { - suiteID := SuiteID(kemID, kdfID, aeadID) - - kem, err := newDHKem(kemID) - if err != nil { - return nil, nil, err - } - pubRecipient, ok := pub.(*ecdh.PublicKey) - if !ok { - return nil, nil, errors.New("incorrect public key type") - } - sharedSecret, encapsulatedKey, err := kem.Encap(pubRecipient) - if err != nil { - return nil, nil, err - } +func newContext(sharedSecret []byte, kemID, kdfID, aeadID uint16, info []byte) (*context, error) { + sid := suiteID(kemID, kdfID, aeadID) kdfInit, ok := SupportedKDFs[kdfID] if !ok { - return nil, nil, errors.New("unsupported KDF id") + return nil, errors.New("unsupported KDF id") } kdf := kdfInit() aeadInfo, ok := SupportedAEADs[aeadID] if !ok { - return nil, nil, errors.New("unsupported AEAD id") + return nil, errors.New("unsupported AEAD id") } - pskIDHash := kdf.LabeledExtract(suiteID, nil, "psk_id_hash", nil) - infoHash := kdf.LabeledExtract(suiteID, nil, "info_hash", info) + pskIDHash, err := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil) + if err != nil { + return nil, err + } + infoHash, err := kdf.LabeledExtract(sid, nil, "info_hash", info) + if err != nil { + return nil, err + } ksContext := append([]byte{0}, pskIDHash...) ksContext = append(ksContext, infoHash...) - secret := kdf.LabeledExtract(suiteID, sharedSecret, "secret", nil) - - key := kdf.LabeledExpand(suiteID, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */) - baseNonce := kdf.LabeledExpand(suiteID, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */) - exporterSecret := kdf.LabeledExpand(suiteID, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/) + secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil) + if err != nil { + return nil, err + } + key, err := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */) + if err != nil { + return nil, err + } + baseNonce, err := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */) + if err != nil { + return nil, err + } + exporterSecret, err := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/) + if err != nil { + return nil, err + } aead, err := aeadInfo.aead(key) if err != nil { - return nil, nil, err + return nil, err } - return encapsulatedKey, &Sender{ - kem: kem, + return &context{ aead: aead, sharedSecret: sharedSecret, - suiteID: suiteID, + suiteID: sid, key: key, baseNonce: baseNonce, exporterSecret: exporterSecret, }, nil } -func (s *Sender) nextNonce() []byte { - nonce := s.seqNum.bytes()[16-s.aead.NonceSize():] - for i := range s.baseNonce { - nonce[i] ^= s.baseNonce[i] +func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) { + kem, err := newDHKem(kemID) + if err != nil { + return nil, nil, err } - // Message limit is, according to the RFC, 2^95+1, which - // is somewhat confusing, but we do as we're told. - if s.seqNum.bitLen() >= (s.aead.NonceSize()*8)-1 { - panic("message limit reached") + sharedSecret, encapsulatedKey, err := kem.Encap(pub) + if err != nil { + return nil, nil, err + } + + context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info) + if err != nil { + return nil, nil, err + } + + return encapsulatedKey, &Sender{context}, nil +} + +func SetupRecipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Recipient, error) { + kem, err := newDHKem(kemID) + if err != nil { + return nil, err + } + sharedSecret, err := kem.Decap(encPubEph, priv) + if err != nil { + return nil, err + } + + context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info) + if err != nil { + return nil, err + } + + return &Recipient{context}, nil +} + +func (ctx *context) nextNonce() []byte { + nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():] + for i := range ctx.baseNonce { + nonce[i] ^= ctx.baseNonce[i] } - s.seqNum = s.seqNum.addOne() return nonce } -func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) { +func (ctx *context) incrementNonce() { + // Message limit is, according to the RFC, 2^95+1, which + // is somewhat confusing, but we do as we're told. + if ctx.seqNum.bitLen() >= (ctx.aead.NonceSize()*8)-1 { + panic("message limit reached") + } + ctx.seqNum = ctx.seqNum.addOne() +} +func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) { ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad) + s.incrementNonce() return ciphertext, nil } -func SuiteID(kemID, kdfID, aeadID uint16) []byte { +func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) { + plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad) + if err != nil { + return nil, err + } + r.incrementNonce() + return plaintext, nil +} + +func suiteID(kemID, kdfID, aeadID uint16) []byte { suiteID := make([]byte, 0, 4+2+2+2) suiteID = append(suiteID, []byte("HPKE")...) suiteID = binary.BigEndian.AppendUint16(suiteID, kemID) @@ -238,6 +326,14 @@ func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) { return kemInfo.curve.NewPublicKey(bytes) } +func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) { + kemInfo, ok := SupportedKEMs[kemID] + if !ok { + return nil, errors.New("unsupported KEM id") + } + return kemInfo.curve.NewPrivateKey(bytes) +} + type uint128 struct { hi, lo uint64 } @@ -256,4 +352,4 @@ func (u uint128) bytes() []byte { binary.BigEndian.PutUint64(b[0:], u.hi) binary.BigEndian.PutUint64(b[8:], u.lo) return b -} +} \ No newline at end of file diff --git a/key_schedule.go b/key_schedule.go index 63636b5..332936c 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -7,94 +7,27 @@ package reality import ( "crypto/ecdh" "crypto/hmac" + "crypto/mlkem" "errors" - "fmt" "hash" "io" - "golang.org/x/crypto/cryptobyte" - "golang.org/x/crypto/hkdf" - "golang.org/x/crypto/sha3" - - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" ) // This file contains the functions necessary to compute the TLS 1.3 key // schedule. See RFC 8446, Section 7. -const ( - resumptionBinderLabel = "res binder" - clientEarlyTrafficLabel = "c e traffic" - clientHandshakeTrafficLabel = "c hs traffic" - serverHandshakeTrafficLabel = "s hs traffic" - clientApplicationTrafficLabel = "c ap traffic" - serverApplicationTrafficLabel = "s ap traffic" - exporterLabel = "exp master" - resumptionLabel = "res master" - trafficUpdateLabel = "traffic upd" -) - -// expandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. -func (c *cipherSuiteTLS13) expandLabel(secret []byte, label string, context []byte, length int) []byte { - var hkdfLabel cryptobyte.Builder - hkdfLabel.AddUint16(uint16(length)) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte("tls13 ")) - b.AddBytes([]byte(label)) - }) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(context) - }) - hkdfLabelBytes, err := hkdfLabel.Bytes() - if err != nil { - // Rather than calling BytesOrPanic, we explicitly handle this error, in - // order to provide a reasonable error message. It should be basically - // impossible for this to panic, and routing errors back through the - // tree rooted in this function is quite painful. The labels are fixed - // size, and the context is either a fixed-length computed hash, or - // parsed from a field which has the same length limitation. As such, an - // error here is likely to only be caused during development. - // - // NOTE: another reasonable approach here might be to return a - // randomized slice if we encounter an error, which would break the - // connection, but avoid panicking. This would perhaps be safer but - // significantly more confusing to users. - panic(fmt.Errorf("failed to construct HKDF label: %s", err)) - } - out := make([]byte, length) - n, err := hkdf.Expand(c.hash.New, secret, hkdfLabelBytes).Read(out) - if err != nil || n != length { - panic("tls: HKDF-Expand-Label invocation failed unexpectedly") - } - return out -} - -// deriveSecret implements Derive-Secret from RFC 8446, Section 7.1. -func (c *cipherSuiteTLS13) deriveSecret(secret []byte, label string, transcript hash.Hash) []byte { - if transcript == nil { - transcript = c.hash.New() - } - return c.expandLabel(secret, label, transcript.Sum(nil), c.hash.Size()) -} - -// extract implements HKDF-Extract with the cipher suite hash. -func (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte { - if newSecret == nil { - newSecret = make([]byte, c.hash.Size()) - } - return hkdf.Extract(c.hash.New, newSecret, currentSecret) -} - // nextTrafficSecret generates the next traffic secret, given the current one, // according to RFC 8446, Section 7.2. func (c *cipherSuiteTLS13) nextTrafficSecret(trafficSecret []byte) []byte { - return c.expandLabel(trafficSecret, trafficUpdateLabel, nil, c.hash.Size()) + return tls13.ExpandLabel(c.hash.New, trafficSecret, "traffic upd", nil, c.hash.Size()) } // trafficKey generates traffic keys according to RFC 8446, Section 7.3. func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) { - key = c.expandLabel(trafficSecret, "key", nil, c.keyLen) - iv = c.expandLabel(trafficSecret, "iv", nil, aeadNonceLength) + key = tls13.ExpandLabel(c.hash.New, trafficSecret, "key", nil, c.keyLen) + iv = tls13.ExpandLabel(c.hash.New, trafficSecret, "iv", nil, aeadNonceLength) return } @@ -102,7 +35,7 @@ func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) { // to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey // selection. func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte { - finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size()) + finishedKey := tls13.ExpandLabel(c.hash.New, baseKey, "finished", nil, c.hash.Size()) verifyData := hmac.New(c.hash.New, finishedKey) verifyData.Write(transcript.Sum(nil)) return verifyData.Sum(nil) @@ -110,51 +43,17 @@ func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) [] // exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to // RFC 8446, Section 7.5. -func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) { - expMasterSecret := c.deriveSecret(masterSecret, exporterLabel, transcript) +func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcript hash.Hash) func(string, []byte, int) ([]byte, error) { + expMasterSecret := s.ExporterMasterSecret(transcript) return func(label string, context []byte, length int) ([]byte, error) { - secret := c.deriveSecret(expMasterSecret, label, nil) - h := c.hash.New() - h.Write(context) - return c.expandLabel(secret, "exporter", h.Sum(nil), length), nil + return expMasterSecret.Exporter(label, context, length), nil } } type keySharePrivateKeys struct { curveID CurveID ecdhe *ecdh.PrivateKey - kyber *mlkem768.DecapsulationKey -} - -// kyberDecapsulate implements decapsulation according to Kyber Round 3. -func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { - K, err := mlkem768.Decapsulate(dk, c) - if err != nil { - return nil, err - } - return kyberSharedSecret(K, c), nil -} - -// kyberEncapsulate implements encapsulation according to Kyber Round 3. -func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - c, ss, err = mlkem768.Encapsulate(ek) - if err != nil { - return nil, nil, err - } - return c, kyberSharedSecret(ss, c), nil -} - -func kyberSharedSecret(K, c []byte) []byte { - // Package mlkem768 implements ML-KEM, which compared to Kyber removed a - // final hashing step. Compute SHAKE-256(K || SHA3-256(c), 32) to match Kyber. - // See https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3. - h := sha3.NewShake256() - h.Write(K) - ch := sha3.Sum256(c) - h.Write(ch[:]) - out := make([]byte, 32) - h.Read(out) - return out + mlkem *mlkem.DecapsulationKey768 } const x25519PublicKeySize = 32 diff --git a/mlkem768/mlkem768.go b/mlkem768/mlkem768.go deleted file mode 100644 index 24bedea..0000000 --- a/mlkem768/mlkem768.go +++ /dev/null @@ -1,886 +0,0 @@ -// Copyright 2023 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 mlkem768 implements the quantum-resistant key encapsulation method -// ML-KEM (formerly known as Kyber). -// -// Only the recommended ML-KEM-768 parameter set is provided. -// -// The version currently implemented is the one specified by [NIST FIPS 203 ipd], -// with the unintentional transposition of the matrix A reverted to match the -// behavior of [Kyber version 3.0]. Future versions of this package might -// introduce backwards incompatible changes to implement changes to FIPS 203. -// -// [Kyber version 3.0]: https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf -// [NIST FIPS 203 ipd]: https://doi.org/10.6028/NIST.FIPS.203.ipd -package mlkem768 - -// This package targets security, correctness, simplicity, readability, and -// reviewability as its primary goals. All critical operations are performed in -// constant time. -// -// Variable and function names, as well as code layout, are selected to -// facilitate reviewing the implementation against the NIST FIPS 203 ipd -// document. -// -// Reviewers unfamiliar with polynomials or linear algebra might find the -// background at https://words.filippo.io/kyber-math/ useful. - -import ( - "crypto/rand" - "crypto/subtle" - "encoding/binary" - "errors" - - "golang.org/x/crypto/sha3" -) - -const ( - // ML-KEM global constants. - n = 256 - q = 3329 - - log2q = 12 - - // ML-KEM-768 parameters. The code makes assumptions based on these values, - // they can't be changed blindly. - k = 3 - η = 2 - du = 10 - dv = 4 - - // encodingSizeX is the byte size of a ringElement or nttElement encoded - // by ByteEncode_X (FIPS 203 (DRAFT), Algorithm 4). - encodingSize12 = n * log2q / 8 - encodingSize10 = n * du / 8 - encodingSize4 = n * dv / 8 - encodingSize1 = n * 1 / 8 - - messageSize = encodingSize1 - decryptionKeySize = k * encodingSize12 - encryptionKeySize = k*encodingSize12 + 32 - - CiphertextSize = k*encodingSize10 + encodingSize4 - EncapsulationKeySize = encryptionKeySize - DecapsulationKeySize = decryptionKeySize + encryptionKeySize + 32 + 32 - SharedKeySize = 32 - SeedSize = 32 + 32 -) - -// A DecapsulationKey is the secret key used to decapsulate a shared key from a -// ciphertext. It includes various precomputed values. -type DecapsulationKey struct { - dk [DecapsulationKeySize]byte - encryptionKey - decryptionKey -} - -// Bytes returns the extended encoding of the decapsulation key, according to -// FIPS 203 (DRAFT). -func (dk *DecapsulationKey) Bytes() []byte { - var b [DecapsulationKeySize]byte - copy(b[:], dk.dk[:]) - return b[:] -} - -// EncapsulationKey returns the public encapsulation key necessary to produce -// ciphertexts. -func (dk *DecapsulationKey) EncapsulationKey() []byte { - var b [EncapsulationKeySize]byte - copy(b[:], dk.dk[decryptionKeySize:]) - return b[:] -} - -// encryptionKey is the parsed and expanded form of a PKE encryption key. -type encryptionKey struct { - t [k]nttElement // ByteDecode₁₂(ek[:384k]) - A [k * k]nttElement // A[i*k+j] = sampleNTT(ρ, j, i) -} - -// decryptionKey is the parsed and expanded form of a PKE decryption key. -type decryptionKey struct { - s [k]nttElement // ByteDecode₁₂(dk[:decryptionKeySize]) -} - -// GenerateKey generates a new decapsulation key, drawing random bytes from -// crypto/rand. The decapsulation key must be kept secret. -func GenerateKey() (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return generateKey(dk) -} - -func generateKey(dk *DecapsulationKey) (*DecapsulationKey, error) { - var d [32]byte - if _, err := rand.Read(d[:]); err != nil { - return nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - var z [32]byte - if _, err := rand.Read(z[:]); err != nil { - return nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - return kemKeyGen(dk, &d, &z), nil -} - -// NewKeyFromSeed deterministically generates a decapsulation key from a 64-byte -// seed in the "d || z" form. The seed must be uniformly random. -func NewKeyFromSeed(seed []byte) (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return newKeyFromSeed(dk, seed) -} - -func newKeyFromSeed(dk *DecapsulationKey, seed []byte) (*DecapsulationKey, error) { - if len(seed) != SeedSize { - return nil, errors.New("mlkem768: invalid seed length") - } - d := (*[32]byte)(seed[:32]) - z := (*[32]byte)(seed[32:]) - return kemKeyGen(dk, d, z), nil -} - -// NewKeyFromExtendedEncoding parses a decapsulation key from its FIPS 203 -// (DRAFT) extended encoding. -func NewKeyFromExtendedEncoding(decapsulationKey []byte) (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return newKeyFromExtendedEncoding(dk, decapsulationKey) -} - -func newKeyFromExtendedEncoding(dk *DecapsulationKey, dkBytes []byte) (*DecapsulationKey, error) { - if len(dkBytes) != DecapsulationKeySize { - return nil, errors.New("mlkem768: invalid decapsulation key length") - } - - // Note that we don't check that H(ek) matches ekPKE, as that's not - // specified in FIPS 203 (DRAFT). This is one reason to prefer the seed - // private key format. - dk.dk = [DecapsulationKeySize]byte(dkBytes) - - dkPKE := dkBytes[:decryptionKeySize] - if err := parseDK(&dk.decryptionKey, dkPKE); err != nil { - return nil, err - } - - ekPKE := dkBytes[decryptionKeySize : decryptionKeySize+encryptionKeySize] - if err := parseEK(&dk.encryptionKey, ekPKE); err != nil { - return nil, err - } - - return dk, nil -} - -// kemKeyGen generates a decapsulation key. -// -// It implements ML-KEM.KeyGen according to FIPS 203 (DRAFT), Algorithm 15, and -// K-PKE.KeyGen according to FIPS 203 (DRAFT), Algorithm 12. The two are merged -// to save copies and allocations. -func kemKeyGen(dk *DecapsulationKey, d, z *[32]byte) *DecapsulationKey { - if dk == nil { - dk = &DecapsulationKey{} - } - - G := sha3.Sum512(d[:]) - ρ, σ := G[:32], G[32:] - - A := &dk.A - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - // Note that this is consistent with Kyber round 3, rather than with - // the initial draft of FIPS 203, because NIST signaled that the - // change was involuntary and will be reverted. - A[i*k+j] = sampleNTT(ρ, j, i) - } - } - - var N byte - s := &dk.s - for i := range s { - s[i] = ntt(samplePolyCBD(σ, N)) - N++ - } - e := make([]nttElement, k) - for i := range e { - e[i] = ntt(samplePolyCBD(σ, N)) - N++ - } - - t := &dk.t - for i := range t { // t = A ◦ s + e - t[i] = e[i] - for j := range s { - t[i] = polyAdd(t[i], nttMul(A[i*k+j], s[j])) - } - } - - // dkPKE ← ByteEncode₁₂(s) - // ekPKE ← ByteEncode₁₂(t) || ρ - // ek ← ekPKE - // dk ← dkPKE || ek || H(ek) || z - dkB := dk.dk[:0] - - for i := range s { - dkB = polyByteEncode(dkB, s[i]) - } - - for i := range t { - dkB = polyByteEncode(dkB, t[i]) - } - dkB = append(dkB, ρ...) - - H := sha3.New256() - H.Write(dkB[decryptionKeySize:]) - dkB = H.Sum(dkB) - - dkB = append(dkB, z[:]...) - - if len(dkB) != len(dk.dk) { - panic("mlkem768: internal error: invalid decapsulation key size") - } - - return dk -} - -// Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from crypto/rand. -// If the encapsulation key is not valid, Encapsulate returns an error. -// -// The shared key must be kept secret. -func Encapsulate(encapsulationKey []byte) (ciphertext, sharedKey []byte, err error) { - // The actual logic is in a separate function to outline this allocation. - var cc [CiphertextSize]byte - return encapsulate(&cc, encapsulationKey) -} - -func encapsulate(cc *[CiphertextSize]byte, encapsulationKey []byte) (ciphertext, sharedKey []byte, err error) { - if len(encapsulationKey) != EncapsulationKeySize { - return nil, nil, errors.New("mlkem768: invalid encapsulation key length") - } - var m [messageSize]byte - if _, err := rand.Read(m[:]); err != nil { - return nil, nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - return kemEncaps(cc, encapsulationKey, &m) -} - -// kemEncaps generates a shared key and an associated ciphertext. -// -// It implements ML-KEM.Encaps according to FIPS 203 (DRAFT), Algorithm 16. -func kemEncaps(cc *[CiphertextSize]byte, ek []byte, m *[messageSize]byte) (c, K []byte, err error) { - if cc == nil { - cc = &[CiphertextSize]byte{} - } - - H := sha3.Sum256(ek[:]) - g := sha3.New512() - g.Write(m[:]) - g.Write(H[:]) - G := g.Sum(nil) - K, r := G[:SharedKeySize], G[SharedKeySize:] - var ex encryptionKey - if err := parseEK(&ex, ek[:]); err != nil { - return nil, nil, err - } - c = pkeEncrypt(cc, &ex, m, r) - return c, K, nil -} - -// parseEK parses an encryption key from its encoded form. -// -// It implements the initial stages of K-PKE.Encrypt according to FIPS 203 -// (DRAFT), Algorithm 13. -func parseEK(ex *encryptionKey, ekPKE []byte) error { - if len(ekPKE) != encryptionKeySize { - return errors.New("mlkem768: invalid encryption key length") - } - - for i := range ex.t { - var err error - ex.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) - if err != nil { - return err - } - ekPKE = ekPKE[encodingSize12:] - } - ρ := ekPKE - - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - // See the note in pkeKeyGen about the order of the indices being - // consistent with Kyber round 3. - ex.A[i*k+j] = sampleNTT(ρ, j, i) - } - } - - return nil -} - -// pkeEncrypt encrypt a plaintext message. -// -// It implements K-PKE.Encrypt according to FIPS 203 (DRAFT), Algorithm 13, -// although the computation of t and AT is done in parseEK. -func pkeEncrypt(cc *[CiphertextSize]byte, ex *encryptionKey, m *[messageSize]byte, rnd []byte) []byte { - var N byte - r, e1 := make([]nttElement, k), make([]ringElement, k) - for i := range r { - r[i] = ntt(samplePolyCBD(rnd, N)) - N++ - } - for i := range e1 { - e1[i] = samplePolyCBD(rnd, N) - N++ - } - e2 := samplePolyCBD(rnd, N) - - u := make([]ringElement, k) // NTT⁻¹(AT ◦ r) + e1 - for i := range u { - u[i] = e1[i] - for j := range r { - // Note that i and j are inverted, as we need the transposed of A. - u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.A[j*k+i], r[j]))) - } - } - - μ := ringDecodeAndDecompress1(m) - - var vNTT nttElement // t⊺ ◦ r - for i := range ex.t { - vNTT = polyAdd(vNTT, nttMul(ex.t[i], r[i])) - } - v := polyAdd(polyAdd(inverseNTT(vNTT), e2), μ) - - c := cc[:0] - for _, f := range u { - c = ringCompressAndEncode10(c, f) - } - c = ringCompressAndEncode4(c, v) - - return c -} - -// Decapsulate generates a shared key from a ciphertext and a decapsulation key. -// If the ciphertext is not valid, Decapsulate returns an error. -// -// The shared key must be kept secret. -func Decapsulate(dk *DecapsulationKey, ciphertext []byte) (sharedKey []byte, err error) { - if len(ciphertext) != CiphertextSize { - return nil, errors.New("mlkem768: invalid ciphertext length") - } - c := (*[CiphertextSize]byte)(ciphertext) - return kemDecaps(dk, c), nil -} - -// kemDecaps produces a shared key from a ciphertext. -// -// It implements ML-KEM.Decaps according to FIPS 203 (DRAFT), Algorithm 17. -func kemDecaps(dk *DecapsulationKey, c *[CiphertextSize]byte) (K []byte) { - h := dk.dk[decryptionKeySize+encryptionKeySize : decryptionKeySize+encryptionKeySize+32] - z := dk.dk[decryptionKeySize+encryptionKeySize+32:] - - m := pkeDecrypt(&dk.decryptionKey, c) - g := sha3.New512() - g.Write(m[:]) - g.Write(h) - G := g.Sum(nil) - Kprime, r := G[:SharedKeySize], G[SharedKeySize:] - J := sha3.NewShake256() - J.Write(z) - J.Write(c[:]) - Kout := make([]byte, SharedKeySize) - J.Read(Kout) - var cc [CiphertextSize]byte - c1 := pkeEncrypt(&cc, &dk.encryptionKey, (*[32]byte)(m), r) - - subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) - return Kout -} - -// parseDK parses a decryption key from its encoded form. -// -// It implements the computation of s from K-PKE.Decrypt according to FIPS 203 -// (DRAFT), Algorithm 14. -func parseDK(dx *decryptionKey, dkPKE []byte) error { - if len(dkPKE) != decryptionKeySize { - return errors.New("mlkem768: invalid decryption key length") - } - - for i := range dx.s { - f, err := polyByteDecode[nttElement](dkPKE[:encodingSize12]) - if err != nil { - return err - } - dx.s[i] = f - dkPKE = dkPKE[encodingSize12:] - } - - return nil -} - -// pkeDecrypt decrypts a ciphertext. -// -// It implements K-PKE.Decrypt according to FIPS 203 (DRAFT), Algorithm 14, -// although the computation of s is done in parseDK. -func pkeDecrypt(dx *decryptionKey, c *[CiphertextSize]byte) []byte { - u := make([]ringElement, k) - for i := range u { - b := (*[encodingSize10]byte)(c[encodingSize10*i : encodingSize10*(i+1)]) - u[i] = ringDecodeAndDecompress10(b) - } - - b := (*[encodingSize4]byte)(c[encodingSize10*k:]) - v := ringDecodeAndDecompress4(b) - - var mask nttElement // s⊺ ◦ NTT(u) - for i := range dx.s { - mask = polyAdd(mask, nttMul(dx.s[i], ntt(u[i]))) - } - w := polySub(v, inverseNTT(mask)) - - return ringCompressAndEncode1(nil, w) -} - -// fieldElement is an integer modulo q, an element of ℤ_q. It is always reduced. -type fieldElement uint16 - -// fieldCheckReduced checks that a value a is < q. -func fieldCheckReduced(a uint16) (fieldElement, error) { - if a >= q { - return 0, errors.New("unreduced field element") - } - return fieldElement(a), nil -} - -// fieldReduceOnce reduces a value a < 2q. -func fieldReduceOnce(a uint16) fieldElement { - x := a - q - // If x underflowed, then x >= 2¹⁶ - q > 2¹⁵, so the top bit is set. - x += (x >> 15) * q - return fieldElement(x) -} - -func fieldAdd(a, b fieldElement) fieldElement { - x := uint16(a + b) - return fieldReduceOnce(x) -} - -func fieldSub(a, b fieldElement) fieldElement { - x := uint16(a - b + q) - return fieldReduceOnce(x) -} - -const ( - barrettMultiplier = 5039 // 2¹² * 2¹² / q - barrettShift = 24 // log₂(2¹² * 2¹²) -) - -// fieldReduce reduces a value a < 2q² using Barrett reduction, to avoid -// potentially variable-time division. -func fieldReduce(a uint32) fieldElement { - quotient := uint32((uint64(a) * barrettMultiplier) >> barrettShift) - return fieldReduceOnce(uint16(a - quotient*q)) -} - -func fieldMul(a, b fieldElement) fieldElement { - x := uint32(a) * uint32(b) - return fieldReduce(x) -} - -// fieldMulSub returns a * (b - c). This operation is fused to save a -// fieldReduceOnce after the subtraction. -func fieldMulSub(a, b, c fieldElement) fieldElement { - x := uint32(a) * uint32(b-c+q) - return fieldReduce(x) -} - -// fieldAddMul returns a * b + c * d. This operation is fused to save a -// fieldReduceOnce and a fieldReduce. -func fieldAddMul(a, b, c, d fieldElement) fieldElement { - x := uint32(a) * uint32(b) - x += uint32(c) * uint32(d) - return fieldReduce(x) -} - -// compress maps a field element uniformly to the range 0 to 2ᵈ-1, according to -// FIPS 203 (DRAFT), Definition 4.5. -func compress(x fieldElement, d uint8) uint16 { - // We want to compute (x * 2ᵈ) / q, rounded to nearest integer, with 1/2 - // rounding up (see FIPS 203 (DRAFT), Section 2.3). - - // Barrett reduction produces a quotient and a remainder in the range [0, 2q), - // such that dividend = quotient * q + remainder. - dividend := uint32(x) << d // x * 2ᵈ - quotient := uint32(uint64(dividend) * barrettMultiplier >> barrettShift) - remainder := dividend - quotient*q - - // Since the remainder is in the range [0, 2q), not [0, q), we need to - // portion it into three spans for rounding. - // - // [ 0, q/2 ) -> round to 0 - // [ q/2, q + q/2 ) -> round to 1 - // [ q + q/2, 2q ) -> round to 2 - // - // We can convert that to the following logic: add 1 if remainder > q/2, - // then add 1 again if remainder > q + q/2. - // - // Note that if remainder > x, then ⌊x⌋ - remainder underflows, and the top - // bit of the difference will be set. - quotient += (q/2 - remainder) >> 31 & 1 - quotient += (q + q/2 - remainder) >> 31 & 1 - - // quotient might have overflowed at this point, so reduce it by masking. - var mask uint32 = (1 << d) - 1 - return uint16(quotient & mask) -} - -// decompress maps a number x between 0 and 2ᵈ-1 uniformly to the full range of -// field elements, according to FIPS 203 (DRAFT), Definition 4.6. -func decompress(y uint16, d uint8) fieldElement { - // We want to compute (y * q) / 2ᵈ, rounded to nearest integer, with 1/2 - // rounding up (see FIPS 203 (DRAFT), Section 2.3). - - dividend := uint32(y) * q - quotient := dividend >> d // (y * q) / 2ᵈ - - // The d'th least-significant bit of the dividend (the most significant bit - // of the remainder) is 1 for the top half of the values that divide to the - // same quotient, which are the ones that round up. - quotient += dividend >> (d - 1) & 1 - - // quotient is at most (2¹¹-1) * q / 2¹¹ + 1 = 3328, so it didn't overflow. - return fieldElement(quotient) -} - -// ringElement is a polynomial, an element of R_q, represented as an array -// according to FIPS 203 (DRAFT), Section 2.4. -type ringElement [n]fieldElement - -// polyAdd adds two ringElements or nttElements. -func polyAdd[T ~[n]fieldElement](a, b T) (s T) { - for i := range s { - s[i] = fieldAdd(a[i], b[i]) - } - return s -} - -// polySub subtracts two ringElements or nttElements. -func polySub[T ~[n]fieldElement](a, b T) (s T) { - for i := range s { - s[i] = fieldSub(a[i], b[i]) - } - return s -} - -// polyByteEncode appends the 384-byte encoding of f to b. -// -// It implements ByteEncode₁₂, according to FIPS 203 (DRAFT), Algorithm 4. -func polyByteEncode[T ~[n]fieldElement](b []byte, f T) []byte { - out, B := sliceForAppend(b, encodingSize12) - for i := 0; i < n; i += 2 { - x := uint32(f[i]) | uint32(f[i+1])<<12 - B[0] = uint8(x) - B[1] = uint8(x >> 8) - B[2] = uint8(x >> 16) - B = B[3:] - } - return out -} - -// polyByteDecode decodes the 384-byte encoding of a polynomial, checking that -// all the coefficients are properly reduced. This achieves the "Modulus check" -// step of ML-KEM Encapsulation Input Validation. -// -// polyByteDecode is also used in ML-KEM Decapsulation, where the input -// validation is not required, but implicitly allowed by the specification. -// -// It implements ByteDecode₁₂, according to FIPS 203 (DRAFT), Algorithm 5. -func polyByteDecode[T ~[n]fieldElement](b []byte) (T, error) { - if len(b) != encodingSize12 { - return T{}, errors.New("mlkem768: invalid encoding length") - } - var f T - for i := 0; i < n; i += 2 { - d := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 - const mask12 = 0b1111_1111_1111 - var err error - if f[i], err = fieldCheckReduced(uint16(d & mask12)); err != nil { - return T{}, errors.New("mlkem768: invalid polynomial encoding") - } - if f[i+1], err = fieldCheckReduced(uint16(d >> 12)); err != nil { - return T{}, errors.New("mlkem768: invalid polynomial encoding") - } - b = b[3:] - } - return f, nil -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// ringCompressAndEncode1 appends a 32-byte encoding of a ring element to s, -// compressing one coefficients per bit. -// -// It implements Compress₁, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₁, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode1(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize1) - for i := range b { - b[i] = 0 - } - for i := range f { - b[i/8] |= uint8(compress(f[i], 1) << (i % 8)) - } - return s -} - -// ringDecodeAndDecompress1 decodes a 32-byte slice to a ring element where each -// bit is mapped to 0 or ⌈q/2⌋. -// -// It implements ByteDecode₁, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₁, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress1(b *[encodingSize1]byte) ringElement { - var f ringElement - for i := range f { - b_i := b[i/8] >> (i % 8) & 1 - const halfQ = (q + 1) / 2 // ⌈q/2⌋, rounded up per FIPS 203 (DRAFT), Section 2.3 - f[i] = fieldElement(b_i) * halfQ // 0 decompresses to 0, and 1 to ⌈q/2⌋ - } - return f -} - -// ringCompressAndEncode4 appends a 128-byte encoding of a ring element to s, -// compressing two coefficients per byte. -// -// It implements Compress₄, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₄, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode4(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize4) - for i := 0; i < n; i += 2 { - b[i/2] = uint8(compress(f[i], 4) | compress(f[i+1], 4)<<4) - } - return s -} - -// ringDecodeAndDecompress4 decodes a 128-byte encoding of a ring element where -// each four bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₄, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₄, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress4(b *[encodingSize4]byte) ringElement { - var f ringElement - for i := 0; i < n; i += 2 { - f[i] = fieldElement(decompress(uint16(b[i/2]&0b1111), 4)) - f[i+1] = fieldElement(decompress(uint16(b[i/2]>>4), 4)) - } - return f -} - -// ringCompressAndEncode10 appends a 320-byte encoding of a ring element to s, -// compressing four coefficients per five bytes. -// -// It implements Compress₁₀, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₁₀, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode10(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize10) - for i := 0; i < n; i += 4 { - var x uint64 - x |= uint64(compress(f[i+0], 10)) - x |= uint64(compress(f[i+1], 10)) << 10 - x |= uint64(compress(f[i+2], 10)) << 20 - x |= uint64(compress(f[i+3], 10)) << 30 - b[0] = uint8(x) - b[1] = uint8(x >> 8) - b[2] = uint8(x >> 16) - b[3] = uint8(x >> 24) - b[4] = uint8(x >> 32) - b = b[5:] - } - return s -} - -// ringDecodeAndDecompress10 decodes a 320-byte encoding of a ring element where -// each ten bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₁₀, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₁₀, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress10(bb *[encodingSize10]byte) ringElement { - b := bb[:] - var f ringElement - for i := 0; i < n; i += 4 { - x := uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 - b = b[5:] - f[i] = fieldElement(decompress(uint16(x>>0&0b11_1111_1111), 10)) - f[i+1] = fieldElement(decompress(uint16(x>>10&0b11_1111_1111), 10)) - f[i+2] = fieldElement(decompress(uint16(x>>20&0b11_1111_1111), 10)) - f[i+3] = fieldElement(decompress(uint16(x>>30&0b11_1111_1111), 10)) - } - return f -} - -// samplePolyCBD draws a ringElement from the special Dη distribution given a -// stream of random bytes generated by the PRF function, according to FIPS 203 -// (DRAFT), Algorithm 7 and Definition 4.1. -func samplePolyCBD(s []byte, b byte) ringElement { - prf := sha3.NewShake256() - prf.Write(s) - prf.Write([]byte{b}) - B := make([]byte, 128) - prf.Read(B) - - // SamplePolyCBD simply draws four (2η) bits for each coefficient, and adds - // the first two and subtracts the last two. - - var f ringElement - for i := 0; i < n; i += 2 { - b := B[i/2] - b_7, b_6, b_5, b_4 := b>>7, b>>6&1, b>>5&1, b>>4&1 - b_3, b_2, b_1, b_0 := b>>3&1, b>>2&1, b>>1&1, b&1 - f[i] = fieldSub(fieldElement(b_0+b_1), fieldElement(b_2+b_3)) - f[i+1] = fieldSub(fieldElement(b_4+b_5), fieldElement(b_6+b_7)) - } - return f -} - -// nttElement is an NTT representation, an element of T_q, represented as an -// array according to FIPS 203 (DRAFT), Section 2.4. -type nttElement [n]fieldElement - -// gammas are the values ζ^2BitRev7(i)+1 mod q for each index i. -var gammas = [128]fieldElement{17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, 2110, 1219, 2935, 394, 885, 2444, 2154, 1175} - -// nttMul multiplies two nttElements. -// -// It implements MultiplyNTTs, according to FIPS 203 (DRAFT), Algorithm 10. -func nttMul(f, g nttElement) nttElement { - var h nttElement - // We use i += 2 for bounds check elimination. See https://go.dev/issue/66826. - for i := 0; i < 256; i += 2 { - a0, a1 := f[i], f[i+1] - b0, b1 := g[i], g[i+1] - h[i] = fieldAddMul(a0, b0, fieldMul(a1, b1), gammas[i/2]) - h[i+1] = fieldAddMul(a0, b1, a1, b0) - } - return h -} - -// zetas are the values ζ^BitRev7(k) mod q for each index k. -var zetas = [128]fieldElement{1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154} - -// ntt maps a ringElement to its nttElement representation. -// -// It implements NTT, according to FIPS 203 (DRAFT), Algorithm 8. -func ntt(f ringElement) nttElement { - k := 1 - for len := 128; len >= 2; len /= 2 { - for start := 0; start < 256; start += 2 * len { - zeta := zetas[k] - k++ - // Bounds check elimination hint. - f, flen := f[start:start+len], f[start+len:start+len+len] - for j := 0; j < len; j++ { - t := fieldMul(zeta, flen[j]) - flen[j] = fieldSub(f[j], t) - f[j] = fieldAdd(f[j], t) - } - } - } - return nttElement(f) -} - -// inverseNTT maps a nttElement back to the ringElement it represents. -// -// It implements NTT⁻¹, according to FIPS 203 (DRAFT), Algorithm 9. -func inverseNTT(f nttElement) ringElement { - k := 127 - for len := 2; len <= 128; len *= 2 { - for start := 0; start < 256; start += 2 * len { - zeta := zetas[k] - k-- - // Bounds check elimination hint. - f, flen := f[start:start+len], f[start+len:start+len+len] - for j := 0; j < len; j++ { - t := f[j] - f[j] = fieldAdd(t, flen[j]) - flen[j] = fieldMulSub(zeta, flen[j], t) - } - } - } - for i := range f { - f[i] = fieldMul(f[i], 3303) // 3303 = 128⁻¹ mod q - } - return ringElement(f) -} - -// sampleNTT draws a uniformly random nttElement from a stream of uniformly -// random bytes generated by the XOF function, according to FIPS 203 (DRAFT), -// Algorithm 6 and Definition 4.2. -func sampleNTT(rho []byte, ii, jj byte) nttElement { - B := sha3.NewShake128() - B.Write(rho) - B.Write([]byte{ii, jj}) - - // SampleNTT essentially draws 12 bits at a time from r, interprets them in - // little-endian, and rejects values higher than q, until it drew 256 - // values. (The rejection rate is approximately 19%.) - // - // To do this from a bytes stream, it draws three bytes at a time, and - // splits them into two uint16 appropriately masked. - // - // r₀ r₁ r₂ - // |- - - - - - - -|- - - - - - - -|- - - - - - - -| - // - // Uint16(r₀ || r₁) - // |- - - - - - - - - - - - - - - -| - // |- - - - - - - - - - - -| - // d₁ - // - // Uint16(r₁ || r₂) - // |- - - - - - - - - - - - - - - -| - // |- - - - - - - - - - - -| - // d₂ - // - // Note that in little-endian, the rightmost bits are the most significant - // bits (dropped with a mask) and the leftmost bits are the least - // significant bits (dropped with a right shift). - - var a nttElement - var j int // index into a - var buf [24]byte // buffered reads from B - off := len(buf) // index into buf, starts in a "buffer fully consumed" state - for { - if off >= len(buf) { - B.Read(buf[:]) - off = 0 - } - d1 := binary.LittleEndian.Uint16(buf[off:]) & 0b1111_1111_1111 - d2 := binary.LittleEndian.Uint16(buf[off+1:]) >> 4 - off += 3 - if d1 < q { - a[j] = fieldElement(d1) - j++ - } - if j >= len(a) { - break - } - if d2 < q { - a[j] = fieldElement(d2) - j++ - } - if j >= len(a) { - break - } - } - return a -} diff --git a/notboring.go b/notboring.go deleted file mode 100644 index 5fe954f..0000000 --- a/notboring.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2022 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-Go file. - -//go:build !boringcrypto - -package reality - -func needFIPS() bool { return false } diff --git a/prf.go b/prf.go index 425216a..290c4ca 100644 --- a/prf.go +++ b/prf.go @@ -14,8 +14,12 @@ import ( "errors" "fmt" "hash" + + "github.com/xtls/reality/tls12" ) +type prfFunc func(secret []byte, label string, seed []byte, keyLen int) []byte + // 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] @@ -45,7 +49,8 @@ func pHash(result, secret, seed []byte, hash func() hash.Hash) { } // prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, Section 5. -func prf10(result, secret, label, seed []byte) { +func prf10(secret []byte, label string, seed []byte, keyLen int) []byte { + result := make([]byte, keyLen) hashSHA1 := sha1.New hashMD5 := md5.New @@ -61,16 +66,14 @@ func prf10(result, secret, label, seed []byte) { for i, b := range result2 { result[i] ^= b } + + return result } // 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) +func prf12(hashFunc func() hash.Hash) prfFunc { + return func(secret []byte, label string, seed []byte, keyLen int) []byte { + return tls12.PRF(hashFunc, secret, label, seed, keyLen) } } @@ -79,13 +82,13 @@ const ( 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") +const masterSecretLabel = "master secret" +const extendedMasterSecretLabel = "extended master secret" +const keyExpansionLabel = "key expansion" +const clientFinishedLabel = "client finished" +const serverFinishedLabel = "server finished" -func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) { +func prfAndHashForVersion(version uint16, suite *cipherSuite) (prfFunc, crypto.Hash) { switch version { case VersionTLS10, VersionTLS11: return prf10, crypto.Hash(0) @@ -99,7 +102,7 @@ func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secr } } -func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) { +func prfForVersion(version uint16, suite *cipherSuite) prfFunc { prf, _ := prfAndHashForVersion(version, suite) return prf } @@ -111,17 +114,19 @@ func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecr seed = append(seed, clientRandom...) seed = append(seed, serverRandom...) - masterSecret := make([]byte, masterSecretLength) - prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed) - return masterSecret + return prfForVersion(version, suite)(preMasterSecret, masterSecretLabel, seed, masterSecretLength) } // 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 + prf, hash := prfAndHashForVersion(version, suite) + if version == VersionTLS12 { + // Use the FIPS 140-3 module only for TLS 1.2 with EMS, which is the + // only TLS 1.0-1.2 approved mode per IG D.Q. + return tls12.MasterSecret(hash.New, preMasterSecret, transcript) + } + return prf(preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength) } // keysFromMasterSecret generates the connection keys from the master @@ -133,8 +138,7 @@ func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clie seed = append(seed, clientRandom...) n := 2*macLen + 2*keyLen + 2*ivLen - keyMaterial := make([]byte, n) - prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed) + keyMaterial := prfForVersion(version, suite)(masterSecret, keyExpansionLabel, seed, n) clientMAC = keyMaterial[:macLen] keyMaterial = keyMaterial[macLen:] serverMAC = keyMaterial[:macLen] @@ -177,7 +181,7 @@ type finishedHash struct { buffer []byte version uint16 - prf func(result, secret, label, seed []byte) + prf prfFunc } func (h *finishedHash) Write(msg []byte) (n int, err error) { @@ -209,17 +213,13 @@ func (h finishedHash) Sum() []byte { // 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 + return h.prf(masterSecret, clientFinishedLabel, h.Sum(), finishedVerifyLength) } // 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 + return h.prf(masterSecret, serverFinishedLabel, h.Sum(), finishedVerifyLength) } // hashForClientCertificate returns the handshake messages so far, pre-hashed if @@ -292,8 +292,6 @@ func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clien seed = append(seed, context...) } - keyMaterial := make([]byte, length) - prfForVersion(version, suite)(keyMaterial, masterSecret, []byte(label), seed) - return keyMaterial, nil + return prfForVersion(version, suite)(masterSecret, label, seed, length), nil } } diff --git a/ticket.go b/ticket.go index 37ac35b..6c2bff8 100644 --- a/ticket.go +++ b/ticket.go @@ -44,20 +44,21 @@ type SessionState struct { // case 0: Empty; // case 1: opaque alpn<1..2^8-1>; // }; - // select (SessionState.type) { - // case server: Empty; - // case client: struct { - // select (SessionState.version) { - // case VersionTLS10..VersionTLS12: Empty; - // case VersionTLS13: struct { - // uint64 use_by; - // uint32 age_add; - // }; + // select (SessionState.version) { + // case VersionTLS10..VersionTLS12: uint16 curve_id; + // case VersionTLS13: select (SessionState.type) { + // case server: Empty; + // case client: struct { + // uint64 use_by; + // uint32 age_add; // }; // }; // }; // } SessionState; // + // The format can be extended backwards-compatibly by adding new fields at + // the end. Otherwise, a new SessionStateType must be used, as different Go + // versions may share the same session ticket encryption key. // Extra is ignored by crypto/tls, but is encoded by [SessionState.Bytes] // and parsed by [ParseSessionState]. @@ -97,6 +98,9 @@ type SessionState struct { useBy uint64 // seconds since UNIX epoch ageAdd uint32 ticket []byte + + // TLS 1.0–1.2 only fields. + curveID CurveID } // Bytes encodes the session, including any private fields, so that it can be @@ -161,11 +165,13 @@ func (s *SessionState) Bytes() ([]byte, error) { b.AddBytes([]byte(s.alpnProtocol)) }) } - if s.isClient { - if s.version >= VersionTLS13 { + if s.version >= VersionTLS13 { + if s.isClient { addUint64(&b, s.useBy) b.AddUint32(s.ageAdd) } + } else { + b.AddUint16(uint16(s.curveID)) } return b.Bytes() } @@ -187,7 +193,6 @@ func ParseSessionState(data []byte) (*SessionState, error) { 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) || @@ -205,6 +210,14 @@ func ParseSessionState(data []byte) (*SessionState, error) { } ss.Extra = append(ss.Extra, e) } + switch typ { + case 1: + ss.isClient = false + case 2: + ss.isClient = true + default: + return nil, errors.New("tls: unknown session encoding") + } switch extMasterSecret { case 0: ss.extMasterSecret = false @@ -229,6 +242,9 @@ func ParseSessionState(data []byte) (*SessionState, error) { ss.activeCertHandles = append(ss.activeCertHandles, c) ss.peerCertificates = append(ss.peerCertificates, c.cert) } + if ss.isClient && len(ss.peerCertificates) == 0 { + return nil, errors.New("tls: no server certificates in client session") + } ss.ocspResponse = cert.OCSPStaple ss.scts = cert.SignedCertificateTimestamps var chainList cryptobyte.String @@ -266,24 +282,16 @@ func ParseSessionState(data []byte) (*SessionState, error) { } ss.alpnProtocol = string(alpn) } - if isClient := typ == 2; !isClient { - if !s.Empty() { + if ss.version >= VersionTLS13 { + if ss.isClient { + if !s.ReadUint64(&ss.useBy) || !s.ReadUint32(&ss.ageAdd) { + return nil, errors.New("tls: invalid session encoding") + } + } + } else { + if !s.ReadUint16((*uint16)(&ss.curveID)) { 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 } @@ -303,6 +311,7 @@ func (c *Conn) sessionState() *SessionState { isClient: c.isClient, extMasterSecret: c.extMasterSecret, verifiedChains: c.verifiedChains, + curveID: c.curveID, } } diff --git a/tls.go b/tls.go index 24af6ec..2b47035 100644 --- a/tls.go +++ b/tls.go @@ -2,8 +2,23 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE-Go file. -// Server side implementation of REALITY protocol, a fork of package tls in Go 1.20. +// Server side implementation of REALITY protocol, a fork of package tls in latest Go. // For client side, please follow https://github.com/XTLS/Xray-core/blob/main/transport/internet/reality/reality.go. + +// Package tls partially implements TLS 1.2, as specified in RFC 5246, +// and TLS 1.3, as specified in RFC 8446. +// +// # FIPS 140-3 mode +// +// When the program is in [FIPS 140-3 mode], this package behaves as if only +// SP 800-140C and SP 800-140D approved protocol versions, cipher suites, +// signature algorithms, certificate public key types and sizes, and key +// exchange and derivation algorithms were implemented. Others are silently +// ignored and not negotiated, or rejected. This set may depend on the +// algorithms supported by the FIPS 140-3 Go Cryptographic Module selected with +// GOFIPS140, and may change across Go versions. +// +// [FIPS 140-3 mode]: https://go.dev/doc/security/fips140 package reality // BUG(agl): The crypto/tls package only implements some countermeasures @@ -38,6 +53,9 @@ import ( "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" + + "github.com/xtls/reality/gcm" + fipsaes "github.com/xtls/reality/aes" ) type CloseWriteConn interface { @@ -158,7 +176,7 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { go func() { for { mutex.Lock() - hs.clientHello, err = hs.c.readClientHello(context.Background()) // TODO: Change some rules in this function. + hs.clientHello, _, err = hs.c.readClientHello(context.Background()) // TODO: Change some rules in this function. if copying || err != nil || hs.c.vers != VersionTLS13 || !config.ServerNames[hs.clientHello.serverName] { break } @@ -173,9 +191,9 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { break } var aead cipher.AEAD - if aesgcmPreferred(hs.clientHello.cipherSuites) { + if isAESGCMPreferred(hs.clientHello.cipherSuites) { block, _ := aes.NewCipher(hs.c.AuthKey) - aead, _ = cipher.NewGCM(block) + aead, _ = gcm.NewGCMForTLS13(block.(*fipsaes.Block)) } else { aead, _ = chacha20poly1305.New(hs.c.AuthKey) } diff --git a/tls12/tls12.go b/tls12/tls12.go new file mode 100644 index 0000000..751e93f --- /dev/null +++ b/tls12/tls12.go @@ -0,0 +1,68 @@ +// Copyright 2024 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 tls12 + +import ( + "crypto/hmac" + "hash" +) + +// PRF implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, +// Section 5 and allowed by SP 800-135, Revision 1, Section 4.2.2. +func PRF[H hash.Hash](hash func() H, secret []byte, label string, seed []byte, keyLen int) []byte { + labelAndSeed := make([]byte, len(label)+len(seed)) + copy(labelAndSeed, label) + copy(labelAndSeed[len(label):], seed) + + result := make([]byte, keyLen) + pHash(hash, result, secret, labelAndSeed) + return result +} + +// pHash implements the P_hash function, as defined in RFC 5246, Section 5. +func pHash[H hash.Hash](hash1 func() H, result, secret, seed []byte) { + h := hmac.New(any(hash1).(func() hash.Hash), secret) + h.Write(seed) + a := h.Sum(nil) + + for len(result) > 0 { + h.Reset() + h.Write(a) + h.Write(seed) + b := h.Sum(nil) + n := copy(result, b) + result = result[n:] + + h.Reset() + h.Write(a) + a = h.Sum(nil) + } +} + +const masterSecretLength = 48 +const extendedMasterSecretLabel = "extended master secret" + +// MasterSecret implements the TLS 1.2 extended master secret derivation, as +// defined in RFC 7627 and allowed by SP 800-135, Revision 1, Section 4.2.2. +func MasterSecret[H hash.Hash](hash func() H, preMasterSecret, transcript []byte) []byte { + // "The TLS 1.2 KDF is an approved KDF when the following conditions are + // satisfied: [...] (3) P_HASH uses either SHA-256, SHA-384 or SHA-512." + //h := hash() + hash() + // switch any(h).(type) { + // case *sha256.Digest: + // if h.Size() != 32 { + // fips140.RecordNonApproved() + // } + // case *sha512.Digest: + // if h.Size() != 46 && h.Size() != 64 { + // fips140.RecordNonApproved() + // } + // default: + // fips140.RecordNonApproved() + // } + + return PRF(hash, preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength) +} \ No newline at end of file diff --git a/tls13/tls13.go b/tls13/tls13.go new file mode 100644 index 0000000..f2f0f9d --- /dev/null +++ b/tls13/tls13.go @@ -0,0 +1,180 @@ +// Copyright 2024 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 tls13 implements the TLS 1.3 Key Schedule as specified in RFC 8446, +// Section 7.1 and allowed by FIPS 140-3 IG 2.4.B Resolution 7. +package tls13 + +import ( + "crypto/hkdf" + "encoding/binary" + "hash" +) + +// We don't set the service indicator in this package but we delegate that to +// the underlying functions because the TLS 1.3 KDF does not have a standard of +// its own. + +// ExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. +func ExpandLabel[H hash.Hash](hash func() H, secret []byte, label string, context []byte, length int) []byte { + if len("tls13 ")+len(label) > 255 || len(context) > 255 { + // It should be impossible for this to panic: labels are fixed strings, + // and context is either a fixed-length computed hash, or parsed from a + // field which has the same length limitation. + // + // Another reasonable approach might be to return a randomized slice if + // we encounter an error, which would break the connection, but avoid + // panicking. This would perhaps be safer but significantly more + // confusing to users. + panic("tls13: label or context too long") + } + hkdfLabel := make([]byte, 0, 2+1+len("tls13 ")+len(label)+1+len(context)) + hkdfLabel = binary.BigEndian.AppendUint16(hkdfLabel, uint16(length)) + hkdfLabel = append(hkdfLabel, byte(len("tls13 ")+len(label))) + hkdfLabel = append(hkdfLabel, "tls13 "...) + hkdfLabel = append(hkdfLabel, label...) + hkdfLabel = append(hkdfLabel, byte(len(context))) + hkdfLabel = append(hkdfLabel, context...) + b, _ := hkdf.Expand(hash, secret, string(hkdfLabel), length) + return b +} + +func extract[H hash.Hash](hash func() H, newSecret, currentSecret []byte) []byte { + if newSecret == nil { + newSecret = make([]byte, hash().Size()) + } + b, _ := hkdf.Extract(hash, newSecret, currentSecret) + return b +} + +func deriveSecret[H hash.Hash](hash func() H, secret []byte, label string, transcript hash.Hash) []byte { + if transcript == nil { + transcript = hash() + } + return ExpandLabel(hash, secret, label, transcript.Sum(nil), transcript.Size()) +} + +const ( + resumptionBinderLabel = "res binder" + clientEarlyTrafficLabel = "c e traffic" + clientHandshakeTrafficLabel = "c hs traffic" + serverHandshakeTrafficLabel = "s hs traffic" + clientApplicationTrafficLabel = "c ap traffic" + serverApplicationTrafficLabel = "s ap traffic" + earlyExporterLabel = "e exp master" + exporterLabel = "exp master" + resumptionLabel = "res master" +) + +type EarlySecret struct { + secret []byte + hash func() hash.Hash +} + +func NewEarlySecret[H hash.Hash](h func() H, psk []byte) *EarlySecret { + return &EarlySecret{ + secret: extract(h, psk, nil), + hash: func() hash.Hash { return h() }, + } +} + +func (s *EarlySecret) ResumptionBinderKey() []byte { + return deriveSecret(s.hash, s.secret, resumptionBinderLabel, nil) +} + +// ClientEarlyTrafficSecret derives the client_early_traffic_secret from the +// early secret and the transcript up to the ClientHello. +func (s *EarlySecret) ClientEarlyTrafficSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript) +} + +type HandshakeSecret struct { + secret []byte + hash func() hash.Hash +} + +func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret { + derived := deriveSecret(s.hash, s.secret, "derived", nil) + return &HandshakeSecret{ + secret: extract(s.hash, sharedSecret, derived), + hash: s.hash, + } +} + +// ClientHandshakeTrafficSecret derives the client_handshake_traffic_secret from +// the handshake secret and the transcript up to the ServerHello. +func (s *HandshakeSecret) ClientHandshakeTrafficSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientHandshakeTrafficLabel, transcript) +} + +// ServerHandshakeTrafficSecret derives the server_handshake_traffic_secret from +// the handshake secret and the transcript up to the ServerHello. +func (s *HandshakeSecret) ServerHandshakeTrafficSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript) +} + +type MasterSecret struct { + secret []byte + hash func() hash.Hash +} + +func (s *HandshakeSecret) MasterSecret() *MasterSecret { + derived := deriveSecret(s.hash, s.secret, "derived", nil) + return &MasterSecret{ + secret: extract(s.hash, nil, derived), + hash: s.hash, + } +} + +// ClientApplicationTrafficSecret derives the client_application_traffic_secret_0 +// from the master secret and the transcript up to the server Finished. +func (s *MasterSecret) ClientApplicationTrafficSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientApplicationTrafficLabel, transcript) +} + +// ServerApplicationTrafficSecret derives the server_application_traffic_secret_0 +// from the master secret and the transcript up to the server Finished. +func (s *MasterSecret) ServerApplicationTrafficSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, serverApplicationTrafficLabel, transcript) +} + +// ResumptionMasterSecret derives the resumption_master_secret from the master secret +// and the transcript up to the client Finished. +func (s *MasterSecret) ResumptionMasterSecret(transcript hash.Hash) []byte { + return deriveSecret(s.hash, s.secret, resumptionLabel, transcript) +} + +type ExporterMasterSecret struct { + secret []byte + hash func() hash.Hash +} + +// ExporterMasterSecret derives the exporter_master_secret from the master secret +// and the transcript up to the server Finished. +func (s *MasterSecret) ExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret { + return &ExporterMasterSecret{ + secret: deriveSecret(s.hash, s.secret, exporterLabel, transcript), + hash: s.hash, + } +} + +// EarlyExporterMasterSecret derives the exporter_master_secret from the early secret +// and the transcript up to the ClientHello. +func (s *EarlySecret) EarlyExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret { + return &ExporterMasterSecret{ + secret: deriveSecret(s.hash, s.secret, earlyExporterLabel, transcript), + hash: s.hash, + } +} + +func (s *ExporterMasterSecret) Exporter(label string, context []byte, length int) []byte { + secret := deriveSecret(s.hash, s.secret, label, nil) + h := s.hash() + h.Write(context) + return ExpandLabel(s.hash, secret, "exporter", h.Sum(nil), length) +} + +func TestingOnlyExporterSecret(s *ExporterMasterSecret) []byte { + return s.secret +} \ No newline at end of file