From 83229a45659582b8f3c1fdfd961e79d745390751 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 3 May 2025 12:55:49 -0400 Subject: [PATCH 01/45] crypto/internal/fips/aes: move GCM to its own package For #69536 Change-Id: Idcef2411c1fd4da302412609f28e438839e8aa4b Reviewed-on: https://go-review.googlesource.com/c/go/+/624736 LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Reviewed-by: Dmitri Shuralyov Auto-Submit: Filippo Valsorda --- cipher_suites.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cipher_suites.go b/cipher_suites.go index 3ce93a8..903272e 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -364,15 +364,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{ From 320c4a644850203d7f46916e5b5e33faf9c3763e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 09:32:40 -0400 Subject: [PATCH 02/45] crypto/internal/fips/aes/gcm: add GCMForTLS12 and GCMForTLS13 For #69536 Change-Id: I2d7b6e7b9932d0f0f582a5ab0bb871395dc2a1e8 Reviewed-on: https://go-review.googlesource.com/c/go/+/626675 LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Reviewed-by: Roland Shoemaker Reviewed-by: Dmitri Shuralyov Auto-Submit: Filippo Valsorda --- aes/aes.go | 133 +++++++++++++++ aes/aes_generic.go | 181 ++++++++++++++++++++ aes/aes_noasm.go | 24 +++ aes/const.go | 356 ++++++++++++++++++++++++++++++++++++++++ alias/alias.go | 30 ++++ byteorder/byteorder.go | 149 +++++++++++++++++ cipher_suites.go | 7 +- gcm/gcm.go | 145 ++++++++++++++++ gcm/gcm_generic.go | 105 ++++++++++++ gcm/gcm_noasm.go | 19 +++ gcm/gcm_nonces.go | 259 +++++++++++++++++++++++++++++ gcm/ghash.go | 163 ++++++++++++++++++ subtle/constant_time.go | 97 +++++++++++ subtle/xor.go | 30 ++++ subtle/xor_generic.go | 62 +++++++ tls.go | 5 +- 16 files changed, 1762 insertions(+), 3 deletions(-) create mode 100644 aes/aes.go create mode 100644 aes/aes_generic.go create mode 100644 aes/aes_noasm.go create mode 100644 aes/const.go create mode 100644 alias/alias.go create mode 100644 byteorder/byteorder.go create mode 100644 gcm/gcm.go create mode 100644 gcm/gcm_generic.go create mode 100644 gcm/gcm_noasm.go create mode 100644 gcm/gcm_nonces.go create mode 100644 gcm/ghash.go create mode 100644 subtle/constant_time.go create mode 100644 subtle/xor.go create mode 100644 subtle/xor_generic.go diff --git a/aes/aes.go b/aes/aes.go new file mode 100644 index 0000000..432251e --- /dev/null +++ b/aes/aes.go @@ -0,0 +1,133 @@ +// 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 ( + // "crypto/internal/fips140" + // "crypto/internal/fips140/alias" + "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..8a9a4d2 --- /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 "github.com/xtls/reality/byteorder" + +// 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 := byteorder.BEUint32(src[0:4]) + s1 := byteorder.BEUint32(src[4:8]) + s2 := byteorder.BEUint32(src[8:12]) + s3 := byteorder.BEUint32(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 + byteorder.BEPutUint32(dst[0:4], s0) + byteorder.BEPutUint32(dst[4:8], s1) + byteorder.BEPutUint32(dst[8:12], s2) + byteorder.BEPutUint32(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 := byteorder.BEUint32(src[0:4]) + s1 := byteorder.BEUint32(src[4:8]) + s2 := byteorder.BEUint32(src[8:12]) + s3 := byteorder.BEUint32(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 + byteorder.BEPutUint32(dst[0:4], s0) + byteorder.BEPutUint32(dst[4:8], s1) + byteorder.BEPutUint32(dst[8:12], s2) + byteorder.BEPutUint32(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] = byteorder.BEUint32(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/byteorder/byteorder.go b/byteorder/byteorder.go new file mode 100644 index 0000000..0af00a4 --- /dev/null +++ b/byteorder/byteorder.go @@ -0,0 +1,149 @@ +// 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 byteorder provides functions for decoding and encoding +// little and big endian integer types from/to byte slices. +package byteorder + +func LEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[0]) | uint16(b[1])<<8 +} + +func LEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func LEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +func LEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func LEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func LEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + ) +} + +func LEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func LEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func LEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56), + ) +} + +func BEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[1]) | uint16(b[0])<<8 +} + +func BEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func BEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v>>8), + byte(v), + ) +} + +func BEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func BEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func BEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func BEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func BEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func BEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v>>56), + byte(v>>48), + byte(v>>40), + byte(v>>32), + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} \ No newline at end of file diff --git a/cipher_suites.go b/cipher_suites.go index 903272e..68640e7 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 @@ -509,7 +512,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) } @@ -537,7 +540,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/gcm/gcm.go b/gcm/gcm.go new file mode 100644 index 0000000..c3d4220 --- /dev/null +++ b/gcm/gcm.go @@ -0,0 +1,145 @@ +// 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 ( + // "crypto/internal/fips140" + + "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..abb2d69 --- /dev/null +++ b/gcm/gcm_generic.go @@ -0,0 +1,105 @@ +// 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 ( + "github.com/xtls/reality/aes" + "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/subtle" +) + +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) + byteorder.BEPutUint64(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:] + byteorder.BEPutUint32(ctr, byteorder.BEUint32(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) + byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8) + byteorder.BEPutUint64(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..e6d0958 --- /dev/null +++ b/gcm/gcm_nonces.go @@ -0,0 +1,259 @@ +// 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/internal/fips140" + // "crypto/internal/fips140/drbg" + + "math" + + "github.com/xtls/reality/aes" + // "github.com/xtls/reality/alias" + "github.com/xtls/reality/byteorder" +) + +// 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 := byteorder.BEUint64(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 = byteorder.BEUint32(nonce[:4]) + } + if g.fixedName != byteorder.BEUint32(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 := byteorder.BEUint64(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 := byteorder.BEUint64(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 := byteorder.BEUint64(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..d1edc42 --- /dev/null +++ b/gcm/ghash.go @@ -0,0 +1,163 @@ +// 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/internal/fips140" + "github.com/xtls/reality/byteorder" +) + +// 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{ + byteorder.BEUint64(H[:8]), + byteorder.BEUint64(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) + } + + byteorder.BEPutUint64(out[:], y.low) + byteorder.BEPutUint64(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 ^= byteorder.BEUint64(blocks) + y.high ^= byteorder.BEUint64(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/subtle/constant_time.go b/subtle/constant_time.go new file mode 100644 index 0000000..b021da3 --- /dev/null +++ b/subtle/constant_time.go @@ -0,0 +1,97 @@ +// 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 subtle + +import ( + "math/bits" + + "github.com/xtls/reality/byteorder" +) + +// ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents +// and 0 otherwise. The time taken is a function of the length of the slices and +// is independent of the contents. If the lengths of x and y do not match it +// returns 0 immediately. +func ConstantTimeCompare(x, y []byte) int { + if len(x) != len(y) { + return 0 + } + + var v byte + + for i := 0; i < len(x); i++ { + v |= x[i] ^ y[i] + } + + return ConstantTimeByteEq(v, 0) +} + +// ConstantTimeLessOrEqBytes returns 1 if x <= y and 0 otherwise. The comparison +// is lexigraphical, or big-endian. The time taken is a function of the length of +// the slices and is independent of the contents. If the lengths of x and y do not +// match it returns 0 immediately. +func ConstantTimeLessOrEqBytes(x, y []byte) int { + if len(x) != len(y) { + return 0 + } + + // Do a constant time subtraction chain y - x. + // If there is no borrow at the end, then x <= y. + var b uint64 + for len(x) > 8 { + x0 := byteorder.BEUint64(x[len(x)-8:]) + y0 := byteorder.BEUint64(y[len(y)-8:]) + _, b = bits.Sub64(y0, x0, b) + x = x[:len(x)-8] + y = y[:len(y)-8] + } + if len(x) > 0 { + xb := make([]byte, 8) + yb := make([]byte, 8) + copy(xb[8-len(x):], x) + copy(yb[8-len(y):], y) + x0 := byteorder.BEUint64(xb) + y0 := byteorder.BEUint64(yb) + _, b = bits.Sub64(y0, x0, b) + } + return int(b ^ 1) +} + +// ConstantTimeSelect returns x if v == 1 and y if v == 0. +// Its behavior is undefined if v takes any other value. +func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y } + +// ConstantTimeByteEq returns 1 if x == y and 0 otherwise. +func ConstantTimeByteEq(x, y uint8) int { + return int((uint32(x^y) - 1) >> 31) +} + +// ConstantTimeEq returns 1 if x == y and 0 otherwise. +func ConstantTimeEq(x, y int32) int { + return int((uint64(uint32(x^y)) - 1) >> 63) +} + +// ConstantTimeCopy copies the contents of y into x (a slice of equal length) +// if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v +// takes any other value. +func ConstantTimeCopy(v int, x, y []byte) { + if len(x) != len(y) { + panic("subtle: slices have different lengths") + } + + xmask := byte(v - 1) + ymask := byte(^(v - 1)) + for i := 0; i < len(x); i++ { + x[i] = x[i]&xmask | y[i]&ymask + } +} + +// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. +// Its behavior is undefined if x or y are negative or > 2**31 - 1. +func ConstantTimeLessOrEq(x, y int) int { + x32 := int32(x) + y32 := int32(y) + return int(((x32 - y32 - 1) >> 31) & 1) +} \ No newline at end of file diff --git a/subtle/xor.go b/subtle/xor.go new file mode 100644 index 0000000..bb7ac02 --- /dev/null +++ b/subtle/xor.go @@ -0,0 +1,30 @@ +// 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 file. + +package subtle + +import "github.com/xtls/reality/alias" + +// XORBytes sets dst[i] = x[i] ^ y[i] for all i < n = min(len(x), len(y)), +// returning n, the number of bytes written to dst. +// +// If dst does not have length at least n, +// XORBytes panics without writing anything to dst. +// +// dst and x or y may overlap exactly or not at all, +// otherwise XORBytes may panic. +func XORBytes(dst, x, y []byte) int { + n := min(len(x), len(y)) + if n == 0 { + return 0 + } + if n > len(dst) { + panic("subtle.XORBytes: dst too short") + } + if alias.InexactOverlap(dst[:n], x[:n]) || alias.InexactOverlap(dst[:n], y[:n]) { + panic("subtle.XORBytes: invalid overlap") + } + xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific + return n +} \ No newline at end of file diff --git a/subtle/xor_generic.go b/subtle/xor_generic.go new file mode 100644 index 0000000..706e5af --- /dev/null +++ b/subtle/xor_generic.go @@ -0,0 +1,62 @@ +// 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 subtle + +import ( + "runtime" + "unsafe" +) + +const wordSize = unsafe.Sizeof(uintptr(0)) + +const supportsUnaligned = runtime.GOARCH == "386" || + runtime.GOARCH == "amd64" || + runtime.GOARCH == "ppc64" || + runtime.GOARCH == "ppc64le" || + runtime.GOARCH == "s390x" + +func xorBytes(dstb, xb, yb *byte, n int) { + // xorBytes assembly is written using pointers and n. Back to slices. + dst := unsafe.Slice(dstb, n) + x := unsafe.Slice(xb, n) + y := unsafe.Slice(yb, n) + + if supportsUnaligned || aligned(dstb, xb, yb) { + xorLoop(words(dst), words(x), words(y)) + if uintptr(n)%wordSize == 0 { + return + } + done := n &^ int(wordSize-1) + dst = dst[done:] + x = x[done:] + y = y[done:] + } + xorLoop(dst, x, y) +} + +// aligned reports whether dst, x, and y are all word-aligned pointers. +func aligned(dst, x, y *byte) bool { + return (uintptr(unsafe.Pointer(dst))|uintptr(unsafe.Pointer(x))|uintptr(unsafe.Pointer(y)))&(wordSize-1) == 0 +} + +// words returns a []uintptr pointing at the same data as x, +// with any trailing partial word removed. +func words(x []byte) []uintptr { + n := uintptr(len(x)) / wordSize + if n == 0 { + // Avoid creating a *uintptr that refers to data smaller than a uintptr; + // see issue 59334. + return nil + } + return unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), n) +} + +func xorLoop[T byte | uintptr](dst, x, y []T) { + x = x[:len(dst)] // remove bounds check in loop + y = y[:len(dst)] // remove bounds check in loop + for i := range dst { + dst[i] = x[i] ^ y[i] + } +} \ No newline at end of file diff --git a/tls.go b/tls.go index 24af6ec..b50b42a 100644 --- a/tls.go +++ b/tls.go @@ -38,6 +38,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 { @@ -175,7 +178,7 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { var aead cipher.AEAD if aesgcmPreferred(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) } From 4db1386622e9ee76a3bd0b160d39eefe0f29ca3a Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 14:17:23 -0400 Subject: [PATCH 03/45] crypto/internal/fips/tls13: implement TLS 1.3 KDF The new implementation encodes the key schedule into the type system, which is actually nicer than what we had before. For #69536 Change-Id: Iddab62c2aae40bc2425a155443576bb9b7aafe03 Reviewed-on: https://go-review.googlesource.com/c/go/+/626836 Reviewed-by: Russ Cox Reviewed-by: Roland Shoemaker Commit-Queue: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney --- fips140/hash.go | 32 +++ handshake_client.go | 9 +- handshake_client_tls13.go | 39 ++-- handshake_server_tls13.go | 38 ++-- hkdf/hkdf.go | 56 +++++ hmac/hmac.go | 172 ++++++++++++++ key_schedule.go | 84 +------ sha256/sha256.go | 231 +++++++++++++++++++ sha256/sha256block.go | 128 +++++++++++ sha256/sha256block_noasm.go | 9 + sha3/hashes.go | 59 +++++ sha3/keccakf.go | 434 ++++++++++++++++++++++++++++++++++++ sha3/sha3.go | 235 +++++++++++++++++++ sha3/sha3_noasm.go | 19 ++ sha512/sha512.go | 301 +++++++++++++++++++++++++ sha512/sha512block.go | 144 ++++++++++++ sha512/sha512block_noasm.go | 9 + tls13/tls13.go | 178 +++++++++++++++ 18 files changed, 2053 insertions(+), 124 deletions(-) create mode 100644 fips140/hash.go create mode 100644 hkdf/hkdf.go create mode 100644 hmac/hmac.go create mode 100644 sha256/sha256.go create mode 100644 sha256/sha256block.go create mode 100644 sha256/sha256block_noasm.go create mode 100644 sha3/hashes.go create mode 100644 sha3/keccakf.go create mode 100644 sha3/sha3.go create mode 100644 sha3/sha3_noasm.go create mode 100644 sha512/sha512.go create mode 100644 sha512/sha512block.go create mode 100644 sha512/sha512block_noasm.go create mode 100644 tls13/tls13.go diff --git a/fips140/hash.go b/fips140/hash.go new file mode 100644 index 0000000..482ddf1 --- /dev/null +++ b/fips140/hash.go @@ -0,0 +1,32 @@ +// 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 fips140 + +import "io" + +// Hash is the common interface implemented by all hash functions. It is a copy +// of [hash.Hash] from the standard library, to avoid depending on security +// definitions from outside of the module. +type Hash interface { + // Write (via the embedded io.Writer interface) adds more data to the + // running hash. It never returns an error. + io.Writer + + // Sum appends the current hash to b and returns the resulting slice. + // It does not change the underlying hash state. + Sum(b []byte) []byte + + // Reset resets the Hash to its initial state. + Reset() + + // Size returns the number of bytes Sum will return. + Size() int + + // BlockSize returns the hash's underlying block size. + // The Write method must be able to accept any amount + // of data, but it may operate more efficiently if all writes + // are a multiple of the block size. + BlockSize() int +} \ No newline at end of file diff --git a/handshake_client.go b/handshake_client.go index 0b1172a..fddee7a 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -24,6 +24,7 @@ import ( "github.com/xtls/reality/hpke" "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" ) type clientHandshakeState struct { @@ -325,7 +326,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 +385,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 } @@ -511,8 +512,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 diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 15efbd1..31509fa 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -17,6 +17,8 @@ import ( "time" "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" + "golang.org/x/crypto/hkdf" ) type clientHandshakeStateTLS13 struct { @@ -27,7 +29,7 @@ type clientHandshakeStateTLS13 struct { keyShareKeys *keySharePrivateKeys session *SessionState - earlySecret []byte + earlySecret *tls13.EarlySecret binderKey []byte certReq *certificateRequestMsgTLS13 @@ -35,7 +37,7 @@ type clientHandshakeStateTLS13 struct { sentDummyCCS bool suite *cipherSuiteTLS13 transcript hash.Hash - masterSecret []byte + masterSecret *tls13.MasterSecret trafficSecret []byte // client_application_traffic_secret_0 echContext *echContext } @@ -89,8 +91,8 @@ func (hs *clientHandshakeStateTLS13) handshake() error { 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), + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.echContext.innerHello.random, nil), "ech accept confirmation", confTranscript.Sum(nil), 8, @@ -266,8 +268,8 @@ 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), + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.echContext.innerHello.random, nil), "hrr ech accept confirmation", confTranscript.Sum(nil), 8, @@ -511,17 +513,14 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { 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 +542,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 } @@ -732,10 +730,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 +838,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 { @@ -887,7 +882,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_server_tls13.go b/handshake_server_tls13.go index 72ed000..f97381e 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -23,6 +23,7 @@ import ( "time" "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/tls13" ) // maxClientPSKIdentities is the number of client PSK identities the server will @@ -41,10 +42,10 @@ 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 @@ -435,8 +436,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 +465,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) } @@ -702,16 +703,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 { @@ -836,13 +834,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 { @@ -908,8 +903,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 +918,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/hkdf/hkdf.go b/hkdf/hkdf.go new file mode 100644 index 0000000..0143eb8 --- /dev/null +++ b/hkdf/hkdf.go @@ -0,0 +1,56 @@ +// 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 hkdf + +import ( + "github.com/xtls/reality/fips140" + "github.com/xtls/reality/hmac" +) + +func Extract[H fips140.Hash](h func() H, secret, salt []byte) []byte { + if len(secret) < 112/8 { + // fips140.RecordNonApproved() + } + if salt == nil { + salt = make([]byte, h().Size()) + } + extractor := hmac.New(h, salt) + hmac.MarkAsUsedInKDF(extractor) + extractor.Write(secret) + + return extractor.Sum(nil) +} + +func Expand[H fips140.Hash](h func() H, pseudorandomKey []byte, info string, keyLen int) []byte { + out := make([]byte, 0, keyLen) + expander := hmac.New(h, pseudorandomKey) + hmac.MarkAsUsedInKDF(expander) + var counter uint8 + var buf []byte + + for len(out) < keyLen { + counter++ + if counter == 0 { + panic("hkdf: counter overflow") + } + if counter > 1 { + expander.Reset() + } + expander.Write(buf) + expander.Write([]byte(info)) + expander.Write([]byte{counter}) + buf = expander.Sum(buf[:0]) + remain := keyLen - len(out) + remain = min(remain, len(buf)) + out = append(out, buf[:remain]...) + } + + return out +} + +func Key[H fips140.Hash](h func() H, secret, salt []byte, info string, keyLen int) []byte { + prk := Extract(h, secret, salt) + return Expand(h, prk, info, keyLen) +} \ No newline at end of file diff --git a/hmac/hmac.go b/hmac/hmac.go new file mode 100644 index 0000000..b4cc25e --- /dev/null +++ b/hmac/hmac.go @@ -0,0 +1,172 @@ +// 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 hmac implements HMAC according to [FIPS 198-1]. +// +// [FIPS 198-1]: https://doi.org/10.6028/NIST.FIPS.198-1 +package hmac + +import ( + "github.com/xtls/reality/fips140" + "github.com/xtls/reality/sha256" + "github.com/xtls/reality/sha3" + "github.com/xtls/reality/sha512" +) + +// key is zero padded to the block size of the hash function +// ipad = 0x36 byte repeated for key length +// opad = 0x5c byte repeated for key length +// hmac = H([key ^ opad] H([key ^ ipad] text)) + +// marshalable is the combination of encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler. Their method definitions are repeated here to +// avoid a dependency on the encoding package. +type marshalable interface { + MarshalBinary() ([]byte, error) + UnmarshalBinary([]byte) error +} + +type HMAC struct { + opad, ipad []byte + outer, inner fips140.Hash + + // If marshaled is true, then opad and ipad do not contain a padded + // copy of the key, but rather the marshaled state of outer/inner after + // opad/ipad has been fed into it. + marshaled bool + + // forHKDF and keyLen are stored to inform the service indicator decision. + forHKDF bool + keyLen int +} + +func (h *HMAC) Sum(in []byte) []byte { + // Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for + // legacy use (i.e. verification only) and we don't support that. However, + // HKDF uses the HMAC key for the salt, which is allowed to be shorter. + if h.keyLen < 112/8 && !h.forHKDF { + // fips140.RecordNonApproved() + } + switch h.inner.(type) { + case *sha256.Digest, *sha512.Digest, *sha3.Digest: + default: + // fips140.RecordNonApproved() + } + + origLen := len(in) + in = h.inner.Sum(in) + + if h.marshaled { + if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil { + panic(err) + } + } else { + h.outer.Reset() + h.outer.Write(h.opad) + } + h.outer.Write(in[origLen:]) + return h.outer.Sum(in[:origLen]) +} + +func (h *HMAC) Write(p []byte) (n int, err error) { + return h.inner.Write(p) +} + +func (h *HMAC) Size() int { return h.outer.Size() } +func (h *HMAC) BlockSize() int { return h.inner.BlockSize() } + +func (h *HMAC) Reset() { + if h.marshaled { + if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil { + panic(err) + } + return + } + + h.inner.Reset() + h.inner.Write(h.ipad) + + // If the underlying hash is marshalable, we can save some time by saving a + // copy of the hash state now, and restoring it on future calls to Reset and + // Sum instead of writing ipad/opad every time. + // + // We do this on Reset to avoid slowing down the common single-use case. + // + // This is allowed by FIPS 198-1, Section 6: "Conceptually, the intermediate + // results of the compression function on the B-byte blocks (K0 ⊕ ipad) and + // (K0 ⊕ opad) can be precomputed once, at the time of generation of the key + // K, or before its first use. These intermediate results can be stored and + // then used to initialize H each time that a message needs to be + // authenticated using the same key. [...] These stored intermediate values + // shall be treated and protected in the same manner as secret keys." + marshalableInner, innerOK := h.inner.(marshalable) + if !innerOK { + return + } + marshalableOuter, outerOK := h.outer.(marshalable) + if !outerOK { + return + } + + imarshal, err := marshalableInner.MarshalBinary() + if err != nil { + return + } + + h.outer.Reset() + h.outer.Write(h.opad) + omarshal, err := marshalableOuter.MarshalBinary() + if err != nil { + return + } + + // Marshaling succeeded; save the marshaled state for later + h.ipad = imarshal + h.opad = omarshal + h.marshaled = true +} + +// New returns a new HMAC hash using the given [fips140.Hash] type and key. +func New[H fips140.Hash](h func() H, key []byte) *HMAC { + hm := &HMAC{keyLen: len(key)} + hm.outer = h() + hm.inner = h() + unique := true + func() { + defer func() { + // The comparison might panic if the underlying types are not comparable. + _ = recover() + }() + if hm.outer == hm.inner { + unique = false + } + }() + if !unique { + panic("crypto/hmac: hash generation function does not produce unique values") + } + blocksize := hm.inner.BlockSize() + hm.ipad = make([]byte, blocksize) + hm.opad = make([]byte, blocksize) + if len(key) > blocksize { + // If key is too big, hash it. + hm.outer.Write(key) + key = hm.outer.Sum(nil) + } + copy(hm.ipad, key) + copy(hm.opad, key) + for i := range hm.ipad { + hm.ipad[i] ^= 0x36 + } + for i := range hm.opad { + hm.opad[i] ^= 0x5c + } + hm.inner.Write(hm.ipad) + + return hm +} + +// MarkAsUsedInKDF records that this HMAC instance is used as part of a KDF. +func MarkAsUsedInKDF(h *HMAC) { + h.forHKDF = true +} \ No newline at end of file diff --git a/key_schedule.go b/key_schedule.go index 63636b5..0210be9 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -8,93 +8,28 @@ import ( "crypto/ecdh" "crypto/hmac" "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 +37,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,13 +45,10 @@ 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 } } diff --git a/sha256/sha256.go b/sha256/sha256.go new file mode 100644 index 0000000..d0e15b7 --- /dev/null +++ b/sha256/sha256.go @@ -0,0 +1,231 @@ +// 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 sha256 implements the SHA-224 and SHA-256 hash algorithms as defined +// in FIPS 180-4. +package sha256 + +import ( + //"github.com/xtls/reality" + "github.com/xtls/reality/byteorder" + "errors" +) + +// The size of a SHA-256 checksum in bytes. +const size = 32 + +// The size of a SHA-224 checksum in bytes. +const size224 = 28 + +// The block size of SHA-256 and SHA-224 in bytes. +const blockSize = 64 + +const ( + chunk = 64 + init0 = 0x6A09E667 + init1 = 0xBB67AE85 + init2 = 0x3C6EF372 + init3 = 0xA54FF53A + init4 = 0x510E527F + init5 = 0x9B05688C + init6 = 0x1F83D9AB + init7 = 0x5BE0CD19 + init0_224 = 0xC1059ED8 + init1_224 = 0x367CD507 + init2_224 = 0x3070DD17 + init3_224 = 0xF70E5939 + init4_224 = 0xFFC00B31 + init5_224 = 0x68581511 + init6_224 = 0x64F98FA7 + init7_224 = 0xBEFA4FA4 +) + +// Digest is a SHA-224 or SHA-256 [hash.Hash] implementation. +type Digest struct { + h [8]uint32 + x [chunk]byte + nx int + len uint64 + is224 bool // mark if this digest is SHA-224 +} + +const ( + magic224 = "sha\x02" + magic256 = "sha\x03" + marshaledSize = len(magic256) + 8*4 + chunk + 8 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + if d.is224 { + b = append(b, magic224...) + } else { + b = append(b, magic256...) + } + b = byteorder.BEAppendUint32(b, d.h[0]) + b = byteorder.BEAppendUint32(b, d.h[1]) + b = byteorder.BEAppendUint32(b, d.h[2]) + b = byteorder.BEAppendUint32(b, d.h[3]) + b = byteorder.BEAppendUint32(b, d.h[4]) + b = byteorder.BEAppendUint32(b, d.h[5]) + b = byteorder.BEAppendUint32(b, d.h[6]) + b = byteorder.BEAppendUint32(b, d.h[7]) + b = append(b, d.x[:d.nx]...) + b = append(b, make([]byte, len(d.x)-d.nx)...) + b = byteorder.BEAppendUint64(b, d.len) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic224) || (d.is224 && string(b[:len(magic224)]) != magic224) || (!d.is224 && string(b[:len(magic256)]) != magic256) { + return errors.New("crypto/sha256: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/sha256: invalid hash state size") + } + b = b[len(magic224):] + b, d.h[0] = consumeUint32(b) + b, d.h[1] = consumeUint32(b) + b, d.h[2] = consumeUint32(b) + b, d.h[3] = consumeUint32(b) + b, d.h[4] = consumeUint32(b) + b, d.h[5] = consumeUint32(b) + b, d.h[6] = consumeUint32(b) + b, d.h[7] = consumeUint32(b) + b = b[copy(d.x[:], b):] + b, d.len = consumeUint64(b) + d.nx = int(d.len % chunk) + return nil +} + +func consumeUint64(b []byte) ([]byte, uint64) { + return b[8:], byteorder.BEUint64(b) +} + +func consumeUint32(b []byte) ([]byte, uint32) { + return b[4:], byteorder.BEUint32(b) +} + +func (d *Digest) Reset() { + if !d.is224 { + d.h[0] = init0 + d.h[1] = init1 + d.h[2] = init2 + d.h[3] = init3 + d.h[4] = init4 + d.h[5] = init5 + d.h[6] = init6 + d.h[7] = init7 + } else { + d.h[0] = init0_224 + d.h[1] = init1_224 + d.h[2] = init2_224 + d.h[3] = init3_224 + d.h[4] = init4_224 + d.h[5] = init5_224 + d.h[6] = init6_224 + d.h[7] = init7_224 + } + d.nx = 0 + d.len = 0 +} + +// New returns a new Digest computing the SHA-256 hash. +func New() *Digest { + d := new(Digest) + d.Reset() + return d +} + +// New224 returns a new Digest computing the SHA-224 hash. +func New224() *Digest { + d := new(Digest) + d.is224 = true + d.Reset() + return d +} + +func (d *Digest) Size() int { + if !d.is224 { + return size + } + return size224 +} + +func (d *Digest) BlockSize() int { return blockSize } + +func (d *Digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == chunk { + block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d *Digest) Sum(in []byte) []byte { + //fips140.RecordApproved() + // Make a copy of d so that caller can keep writing and summing. + d0 := *d + hash := d0.checkSum() + if d0.is224 { + return append(in, hash[:size224]...) + } + return append(in, hash[:]...) +} + +func (d *Digest) checkSum() [size]byte { + len := d.len + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + var tmp [64 + 8]byte // padding + length buffer + tmp[0] = 0x80 + var t uint64 + if len%64 < 56 { + t = 56 - len%64 + } else { + t = 64 + 56 - len%64 + } + + // Length in bits. + len <<= 3 + padlen := tmp[:t+8] + byteorder.BEPutUint64(padlen[t+0:], len) + d.Write(padlen) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [size]byte + + byteorder.BEPutUint32(digest[0:], d.h[0]) + byteorder.BEPutUint32(digest[4:], d.h[1]) + byteorder.BEPutUint32(digest[8:], d.h[2]) + byteorder.BEPutUint32(digest[12:], d.h[3]) + byteorder.BEPutUint32(digest[16:], d.h[4]) + byteorder.BEPutUint32(digest[20:], d.h[5]) + byteorder.BEPutUint32(digest[24:], d.h[6]) + if !d.is224 { + byteorder.BEPutUint32(digest[28:], d.h[7]) + } + + return digest +} \ No newline at end of file diff --git a/sha256/sha256block.go b/sha256/sha256block.go new file mode 100644 index 0000000..55fae63 --- /dev/null +++ b/sha256/sha256block.go @@ -0,0 +1,128 @@ +// 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. + +// SHA256 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package sha256 + +import "math/bits" + +var _K = [...]uint32{ + 0x428a2f98, + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, +} + +func blockGeneric(dig *Digest, p []byte) { + var w [64]uint32 + h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] + for len(p) >= chunk { + // Can interlace the computation of w with the + // rounds below if needed for speed. + for i := 0; i < 16; i++ { + j := i * 4 + w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3]) + } + for i := 16; i < 64; i++ { + v1 := w[i-2] + t1 := (bits.RotateLeft32(v1, -17)) ^ (bits.RotateLeft32(v1, -19)) ^ (v1 >> 10) + v2 := w[i-15] + t2 := (bits.RotateLeft32(v2, -7)) ^ (bits.RotateLeft32(v2, -18)) ^ (v2 >> 3) + w[i] = t1 + w[i-7] + t2 + w[i-16] + } + + a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 + + for i := 0; i < 64; i++ { + t1 := h + ((bits.RotateLeft32(e, -6)) ^ (bits.RotateLeft32(e, -11)) ^ (bits.RotateLeft32(e, -25))) + ((e & f) ^ (^e & g)) + _K[i] + w[i] + + t2 := ((bits.RotateLeft32(a, -2)) ^ (bits.RotateLeft32(a, -13)) ^ (bits.RotateLeft32(a, -22))) + ((a & b) ^ (a & c) ^ (b & c)) + + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + } + + h0 += a + h1 += b + h2 += c + h3 += d + h4 += e + h5 += f + h6 += g + h7 += h + + p = p[chunk:] + } + + dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 +} \ No newline at end of file diff --git a/sha256/sha256block_noasm.go b/sha256/sha256block_noasm.go new file mode 100644 index 0000000..119a998 --- /dev/null +++ b/sha256/sha256block_noasm.go @@ -0,0 +1,9 @@ +// Copyright 2016 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 sha256 + +func block(dig *Digest, p []byte) { + blockGeneric(dig, p) +} \ No newline at end of file diff --git a/sha3/hashes.go b/sha3/hashes.go new file mode 100644 index 0000000..b719ca1 --- /dev/null +++ b/sha3/hashes.go @@ -0,0 +1,59 @@ +// Copyright 2014 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 sha3 + +// New224 returns a new Digest computing the SHA3-224 hash. +func New224() *Digest { + return &Digest{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3} +} + +// New256 returns a new Digest computing the SHA3-256 hash. +func New256() *Digest { + return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3} +} + +// New384 returns a new Digest computing the SHA3-384 hash. +func New384() *Digest { + return &Digest{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3} +} + +// New512 returns a new Digest computing the SHA3-512 hash. +func New512() *Digest { + return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3} +} + +// TODO(fips): do this in the stdlib crypto/sha3 package. +// +// crypto.RegisterHash(crypto.SHA3_224, New224) +// crypto.RegisterHash(crypto.SHA3_256, New256) +// crypto.RegisterHash(crypto.SHA3_384, New384) +// crypto.RegisterHash(crypto.SHA3_512, New512) + +const ( + dsbyteSHA3 = 0b00000110 + dsbyteKeccak = 0b00000001 + dsbyteShake = 0b00011111 + dsbyteCShake = 0b00000100 + + // rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in + // bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits. + rateK256 = (1600 - 256) / 8 + rateK448 = (1600 - 448) / 8 + rateK512 = (1600 - 512) / 8 + rateK768 = (1600 - 768) / 8 + rateK1024 = (1600 - 1024) / 8 +) + +// NewLegacyKeccak256 returns a new Digest computing the legacy, non-standard +// Keccak-256 hash. +func NewLegacyKeccak256() *Digest { + return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak} +} + +// NewLegacyKeccak512 returns a new Digest computing the legacy, non-standard +// Keccak-512 hash. +func NewLegacyKeccak512() *Digest { + return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak} +} \ No newline at end of file diff --git a/sha3/keccakf.go b/sha3/keccakf.go new file mode 100644 index 0000000..1a6ec83 --- /dev/null +++ b/sha3/keccakf.go @@ -0,0 +1,434 @@ +// Copyright 2014 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 sha3 + +import ( + "encoding/binary" + + "github.com/xtls/reality/byteorder" + //"crypto/internal/fips140deps/cpu" + "math/bits" + "unsafe" +) + +// rc stores the round constants for use in the ι step. +var rc = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// keccakF1600Generic applies the Keccak permutation. +func keccakF1600Generic(da *[200]byte) { + var a *[25]uint64 + //if cpu.BigEndian { + if binary.NativeEndian.Uint16([]byte{0x12, 0x34}) != uint16(0x3412) { + a = new([25]uint64) + for i := range a { + a[i] = byteorder.LEUint64(da[i*8:]) + } + defer func() { + for i := range a { + byteorder.LEPutUint64(da[i*8:], a[i]) + } + }() + } else { + a = (*[25]uint64)(unsafe.Pointer(da)) + } + + // Implementation translated from Keccak-inplace.c + // in the keccak reference code. + var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 + + for i := 0; i < 24; i += 4 { + // Combines the 5 steps in each round into 2 steps. + // Unrolls 4 rounds per loop and spreads some steps across rounds. + + // Round 1 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[6] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[12] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[18] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[24] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] + a[6] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[16] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[22] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[3] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[10] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[1] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[7] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[19] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[20] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[11] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[23] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[4] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[5] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[2] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[8] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[14] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[15] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + // Round 2 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[16] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[7] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[23] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[14] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] + a[16] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[11] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[2] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[18] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[20] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[6] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[22] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[4] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[15] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[1] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[8] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[24] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[10] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[12] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[3] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[19] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[5] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + // Round 3 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[11] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[22] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[8] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[19] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] + a[11] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[1] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[12] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[23] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[15] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[16] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[2] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[24] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[5] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[6] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[3] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[14] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[20] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[7] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[18] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[4] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[10] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + // Round 4 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[1] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[2] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[3] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[4] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] + a[1] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[6] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[7] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[8] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[5] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[11] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[12] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[14] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[10] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[16] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[18] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[19] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[15] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[22] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[23] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[24] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[20] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + } +} \ No newline at end of file diff --git a/sha3/sha3.go b/sha3/sha3.go new file mode 100644 index 0000000..7e4345d --- /dev/null +++ b/sha3/sha3.go @@ -0,0 +1,235 @@ +// Copyright 2014 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 sha3 implements the SHA-3 fixed-output-length hash functions and +// the SHAKE variable-output-length functions defined by [FIPS 202], as well as +// the cSHAKE extendable-output-length functions defined by [SP 800-185]. +// +// [FIPS 202]: https://doi.org/10.6028/NIST.FIPS.202 +// [SP 800-185]: https://doi.org/10.6028/NIST.SP.800-185 +package sha3 + +import ( + // "github.com/xtls/reality/fips140" + "github.com/xtls/reality/subtle" + "errors" +) + +// spongeDirection indicates the direction bytes are flowing through the sponge. +type spongeDirection int + +const ( + // spongeAbsorbing indicates that the sponge is absorbing input. + spongeAbsorbing spongeDirection = iota + // spongeSqueezing indicates that the sponge is being squeezed. + spongeSqueezing +) + +type Digest struct { + a [1600 / 8]byte // main state of the hash + + // a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR + // into before running the permutation. If squeezing, it's the remaining + // output to produce before running the permutation. + n, rate int + + // dsbyte contains the "domain separation" bits and the first bit of + // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the + // SHA-3 and SHAKE functions by appending bitstrings to the message. + // Using a little-endian bit-ordering convention, these are "01" for SHA-3 + // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the + // padding rule from section 5.1 is applied to pad the message to a multiple + // of the rate, which involves adding a "1" bit, zero or more "0" bits, and + // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, + // giving 00000110b (0x06) and 00011111b (0x1f). + // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf + // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and + // Extendable-Output Functions (May 2014)" + dsbyte byte + + outputLen int // the default output size in bytes + state spongeDirection // whether the sponge is absorbing or squeezing +} + +// BlockSize returns the rate of sponge underlying this hash function. +func (d *Digest) BlockSize() int { return d.rate } + +// Size returns the output size of the hash function in bytes. +func (d *Digest) Size() int { return d.outputLen } + +// Reset resets the Digest to its initial state. +func (d *Digest) Reset() { + // Zero the permutation's state. + for i := range d.a { + d.a[i] = 0 + } + d.state = spongeAbsorbing + d.n = 0 +} + +func (d *Digest) Clone() *Digest { + ret := *d + return &ret +} + +// permute applies the KeccakF-1600 permutation. +func (d *Digest) permute() { + keccakF1600(&d.a) + d.n = 0 +} + +// padAndPermute appends the domain separation bits in dsbyte, applies +// the multi-bitrate 10..1 padding rule, and permutes the state. +func (d *Digest) padAndPermute() { + // Pad with this instance's domain-separator bits. We know that there's + // at least one byte of space in the sponge because, if it were full, + // permute would have been called to empty it. dsbyte also contains the + // first one bit for the padding. See the comment in the state struct. + d.a[d.n] ^= d.dsbyte + // This adds the final one bit for the padding. Because of the way that + // bits are numbered from the LSB upwards, the final bit is the MSB of + // the last byte. + d.a[d.rate-1] ^= 0x80 + // Apply the permutation + d.permute() + d.state = spongeSqueezing +} + +// Write absorbs more data into the hash's state. +func (d *Digest) Write(p []byte) (n int, err error) { return d.write(p) } +func (d *Digest) writeGeneric(p []byte) (n int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: Write after Read") + } + + n = len(p) + + for len(p) > 0 { + x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) + d.n += x + p = p[x:] + + // If the sponge is full, apply the permutation. + if d.n == d.rate { + d.permute() + } + } + + return +} + +// read squeezes an arbitrary number of bytes from the sponge. +func (d *Digest) readGeneric(out []byte) (n int, err error) { + // If we're still absorbing, pad and apply the permutation. + if d.state == spongeAbsorbing { + d.padAndPermute() + } + + n = len(out) + + // Now, do the squeezing. + for len(out) > 0 { + // Apply the permutation if we've squeezed the sponge dry. + if d.n == d.rate { + d.permute() + } + + x := copy(out, d.a[d.n:d.rate]) + d.n += x + out = out[x:] + } + + return +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (d *Digest) Sum(b []byte) []byte { + // fips140.RecordApproved() + return d.sum(b) +} + +func (d *Digest) sumGeneric(b []byte) []byte { + if d.state != spongeAbsorbing { + panic("sha3: Sum after Read") + } + + // Make a copy of the original hash so that caller can keep writing + // and summing. + dup := d.Clone() + hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation + dup.read(hash) + return append(b, hash...) +} + +const ( + magicSHA3 = "sha\x08" + magicShake = "sha\x09" + magicCShake = "sha\x0a" + magicKeccak = "sha\x0b" + // magic || rate || main state || n || sponge direction + marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + switch d.dsbyte { + case dsbyteSHA3: + b = append(b, magicSHA3...) + case dsbyteShake: + b = append(b, magicShake...) + case dsbyteCShake: + b = append(b, magicCShake...) + case dsbyteKeccak: + b = append(b, magicKeccak...) + default: + panic("unknown dsbyte") + } + // rate is at most 168, and n is at most rate. + b = append(b, byte(d.rate)) + b = append(b, d.a[:]...) + b = append(b, byte(d.n), byte(d.state)) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) != marshaledSize { + return errors.New("sha3: invalid hash state") + } + + magic := string(b[:len(magicSHA3)]) + b = b[len(magicSHA3):] + switch { + case magic == magicSHA3 && d.dsbyte == dsbyteSHA3: + case magic == magicShake && d.dsbyte == dsbyteShake: + case magic == magicCShake && d.dsbyte == dsbyteCShake: + case magic == magicKeccak && d.dsbyte == dsbyteKeccak: + default: + return errors.New("sha3: invalid hash state identifier") + } + + rate := int(b[0]) + b = b[1:] + if rate != d.rate { + return errors.New("sha3: invalid hash state function") + } + + copy(d.a[:], b) + b = b[len(d.a):] + + n, state := int(b[0]), spongeDirection(b[1]) + if n > d.rate { + return errors.New("sha3: invalid hash state") + } + d.n = n + if state != spongeAbsorbing && state != spongeSqueezing { + return errors.New("sha3: invalid hash state") + } + d.state = state + + return nil +} \ No newline at end of file diff --git a/sha3/sha3_noasm.go b/sha3/sha3_noasm.go new file mode 100644 index 0000000..eba7eca --- /dev/null +++ b/sha3/sha3_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 sha3 + +func keccakF1600(a *[200]byte) { + keccakF1600Generic(a) +} + +func (d *Digest) write(p []byte) (n int, err error) { + return d.writeGeneric(p) +} +func (d *Digest) read(out []byte) (n int, err error) { + return d.readGeneric(out) +} +func (d *Digest) sum(b []byte) []byte { + return d.sumGeneric(b) +} \ No newline at end of file diff --git a/sha512/sha512.go b/sha512/sha512.go new file mode 100644 index 0000000..9fbdb56 --- /dev/null +++ b/sha512/sha512.go @@ -0,0 +1,301 @@ +// 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 sha512 implements the SHA-384, SHA-512, SHA-512/224, and SHA-512/256 +// hash algorithms as defined in FIPS 180-4. +package sha512 + +import ( + // "github.com/xtls/reality/fips140" + "github.com/xtls/reality/byteorder" + "errors" +) + +const ( + // size512 is the size, in bytes, of a SHA-512 checksum. + size512 = 64 + + // size224 is the size, in bytes, of a SHA-512/224 checksum. + size224 = 28 + + // size256 is the size, in bytes, of a SHA-512/256 checksum. + size256 = 32 + + // size384 is the size, in bytes, of a SHA-384 checksum. + size384 = 48 + + // blockSize is the block size, in bytes, of the SHA-512/224, + // SHA-512/256, SHA-384 and SHA-512 hash functions. + blockSize = 128 +) + +const ( + chunk = 128 + init0 = 0x6a09e667f3bcc908 + init1 = 0xbb67ae8584caa73b + init2 = 0x3c6ef372fe94f82b + init3 = 0xa54ff53a5f1d36f1 + init4 = 0x510e527fade682d1 + init5 = 0x9b05688c2b3e6c1f + init6 = 0x1f83d9abfb41bd6b + init7 = 0x5be0cd19137e2179 + init0_224 = 0x8c3d37c819544da2 + init1_224 = 0x73e1996689dcd4d6 + init2_224 = 0x1dfab7ae32ff9c82 + init3_224 = 0x679dd514582f9fcf + init4_224 = 0x0f6d2b697bd44da8 + init5_224 = 0x77e36f7304c48942 + init6_224 = 0x3f9d85a86a1d36c8 + init7_224 = 0x1112e6ad91d692a1 + init0_256 = 0x22312194fc2bf72c + init1_256 = 0x9f555fa3c84c64c2 + init2_256 = 0x2393b86b6f53b151 + init3_256 = 0x963877195940eabd + init4_256 = 0x96283ee2a88effe3 + init5_256 = 0xbe5e1e2553863992 + init6_256 = 0x2b0199fc2c85b8aa + init7_256 = 0x0eb72ddc81c52ca2 + init0_384 = 0xcbbb9d5dc1059ed8 + init1_384 = 0x629a292a367cd507 + init2_384 = 0x9159015a3070dd17 + init3_384 = 0x152fecd8f70e5939 + init4_384 = 0x67332667ffc00b31 + init5_384 = 0x8eb44a8768581511 + init6_384 = 0xdb0c2e0d64f98fa7 + init7_384 = 0x47b5481dbefa4fa4 +) + +// Digest is a SHA-384, SHA-512, SHA-512/224, or SHA-512/256 [hash.Hash] +// implementation. +type Digest struct { + h [8]uint64 + x [chunk]byte + nx int + len uint64 + size int // size224, size256, size384, or size512 +} + +func (d *Digest) Reset() { + switch d.size { + case size384: + d.h[0] = init0_384 + d.h[1] = init1_384 + d.h[2] = init2_384 + d.h[3] = init3_384 + d.h[4] = init4_384 + d.h[5] = init5_384 + d.h[6] = init6_384 + d.h[7] = init7_384 + case size224: + d.h[0] = init0_224 + d.h[1] = init1_224 + d.h[2] = init2_224 + d.h[3] = init3_224 + d.h[4] = init4_224 + d.h[5] = init5_224 + d.h[6] = init6_224 + d.h[7] = init7_224 + case size256: + d.h[0] = init0_256 + d.h[1] = init1_256 + d.h[2] = init2_256 + d.h[3] = init3_256 + d.h[4] = init4_256 + d.h[5] = init5_256 + d.h[6] = init6_256 + d.h[7] = init7_256 + case size512: + d.h[0] = init0 + d.h[1] = init1 + d.h[2] = init2 + d.h[3] = init3 + d.h[4] = init4 + d.h[5] = init5 + d.h[6] = init6 + d.h[7] = init7 + default: + panic("unknown size") + } + d.nx = 0 + d.len = 0 +} + +const ( + magic384 = "sha\x04" + magic512_224 = "sha\x05" + magic512_256 = "sha\x06" + magic512 = "sha\x07" + marshaledSize = len(magic512) + 8*8 + chunk + 8 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + switch d.size { + case size384: + b = append(b, magic384...) + case size224: + b = append(b, magic512_224...) + case size256: + b = append(b, magic512_256...) + case size512: + b = append(b, magic512...) + default: + panic("unknown size") + } + b = byteorder.BEAppendUint64(b, d.h[0]) + b = byteorder.BEAppendUint64(b, d.h[1]) + b = byteorder.BEAppendUint64(b, d.h[2]) + b = byteorder.BEAppendUint64(b, d.h[3]) + b = byteorder.BEAppendUint64(b, d.h[4]) + b = byteorder.BEAppendUint64(b, d.h[5]) + b = byteorder.BEAppendUint64(b, d.h[6]) + b = byteorder.BEAppendUint64(b, d.h[7]) + b = append(b, d.x[:d.nx]...) + b = append(b, make([]byte, len(d.x)-d.nx)...) + b = byteorder.BEAppendUint64(b, d.len) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic512) { + return errors.New("crypto/sha512: invalid hash state identifier") + } + switch { + case d.size == size384 && string(b[:len(magic384)]) == magic384: + case d.size == size224 && string(b[:len(magic512_224)]) == magic512_224: + case d.size == size256 && string(b[:len(magic512_256)]) == magic512_256: + case d.size == size512 && string(b[:len(magic512)]) == magic512: + default: + return errors.New("crypto/sha512: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/sha512: invalid hash state size") + } + b = b[len(magic512):] + b, d.h[0] = consumeUint64(b) + b, d.h[1] = consumeUint64(b) + b, d.h[2] = consumeUint64(b) + b, d.h[3] = consumeUint64(b) + b, d.h[4] = consumeUint64(b) + b, d.h[5] = consumeUint64(b) + b, d.h[6] = consumeUint64(b) + b, d.h[7] = consumeUint64(b) + b = b[copy(d.x[:], b):] + b, d.len = consumeUint64(b) + d.nx = int(d.len % chunk) + return nil +} + +func consumeUint64(b []byte) ([]byte, uint64) { + return b[8:], byteorder.BEUint64(b) +} + +// New returns a new Digest computing the SHA-512 hash. +func New() *Digest { + d := &Digest{size: size512} + d.Reset() + return d +} + +// New512_224 returns a new Digest computing the SHA-512/224 hash. +func New512_224() *Digest { + d := &Digest{size: size224} + d.Reset() + return d +} + +// New512_256 returns a new Digest computing the SHA-512/256 hash. +func New512_256() *Digest { + d := &Digest{size: size256} + d.Reset() + return d +} + +// New384 returns a new Digest computing the SHA-384 hash. +func New384() *Digest { + d := &Digest{size: size384} + d.Reset() + return d +} + +func (d *Digest) Size() int { + return d.size +} + +func (d *Digest) BlockSize() int { return blockSize } + +func (d *Digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == chunk { + block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d *Digest) Sum(in []byte) []byte { + // fips140.RecordApproved() + // Make a copy of d so that caller can keep writing and summing. + d0 := new(Digest) + *d0 = *d + hash := d0.checkSum() + return append(in, hash[:d.size]...) +} + +func (d *Digest) checkSum() [size512]byte { + // Padding. Add a 1 bit and 0 bits until 112 bytes mod 128. + len := d.len + var tmp [128 + 16]byte // padding + length buffer + tmp[0] = 0x80 + var t uint64 + if len%128 < 112 { + t = 112 - len%128 + } else { + t = 128 + 112 - len%128 + } + + // Length in bits. + len <<= 3 + padlen := tmp[:t+16] + // Upper 64 bits are always zero, because len variable has type uint64, + // and tmp is already zeroed at that index, so we can skip updating it. + // byteorder.BEPutUint64(padlen[t+0:], 0) + byteorder.BEPutUint64(padlen[t+8:], len) + d.Write(padlen) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [size512]byte + byteorder.BEPutUint64(digest[0:], d.h[0]) + byteorder.BEPutUint64(digest[8:], d.h[1]) + byteorder.BEPutUint64(digest[16:], d.h[2]) + byteorder.BEPutUint64(digest[24:], d.h[3]) + byteorder.BEPutUint64(digest[32:], d.h[4]) + byteorder.BEPutUint64(digest[40:], d.h[5]) + if d.size != size384 { + byteorder.BEPutUint64(digest[48:], d.h[6]) + byteorder.BEPutUint64(digest[56:], d.h[7]) + } + + return digest +} \ No newline at end of file diff --git a/sha512/sha512block.go b/sha512/sha512block.go new file mode 100644 index 0000000..feb6827 --- /dev/null +++ b/sha512/sha512block.go @@ -0,0 +1,144 @@ +// 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. + +// SHA512 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package sha512 + +import "math/bits" + +var _K = [...]uint64{ + 0x428a2f98d728ae22, + 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, + 0x59f111f1b605d019, + 0x923f82a4af194f9b, + 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, + 0x12835b0145706fbe, + 0x243185be4ee4b28c, + 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, + 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, + 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, + 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, + 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, + 0x76f988da831153b5, + 0x983e5152ee66dfab, + 0xa831c66d2db43210, + 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, + 0xd5a79147930aa725, + 0x06ca6351e003826f, + 0x142929670a0e6e70, + 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, + 0x650a73548baf63de, + 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, + 0x92722c851482353b, + 0xa2bfe8a14cf10364, + 0xa81a664bbc423001, + 0xc24b8b70d0f89791, + 0xc76c51a30654be30, + 0xd192e819d6ef5218, + 0xd69906245565a910, + 0xf40e35855771202a, + 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, + 0x1e376c085141ab53, + 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, + 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, + 0x78a5636f43172f60, + 0x84c87814a1f0ab72, + 0x8cc702081a6439ec, + 0x90befffa23631e28, + 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, + 0xca273eceea26619c, + 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, + 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, + 0x1b710b35131c471b, + 0x28db77f523047d84, + 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, + 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, + 0x6c44198c4a475817, +} + +func blockGeneric(dig *Digest, p []byte) { + var w [80]uint64 + h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] + for len(p) >= chunk { + for i := 0; i < 16; i++ { + j := i * 8 + w[i] = uint64(p[j])<<56 | uint64(p[j+1])<<48 | uint64(p[j+2])<<40 | uint64(p[j+3])<<32 | + uint64(p[j+4])<<24 | uint64(p[j+5])<<16 | uint64(p[j+6])<<8 | uint64(p[j+7]) + } + for i := 16; i < 80; i++ { + v1 := w[i-2] + t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6) + v2 := w[i-15] + t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7) + + w[i] = t1 + w[i-7] + t2 + w[i-16] + } + + a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 + + for i := 0; i < 80; i++ { + t1 := h + (bits.RotateLeft64(e, -14) ^ bits.RotateLeft64(e, -18) ^ bits.RotateLeft64(e, -41)) + ((e & f) ^ (^e & g)) + _K[i] + w[i] + + t2 := (bits.RotateLeft64(a, -28) ^ bits.RotateLeft64(a, -34) ^ bits.RotateLeft64(a, -39)) + ((a & b) ^ (a & c) ^ (b & c)) + + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + } + + h0 += a + h1 += b + h2 += c + h3 += d + h4 += e + h5 += f + h6 += g + h7 += h + + p = p[chunk:] + } + + dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 +} \ No newline at end of file diff --git a/sha512/sha512block_noasm.go b/sha512/sha512block_noasm.go new file mode 100644 index 0000000..1a3ada7 --- /dev/null +++ b/sha512/sha512block_noasm.go @@ -0,0 +1,9 @@ +// Copyright 2016 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 sha512 + +func block(dig *Digest, p []byte) { + blockGeneric(dig, p) +} \ No newline at end of file diff --git a/tls13/tls13.go b/tls13/tls13.go new file mode 100644 index 0000000..90e8ad2 --- /dev/null +++ b/tls13/tls13.go @@ -0,0 +1,178 @@ +// 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 ( + "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140" + "github.com/xtls/reality/hkdf" +) + +// 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 fips140.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 = byteorder.BEAppendUint16(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...) + return hkdf.Expand(hash, secret, string(hkdfLabel), length) +} + +func extract[H fips140.Hash](hash func() H, newSecret, currentSecret []byte) []byte { + if newSecret == nil { + newSecret = make([]byte, hash().Size()) + } + return hkdf.Extract(hash, newSecret, currentSecret) +} + +func deriveSecret[H fips140.Hash](hash func() H, secret []byte, label string, transcript fips140.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() fips140.Hash +} + +func NewEarlySecret[H fips140.Hash](hash func() H, psk []byte) *EarlySecret { + return &EarlySecret{ + secret: extract(hash, psk, nil), + hash: func() fips140.Hash { return hash() }, + } +} + +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 fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript) +} + +type HandshakeSecret struct { + secret []byte + hash func() fips140.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 fips140.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 fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript) +} + +type MasterSecret struct { + secret []byte + hash func() fips140.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 fips140.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 fips140.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 fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, resumptionLabel, transcript) +} + +type ExporterMasterSecret struct { + secret []byte + hash func() fips140.Hash +} + +// ExporterMasterSecret derives the exporter_master_secret from the master secret +// and the transcript up to the server Finished. +func (s *MasterSecret) ExporterMasterSecret(transcript fips140.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 fips140.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 From e449f03f32fae13df6c4f06795eb03999eb24f97 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 17:01:30 -0400 Subject: [PATCH 04/45] crypto/internal/mlkem768: make Decapsulate a method This will make it easier to support multiple sizes if needed. Change-Id: I47495559fdbbf678fd98421ad6cb28172e5c810d Reviewed-on: https://go-review.googlesource.com/c/go/+/621977 Reviewed-by: Daniel McCarney Reviewed-by: Russ Cox LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker --- key_schedule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key_schedule.go b/key_schedule.go index 0210be9..8f5f2cf 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -60,7 +60,7 @@ type keySharePrivateKeys struct { // kyberDecapsulate implements decapsulation according to Kyber Round 3. func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { - K, err := mlkem768.Decapsulate(dk, c) + K, err := dk.Decapsulate(c) if err != nil { return nil, err } From b6a28bda368e8e5d99a8559859ca9fcd503d7af9 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 17:04:23 -0400 Subject: [PATCH 05/45] crypto/internal/mlkem768: add EncapsulationKey type Change-Id: I3feacb044caa15ac9bbfc11f5d90bebf8a505510 Reviewed-on: https://go-review.googlesource.com/c/go/+/621980 Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Reviewed-by: Russ Cox --- handshake_client.go | 4 ++-- key_schedule.go | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/handshake_client.go b/handshake_client.go index fddee7a..679f1a6 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -164,7 +164,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if _, err := io.ReadFull(config.rand(), seed); err != nil { return nil, nil, nil, err } - keyShareKeys.kyber, err = mlkem768.NewKeyFromSeed(seed) + keyShareKeys.kyber, err = mlkem768.NewDecapsulationKey(seed) if err != nil { return nil, nil, nil, err } @@ -174,7 +174,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon // both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. hello.keyShares = []keyShare{ {group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(), - keyShareKeys.kyber.EncapsulationKey()...)}, + keyShareKeys.kyber.EncapsulationKey().Bytes()...)}, {group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()}, } } else { diff --git a/key_schedule.go b/key_schedule.go index 8f5f2cf..f05a991 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -64,19 +64,20 @@ func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { if err != nil { return nil, err } - return kyberSharedSecret(K, c), nil + return kyberSharedSecret(c, K), nil } // kyberEncapsulate implements encapsulation according to Kyber Round 3. func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - c, ss, err = mlkem768.Encapsulate(ek) + k, err := mlkem768.NewEncapsulationKey(ek) if err != nil { return nil, nil, err } - return c, kyberSharedSecret(ss, c), nil + c, ss = k.Encapsulate() + return c, kyberSharedSecret(c, ss), nil } -func kyberSharedSecret(K, c []byte) []byte { +func kyberSharedSecret(c, K []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. From f63b058d4a270d4ffff5e9ca36fccc02c5bce092 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 17:07:26 -0400 Subject: [PATCH 06/45] crypto/internal/mlkem768: add -768 suffix to all exported identifiers In preparation for introducing ML-KEM-1024. Aside from the constants at the top, all other changes were automated. Change-Id: I0fafce9a776c7b0b9179be1c858709cabf60e80f Reviewed-on: https://go-review.googlesource.com/c/go/+/621981 Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Russ Cox Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI --- handshake_client.go | 2 +- handshake_client_tls13.go | 2 +- handshake_server_tls13.go | 2 +- key_schedule.go | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/handshake_client.go b/handshake_client.go index 679f1a6..f3a0738 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -164,7 +164,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if _, err := io.ReadFull(config.rand(), seed); err != nil { return nil, nil, nil, err } - keyShareKeys.kyber, err = mlkem768.NewDecapsulationKey(seed) + keyShareKeys.kyber, err = mlkem768.NewDecapsulationKey768(seed) if err != nil { return nil, nil, nil, err } diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 31509fa..62aee55 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -481,7 +481,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { ecdhePeerData := hs.serverHello.serverShare.data if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize { + if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize768 { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid server key share") } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index f97381e..abdb8ae 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -276,7 +276,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ecdhData := clientKeyShare.data if selectedGroup == x25519Kyber768Draft00 { ecdhGroup = X25519 - if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize { + if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize768 { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid Kyber client key share") } diff --git a/key_schedule.go b/key_schedule.go index f05a991..8d96223 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -55,11 +55,11 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip type keySharePrivateKeys struct { curveID CurveID ecdhe *ecdh.PrivateKey - kyber *mlkem768.DecapsulationKey + kyber *mlkem768.DecapsulationKey768 } // kyberDecapsulate implements decapsulation according to Kyber Round 3. -func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { +func kyberDecapsulate(dk *mlkem768.DecapsulationKey768, c []byte) ([]byte, error) { K, err := dk.Decapsulate(c) if err != nil { return nil, err @@ -69,7 +69,7 @@ func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { // kyberEncapsulate implements encapsulation according to Kyber Round 3. func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - k, err := mlkem768.NewEncapsulationKey(ek) + k, err := mlkem768.NewEncapsulationKey768(ek) if err != nil { return nil, nil, err } From 3833e8e2cbb002fa992ea9cefa2ae94ebc870891 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 21:47:49 -0400 Subject: [PATCH 07/45] crypto/internal/mlkem768: move to crypto/internal/fips/mlkem In the process, replace out-of-module imports with their FIPS versions. For #69536 Change-Id: I83e900b7c38ecf760382e5dca7fd0b1eaa5a5589 Reviewed-on: https://go-review.googlesource.com/c/go/+/626879 LUCI-TryBot-Result: Go LUCI Reviewed-by: Russ Cox Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney Reviewed-by: Michael Knyszek --- aes/ctr.go | 148 +++++++ aes/ctr_noasm.go | 21 + drbg/ctrdrbg.go | 143 ++++++ drbg/rand.go | 102 +++++ entropy/entropy.go | 29 ++ handshake_client.go | 6 +- handshake_client_tls13.go | 4 +- handshake_server_tls13.go | 4 +- key_schedule.go | 18 +- mlkem/field.go | 550 +++++++++++++++++++++++ mlkem/mlkem768.go | 517 ++++++++++++++++++++++ mlkem768/mlkem768.go | 886 -------------------------------------- randutil/randutil.go | 26 ++ sha3/shake.go | 151 +++++++ 14 files changed, 1703 insertions(+), 902 deletions(-) create mode 100644 aes/ctr.go create mode 100644 aes/ctr_noasm.go create mode 100644 drbg/ctrdrbg.go create mode 100644 drbg/rand.go create mode 100644 entropy/entropy.go create mode 100644 mlkem/field.go create mode 100644 mlkem/mlkem768.go delete mode 100644 mlkem768/mlkem768.go create mode 100644 randutil/randutil.go create mode 100644 sha3/shake.go diff --git a/aes/ctr.go b/aes/ctr.go new file mode 100644 index 0000000..89e2305 --- /dev/null +++ b/aes/ctr.go @@ -0,0 +1,148 @@ +// 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 aes + +import ( + //"crypto/internal/fips140" + "github.com/xtls/reality/alias" + "github.com/xtls/reality/subtle" + "github.com/xtls/reality/byteorder" + "math/bits" +) + +type CTR struct { + b Block + ivlo, ivhi uint64 // start counter as 64-bit limbs + offset uint64 // for XORKeyStream only +} + +func NewCTR(b *Block, iv []byte) *CTR { + // Allocate the CTR here, in an easily inlineable function, so + // the allocation can be done in the caller's stack frame + // instead of the heap. See issue 70499. + c := newCTR(b, iv) + return &c +} +func newCTR(b *Block, iv []byte) CTR { + if len(iv) != BlockSize { + panic("bad IV length") + } + + return CTR{ + b: *b, + ivlo: byteorder.BEUint64(iv[8:16]), + ivhi: byteorder.BEUint64(iv[0:8]), + offset: 0, + } +} + +func (c *CTR) XORKeyStream(dst, src []byte) { + c.XORKeyStreamAt(dst, src, c.offset) + + var carry uint64 + c.offset, carry = bits.Add64(c.offset, uint64(len(src)), 0) + if carry != 0 { + panic("crypto/aes: counter overflow") + } +} + +// RoundToBlock is used by CTR_DRBG, which discards the rightmost unused bits at +// each request. It rounds the offset up to the next block boundary. +func RoundToBlock(c *CTR) { + if remainder := c.offset % BlockSize; remainder != 0 { + var carry uint64 + c.offset, carry = bits.Add64(c.offset, BlockSize-remainder, 0) + if carry != 0 { + panic("crypto/aes: counter overflow") + } + } +} + +// XORKeyStreamAt behaves like XORKeyStream but keeps no state, and instead +// seeks into the keystream by the given bytes offset from the start (ignoring +// any XORKetStream calls). This allows for random access into the keystream, up +// to 16 EiB from the start. +func (c *CTR) XORKeyStreamAt(dst, src []byte, offset uint64) { + if len(dst) < len(src) { + panic("crypto/aes: len(dst) < len(src)") + } + dst = dst[:len(src)] + if alias.InexactOverlap(dst, src) { + panic("crypto/aes: invalid buffer overlap") + } + //fips140.RecordApproved() + + ivlo, ivhi := add128(c.ivlo, c.ivhi, offset/BlockSize) + + if blockOffset := offset % BlockSize; blockOffset != 0 { + // We have a partial block at the beginning. + var in, out [BlockSize]byte + copy(in[blockOffset:], src) + ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) + n := copy(dst, out[blockOffset:]) + src = src[n:] + dst = dst[n:] + ivlo, ivhi = add128(ivlo, ivhi, 1) + } + + for len(src) >= 8*BlockSize { + ctrBlocks8(&c.b, (*[8 * BlockSize]byte)(dst), (*[8 * BlockSize]byte)(src), ivlo, ivhi) + src = src[8*BlockSize:] + dst = dst[8*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 8) + } + + // The tail can have at most 7 = 4 + 2 + 1 blocks. + if len(src) >= 4*BlockSize { + ctrBlocks4(&c.b, (*[4 * BlockSize]byte)(dst), (*[4 * BlockSize]byte)(src), ivlo, ivhi) + src = src[4*BlockSize:] + dst = dst[4*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 4) + } + if len(src) >= 2*BlockSize { + ctrBlocks2(&c.b, (*[2 * BlockSize]byte)(dst), (*[2 * BlockSize]byte)(src), ivlo, ivhi) + src = src[2*BlockSize:] + dst = dst[2*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 2) + } + if len(src) >= 1*BlockSize { + ctrBlocks1(&c.b, (*[1 * BlockSize]byte)(dst), (*[1 * BlockSize]byte)(src), ivlo, ivhi) + src = src[1*BlockSize:] + dst = dst[1*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 1) + } + + if len(src) != 0 { + // We have a partial block at the end. + var in, out [BlockSize]byte + copy(in[:], src) + ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) + copy(dst, out[:]) + } +} + +// Each ctrBlocksN function XORs src with N blocks of counter keystream, and +// stores it in dst. src is loaded in full before storing dst, so they can +// overlap even inexactly. The starting counter value is passed in as a pair of +// little-endian 64-bit integers. + +func ctrBlocks(b *Block, dst, src []byte, ivlo, ivhi uint64) { + buf := make([]byte, len(src), 8*BlockSize) + for i := 0; i < len(buf); i += BlockSize { + byteorder.BEPutUint64(buf[i:], ivhi) + byteorder.BEPutUint64(buf[i+8:], ivlo) + ivlo, ivhi = add128(ivlo, ivhi, 1) + encryptBlock(b, buf[i:], buf[i:]) + } + // XOR into buf first, in case src and dst overlap (see above). + subtle.XORBytes(buf, src, buf) + copy(dst, buf) +} + +func add128(lo, hi uint64, x uint64) (uint64, uint64) { + lo, c := bits.Add64(lo, x, 0) + hi, _ = bits.Add64(hi, 0, c) + return lo, hi +} \ No newline at end of file diff --git a/aes/ctr_noasm.go b/aes/ctr_noasm.go new file mode 100644 index 0000000..e93fc80 --- /dev/null +++ b/aes/ctr_noasm.go @@ -0,0 +1,21 @@ +// 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 + +func ctrBlocks1(b *Block, dst, src *[BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks2(b *Block, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks4(b *Block, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks8(b *Block, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} \ No newline at end of file diff --git a/drbg/ctrdrbg.go b/drbg/ctrdrbg.go new file mode 100644 index 0000000..9d4257e --- /dev/null +++ b/drbg/ctrdrbg.go @@ -0,0 +1,143 @@ +// 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 drbg + +import ( + //"crypto/internal/fips140" + "github.com/xtls/reality/aes" + "github.com/xtls/reality/subtle" + "github.com/xtls/reality/byteorder" + "math/bits" +) + +// Counter is an SP 800-90A Rev. 1 CTR_DRBG instantiated with AES-256. +// +// Per Table 3, it has a security strength of 256 bits, a seed size of 384 bits, +// a counter length of 128 bits, a reseed interval of 2^48 requests, and a +// maximum request size of 2^19 bits (2^16 bytes, 64 KiB). +// +// We support a narrow range of parameters that fit the needs of our RNG: +// AES-256, no derivation function, no personalization string, no prediction +// resistance, and 384-bit additional input. +// +// WARNING: this type provides tightly scoped support for the DRBG +// functionality we need for FIPS 140-3 _only_. This type _should not_ be used +// outside of the FIPS 140-3 module for any other use. +// +// In particular, as documented, Counter does not support the derivation +// function, or personalization strings which are necessary for safely using +// this DRBG for generic purposes without leaking sensitive values. +type Counter struct { + // c is instantiated with K as the key and V as the counter. + c aes.CTR + + reseedCounter uint64 +} + +const ( + keySize = 256 / 8 + SeedSize = keySize + aes.BlockSize + reseedInterval = 1 << 48 + maxRequestSize = (1 << 19) / 8 +) + +func NewCounter(entropy *[SeedSize]byte) *Counter { + // CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1. + //fips140.RecordApproved() + + K := make([]byte, keySize) + V := make([]byte, aes.BlockSize) + + // V starts at 0, but is incremented in CTR_DRBG_Update before each use, + // unlike AES-CTR where it is incremented after each use. + V[len(V)-1] = 1 + + cipher, err := aes.New(K) + if err != nil { + panic(err) + } + + c := &Counter{} + c.c = *aes.NewCTR(cipher, V) + c.update(entropy) + c.reseedCounter = 1 + return c +} + +func (c *Counter) update(seed *[SeedSize]byte) { + // CTR_DRBG_Update, per Section 10.2.1.2. + + temp := make([]byte, SeedSize) + c.c.XORKeyStream(temp, seed[:]) + K := temp[:keySize] + V := temp[keySize:] + + // Again, we pre-increment V, like in NewCounter. + increment((*[aes.BlockSize]byte)(V)) + + cipher, err := aes.New(K) + if err != nil { + panic(err) + } + c.c = *aes.NewCTR(cipher, V) +} + +func increment(v *[aes.BlockSize]byte) { + hi := byteorder.BEUint64(v[:8]) + lo := byteorder.BEUint64(v[8:]) + lo, c := bits.Add64(lo, 1, 0) + hi, _ = bits.Add64(hi, 0, c) + byteorder.BEPutUint64(v[:8], hi) + byteorder.BEPutUint64(v[8:], lo) +} + +func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) { + // CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1. + //fips140.RecordApproved() + + var seed [SeedSize]byte + subtle.XORBytes(seed[:], entropy[:], additionalInput[:]) + c.update(&seed) + c.reseedCounter = 1 +} + +// Generate produces at most maxRequestSize bytes of random data in out. +func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) { + // CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1. + //fips140.RecordApproved() + + if len(out) > maxRequestSize { + panic("crypto/drbg: internal error: request size exceeds maximum") + } + + // Step 1. + if c.reseedCounter > reseedInterval { + return true + } + + // Step 2. + if additionalInput != nil { + c.update(additionalInput) + } else { + // If the additional input is null, the first CTR_DRBG_Update is + // skipped, but the additional input is replaced with an all-zero string + // for the second CTR_DRBG_Update. + additionalInput = new([SeedSize]byte) + } + + // Steps 3-5. + clear(out) + c.c.XORKeyStream(out, out) + aes.RoundToBlock(&c.c) + + // Step 6. + c.update(additionalInput) + + // Step 7. + c.reseedCounter++ + + // Step 8. + return false +} \ No newline at end of file diff --git a/drbg/rand.go b/drbg/rand.go new file mode 100644 index 0000000..7fe0b57 --- /dev/null +++ b/drbg/rand.go @@ -0,0 +1,102 @@ +// 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 drbg provides cryptographically secure random bytes +// usable by FIPS code. In FIPS mode it uses an SP 800-90A Rev. 1 +// Deterministic Random Bit Generator (DRBG). Otherwise, +// it uses the operating system's random number generator. +package drbg + +import ( + "github.com/xtls/reality/entropy" + // "crypto/internal/fips140" + "github.com/xtls/reality/randutil" + // "github.com/xtls/reality/sysrand" + + "crypto/rand" + "io" + "sync" +) + +var drbgs = sync.Pool{ + New: func() any { + var c *Counter + entropy.Depleted(func(seed *[48]byte) { + c = NewCounter(seed) + }) + return c + }, +} + +// Read fills b with cryptographically secure random bytes. In FIPS mode, it +// uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). +// Otherwise, it uses the operating system's random number generator. +func Read(b []byte) { + // if !fips140.Enabled { + // rand.Read(b) + // return + // } + + // At every read, 128 random bits from the operating system are mixed as + // additional input, to make the output as strong as non-FIPS randomness. + // This is not credited as entropy for FIPS purposes, as allowed by Section + // 8.7.2: "Note that a DRBG does not rely on additional input to provide + // entropy, even though entropy could be provided in the additional input". + additionalInput := new([SeedSize]byte) + rand.Read(additionalInput[:16]) + + drbg := drbgs.Get().(*Counter) + defer drbgs.Put(drbg) + + for len(b) > 0 { + size := min(len(b), maxRequestSize) + if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { + // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in + // Section 9.3.2: if Generate reports a reseed is required, the + // additional input is passed to Reseed along with the entropy and + // then nulled before the next Generate call. + entropy.Depleted(func(seed *[48]byte) { + drbg.Reseed(seed, additionalInput) + }) + additionalInput = nil + continue + } + b = b[size:] + } +} + +// DefaultReader is a sentinel type, embedded in the default +// [crypto/rand.Reader], used to recognize it when passed to +// APIs that accept a rand io.Reader. +type DefaultReader interface{ defaultReader() } + +// ReadWithReader uses Reader to fill b with cryptographically secure random +// bytes. It is intended for use in APIs that expose a rand io.Reader. +// +// If Reader is not the default Reader from crypto/rand, +// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called. +func ReadWithReader(r io.Reader, b []byte) error { + if _, ok := r.(DefaultReader); ok { + Read(b) + return nil + } + + //fips140.RecordNonApproved() + randutil.MaybeReadByte(r) + _, err := io.ReadFull(r, b) + return err +} + +// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call +// [randutil.MaybeReadByte] on non-default Readers. +func ReadWithReaderDeterministic(r io.Reader, b []byte) error { + if _, ok := r.(DefaultReader); ok { + Read(b) + return nil + } + + //fips140.RecordNonApproved() + _, err := io.ReadFull(r, b) + return err +} \ No newline at end of file diff --git a/entropy/entropy.go b/entropy/entropy.go new file mode 100644 index 0000000..0bea141 --- /dev/null +++ b/entropy/entropy.go @@ -0,0 +1,29 @@ +// 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 entropy provides the passive entropy source for the FIPS 140-3 +// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read]. +// +// This complies with IG 9.3.A, Additional Comment 12, which until January 1, +// 2026 allows new modules to meet an [earlier version] of Resolution 2(b): +// "A software module that contains an approved DRBG that receives a LOAD +// command (or its logical equivalent) with entropy obtained from [...] inside +// the physical perimeter of the operational environment of the module [...]." +// +// Distributions that have their own SP 800-90B entropy source should replace +// this package with their own implementation. +// +// [earlier version]: https://csrc.nist.gov/CSRC/media/Projects/cryptographic-module-validation-program/documents/IG%209.3.A%20Resolution%202b%5BMarch%2026%202024%5D.pdf +package entropy + +// "github.com/xtls/reality/sysrand" +import "crypto/rand" + +// Depleted notifies the entropy source that the entropy in the module is +// "depleted" and provides the callback for the LOAD command. +func Depleted(LOAD func(*[48]byte)) { + var entropy [48]byte + rand.Read(entropy[:]) + LOAD(&entropy) +} \ No newline at end of file diff --git a/handshake_client.go b/handshake_client.go index f3a0738..9cd987a 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -23,7 +23,7 @@ import ( "time" "github.com/xtls/reality/hpke" - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -160,11 +160,11 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon 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.NewDecapsulationKey768(seed) + keyShareKeys.kyber, err = mlkem.NewDecapsulationKey768(seed) if err != nil { return nil, nil, nil, err } diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 62aee55..f23bc8f 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -16,7 +16,7 @@ import ( "slices" "time" - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" "golang.org/x/crypto/hkdf" ) @@ -481,7 +481,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { ecdhePeerData := hs.serverHello.serverShare.data if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize768 { + if len(ecdhePeerData) != x25519PublicKeySize+mlkem.CiphertextSize768 { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid server key share") } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index abdb8ae..1dce1c7 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -22,7 +22,7 @@ import ( "slices" "time" - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -276,7 +276,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ecdhData := clientKeyShare.data if selectedGroup == x25519Kyber768Draft00 { ecdhGroup = X25519 - if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize768 { + if len(ecdhData) != x25519PublicKeySize+mlkem.EncapsulationKeySize768 { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid Kyber client key share") } diff --git a/key_schedule.go b/key_schedule.go index 8d96223..7113751 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -11,9 +11,8 @@ import ( "hash" "io" - "golang.org/x/crypto/sha3" - - "github.com/xtls/reality/mlkem768" + "github.com/xtls/reality/mlkem" + "github.com/xtls/reality/sha3" "github.com/xtls/reality/tls13" ) @@ -55,11 +54,11 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip type keySharePrivateKeys struct { curveID CurveID ecdhe *ecdh.PrivateKey - kyber *mlkem768.DecapsulationKey768 + kyber *mlkem.DecapsulationKey768 } // kyberDecapsulate implements decapsulation according to Kyber Round 3. -func kyberDecapsulate(dk *mlkem768.DecapsulationKey768, c []byte) ([]byte, error) { +func kyberDecapsulate(dk *mlkem.DecapsulationKey768, c []byte) ([]byte, error) { K, err := dk.Decapsulate(c) if err != nil { return nil, err @@ -69,7 +68,7 @@ func kyberDecapsulate(dk *mlkem768.DecapsulationKey768, c []byte) ([]byte, error // kyberEncapsulate implements encapsulation according to Kyber Round 3. func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - k, err := mlkem768.NewEncapsulationKey768(ek) + k, err := mlkem.NewEncapsulationKey768(ek) if err != nil { return nil, nil, err } @@ -78,13 +77,14 @@ func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { } func kyberSharedSecret(c, K []byte) []byte { - // Package mlkem768 implements ML-KEM, which compared to Kyber removed a + // Package mlkem 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[:]) + ch := sha3.New256() + ch.Write(c) + h.Write(ch.Sum(nil)) out := make([]byte, 32) h.Read(out) return out diff --git a/mlkem/field.go b/mlkem/field.go new file mode 100644 index 0000000..4f94ea9 --- /dev/null +++ b/mlkem/field.go @@ -0,0 +1,550 @@ +// 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 mlkem + +import ( + "github.com/xtls/reality/sha3" + "github.com/xtls/reality/byteorder" + "errors" +) + +// 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, Definition 4.7. +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, 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, Definition 4.8. +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, 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, Section 2.4.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, Algorithm 5. +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 fulfills the "Modulus check" +// step of ML-KEM Encapsulation. +// +// It implements ByteDecode₁₂, according to FIPS 203, Algorithm 6. +func polyByteDecode[T ~[n]fieldElement](b []byte) (T, error) { + if len(b) != encodingSize12 { + return T{}, errors.New("mlkem: 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("mlkem: invalid polynomial encoding") + } + if f[i+1], err = fieldCheckReduced(uint16(d >> 12)); err != nil { + return T{}, errors.New("mlkem: 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, Definition 4.7, +// followed by ByteEncode₁, according to FIPS 203, Algorithm 5. +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, Algorithm 6, +// followed by Decompress₁, according to FIPS 203, Definition 4.8. +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, 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, Definition 4.7, +// followed by ByteEncode₄, according to FIPS 203, Algorithm 5. +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, Algorithm 6, +// followed by Decompress₄, according to FIPS 203, Definition 4.8. +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, Definition 4.7, +// followed by ByteEncode₁₀, according to FIPS 203, Algorithm 5. +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], 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, Algorithm 6, +// followed by Decompress₁₀, according to FIPS 203, Definition 4.8. +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 +} + +// ringCompressAndEncode appends an encoding of a ring element to s, +// compressing each coefficient to d bits. +// +// It implements Compress, according to FIPS 203, Definition 4.7, +// followed by ByteEncode, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode(s []byte, f ringElement, d uint8) []byte { + var b byte + var bIdx uint8 + for i := 0; i < n; i++ { + c := compress(f[i], d) + var cIdx uint8 + for cIdx < d { + b |= byte(c>>cIdx) << bIdx + bits := min(8-bIdx, d-cIdx) + bIdx += bits + cIdx += bits + if bIdx == 8 { + s = append(s, b) + b = 0 + bIdx = 0 + } + } + } + if bIdx != 0 { + panic("mlkem: internal error: bitsFilled != 0") + } + return s +} + +// ringDecodeAndDecompress decodes an encoding of a ring element where +// each d bits are mapped to an equidistant distribution. +// +// It implements ByteDecode, according to FIPS 203, Algorithm 6, +// followed by Decompress, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress(b []byte, d uint8) ringElement { + var f ringElement + var bIdx uint8 + for i := 0; i < n; i++ { + var c uint16 + var cIdx uint8 + for cIdx < d { + c |= uint16(b[0]>>bIdx) << cIdx + c &= (1 << d) - 1 + bits := min(8-bIdx, d-cIdx) + bIdx += bits + cIdx += bits + if bIdx == 8 { + b = b[1:] + bIdx = 0 + } + } + f[i] = fieldElement(decompress(c, d)) + } + if len(b) != 0 { + panic("mlkem: internal error: leftover bytes") + } + return f +} + +// ringCompressAndEncode5 appends a 160-byte encoding of a ring element to s, +// compressing eight coefficients per five bytes. +// +// It implements Compress₅, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₅, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode5(s []byte, f ringElement) []byte { + return ringCompressAndEncode(s, f, 5) +} + +// ringDecodeAndDecompress5 decodes a 160-byte encoding of a ring element where +// each five bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₅, according to FIPS 203, Algorithm 6, +// followed by Decompress₅, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress5(bb *[encodingSize5]byte) ringElement { + return ringDecodeAndDecompress(bb[:], 5) +} + +// ringCompressAndEncode11 appends a 352-byte encoding of a ring element to s, +// compressing eight coefficients per eleven bytes. +// +// It implements Compress₁₁, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₁₁, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode11(s []byte, f ringElement) []byte { + return ringCompressAndEncode(s, f, 11) +} + +// ringDecodeAndDecompress11 decodes a 352-byte encoding of a ring element where +// each eleven bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₁₁, according to FIPS 203, Algorithm 6, +// followed by Decompress₁₁, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress11(bb *[encodingSize11]byte) ringElement { + return ringDecodeAndDecompress(bb[:], 11) +} + +// samplePolyCBD draws a ringElement from the special Dη distribution given a +// stream of random bytes generated by the PRF function, according to FIPS 203, +// Algorithm 8 and Definition 4.3. +func samplePolyCBD(s []byte, b byte) ringElement { + prf := sha3.NewShake256() + prf.Write(s) + prf.Write([]byte{b}) + B := make([]byte, 64*2) // η = 2 + 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, Section 2.4.4. +type nttElement [n]fieldElement + +// gammas are the values ζ^2BitRev7(i)+1 mod q for each index i, according to +// FIPS 203, Appendix A (with negative values reduced to positive). +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, Algorithm 11. +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, according to FIPS +// 203, Appendix A. +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, Algorithm 9. +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, Algorithm 10. +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, +// Algorithm 7. +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 := byteorder.LEUint16(buf[off:]) & 0b1111_1111_1111 + d2 := byteorder.LEUint16(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 +} \ No newline at end of file diff --git a/mlkem/mlkem768.go b/mlkem/mlkem768.go new file mode 100644 index 0000000..2b4fc12 --- /dev/null +++ b/mlkem/mlkem768.go @@ -0,0 +1,517 @@ +// 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 mlkem implements the quantum-resistant key encapsulation method +// ML-KEM (formerly known as Kyber), as specified in [NIST FIPS 203]. +// +// [NIST FIPS 203]: https://doi.org/10.6028/NIST.FIPS.203 +package mlkem + +// 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 document. +// +// Reviewers unfamiliar with polynomials or linear algebra might find the +// background at https://words.filippo.io/kyber-math/ useful. +// +// This file implements the recommended parameter set ML-KEM-768. The ML-KEM-1024 +// parameter set implementation is auto-generated from this file. +// +//go:generate go run generate1024.go -input mlkem768.go -output mlkem1024.go + +import ( + "bytes" + //"github.com/xtls/reality/fips140" + "github.com/xtls/reality/drbg" + "github.com/xtls/reality/sha3" + "github.com/xtls/reality/subtle" + "errors" +) + +const ( + // ML-KEM global constants. + n = 256 + q = 3329 + + // encodingSizeX is the byte size of a ringElement or nttElement encoded + // by ByteEncode_X (FIPS 203, Algorithm 5). + encodingSize12 = n * 12 / 8 + encodingSize11 = n * 11 / 8 + encodingSize10 = n * 10 / 8 + encodingSize5 = n * 5 / 8 + encodingSize4 = n * 4 / 8 + encodingSize1 = n * 1 / 8 + + messageSize = encodingSize1 + + SharedKeySize = 32 + SeedSize = 32 + 32 +) + +// ML-KEM-768 parameters. +const ( + k = 3 + + CiphertextSize768 = k*encodingSize10 + encodingSize4 + EncapsulationKeySize768 = k*encodingSize12 + 32 + decapsulationKeySize768 = k*encodingSize12 + EncapsulationKeySize768 + 32 + 32 +) + +// ML-KEM-1024 parameters. +const ( + k1024 = 4 + + CiphertextSize1024 = k1024*encodingSize11 + encodingSize5 + EncapsulationKeySize1024 = k1024*encodingSize12 + 32 + decapsulationKeySize1024 = k1024*encodingSize12 + EncapsulationKeySize1024 + 32 + 32 +) + +// A DecapsulationKey768 is the secret key used to decapsulate a shared key from a +// ciphertext. It includes various precomputed values. +type DecapsulationKey768 struct { + d [32]byte // decapsulation key seed + z [32]byte // implicit rejection sampling seed + + ρ [32]byte // sampleNTT seed for A, stored for the encapsulation key + h [32]byte // H(ek), stored for ML-KEM.Decaps_internal + + encryptionKey + decryptionKey +} + +// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. +// +// The decapsulation key must be kept secret. +func (dk *DecapsulationKey768) Bytes() []byte { + var b [SeedSize]byte + copy(b[:], dk.d[:]) + copy(b[32:], dk.z[:]) + return b[:] +} + +// TestingOnlyExpandedBytes768 returns the decapsulation key as a byte slice +// using the full expanded NIST encoding. +// +// This should only be used for ACVP testing. For all other purposes prefer +// the Bytes method that returns the (much smaller) seed. +func TestingOnlyExpandedBytes768(dk *DecapsulationKey768) []byte { + b := make([]byte, 0, decapsulationKeySize768) + + // ByteEncode₁₂(s) + for i := range dk.s { + b = polyByteEncode(b, dk.s[i]) + } + + // ByteEncode₁₂(t) || ρ + for i := range dk.t { + b = polyByteEncode(b, dk.t[i]) + } + b = append(b, dk.ρ[:]...) + + // H(ek) || z + b = append(b, dk.h[:]...) + b = append(b, dk.z[:]...) + + return b +} + +// EncapsulationKey returns the public encapsulation key necessary to produce +// ciphertexts. +func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 { + return &EncapsulationKey768{ + ρ: dk.ρ, + h: dk.h, + encryptionKey: dk.encryptionKey, + } +} + +// An EncapsulationKey768 is the public key used to produce ciphertexts to be +// decapsulated by the corresponding [DecapsulationKey768]. +type EncapsulationKey768 struct { + ρ [32]byte // sampleNTT seed for A + h [32]byte // H(ek) + encryptionKey +} + +// Bytes returns the encapsulation key as a byte slice. +func (ek *EncapsulationKey768) Bytes() []byte { + // The actual logic is in a separate function to outline this allocation. + b := make([]byte, 0, EncapsulationKeySize768) + return ek.bytes(b) +} + +func (ek *EncapsulationKey768) bytes(b []byte) []byte { + for i := range ek.t { + b = polyByteEncode(b, ek.t[i]) + } + b = append(b, ek.ρ[:]...) + 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]) +} + +// GenerateKey768 generates a new decapsulation key, drawing random bytes from +// a DRBG. The decapsulation key must be kept secret. +func GenerateKey768() (*DecapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey768{} + return generateKey(dk) +} + +func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) { + var d [32]byte + drbg.Read(d[:]) + var z [32]byte + drbg.Read(z[:]) + kemKeyGen(dk, &d, &z) + // if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { + // // This clearly can't happen, but FIPS 140-3 requires us to check. + // panic(err) + // } + //fips140.RecordApproved() + return dk, nil +} + +// GenerateKeyInternal768 is a derandomized version of GenerateKey768, +// exclusively for use in tests. +func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 { + dk := &DecapsulationKey768{} + kemKeyGen(dk, d, z) + return dk +} + +// NewDecapsulationKey768 parses a decapsulation key from a 64-byte +// seed in the "d || z" form. The seed must be uniformly random. +func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey768{} + return newKeyFromSeed(dk, seed) +} + +func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768, error) { + if len(seed) != SeedSize { + return nil, errors.New("mlkem: invalid seed length") + } + d := (*[32]byte)(seed[:32]) + z := (*[32]byte)(seed[32:]) + kemKeyGen(dk, d, z) + // if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { + // // This clearly can't happen, but FIPS 140-3 requires us to check. + // panic(err) + // } + //fips140.RecordApproved() + return dk, nil +} + +// TestingOnlyNewDecapsulationKey768 parses a decapsulation key from its expanded NIST format. +// +// Bytes() must not be called on the returned key, as it will not produce the +// original seed. +// +// This function should only be used for ACVP testing. Prefer NewDecapsulationKey768 for all +// other purposes. +func TestingOnlyNewDecapsulationKey768(b []byte) (*DecapsulationKey768, error) { + if len(b) != decapsulationKeySize768 { + return nil, errors.New("mlkem: invalid NIST decapsulation key length") + } + + dk := &DecapsulationKey768{} + for i := range dk.s { + var err error + dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12]) + if err != nil { + return nil, errors.New("mlkem: invalid secret key encoding") + } + b = b[encodingSize12:] + } + + ek, err := NewEncapsulationKey768(b[:EncapsulationKeySize768]) + if err != nil { + return nil, err + } + dk.ρ = ek.ρ + dk.h = ek.h + dk.encryptionKey = ek.encryptionKey + b = b[EncapsulationKeySize768:] + + if !bytes.Equal(dk.h[:], b[:32]) { + return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes") + } + b = b[32:] + + copy(dk.z[:], b) + + // Generate a random d value for use in Bytes(). This is a safety mechanism + // that avoids returning a broken key vs a random key if this function is + // called in contravention of the TestingOnlyNewDecapsulationKey768 function + // comment advising against it. + drbg.Read(dk.d[:]) + + return dk, nil +} + +// kemKeyGen generates a decapsulation key. +// +// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and +// K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save +// copies and allocations. +func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) { + dk.d = *d + dk.z = *z + + g := sha3.New512() + g.Write(d[:]) + g.Write([]byte{k}) // Module dimension as a domain separator. + G := g.Sum(make([]byte, 0, 64)) + ρ, σ := G[:32], G[32:] + dk.ρ = [32]byte(ρ) + + A := &dk.a + for i := byte(0); i < k; i++ { + for j := byte(0); j < k; j++ { + 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])) + } + } + + H := sha3.New256() + ek := dk.EncapsulationKey().Bytes() + H.Write(ek) + H.Sum(dk.h[:0]) +} + +// kemPCT performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A +// Additional Comment 1: "For key pairs generated for use with approved KEMs in +// FIPS 203, the PCT shall consist of applying the encapsulation key ek to +// encapsulate a shared secret K leading to ciphertext c, and then applying +// decapsulation key dk to retrieve the same shared secret K. The PCT passes if +// the two shared secret K values are equal. The PCT shall be performed either +// when keys are generated/imported, prior to the first exportation, or prior to +// the first operational use (if not exported before the first use)." +func kemPCT(dk *DecapsulationKey768) error { + ek := dk.EncapsulationKey() + K, c := ek.Encapsulate() + K1, err := dk.Decapsulate(c) + if err != nil { + return err + } + if subtle.ConstantTimeCompare(K, K1) != 1 { + return errors.New("mlkem: PCT failed") + } + return nil +} + +// Encapsulate generates a shared key and an associated ciphertext from an +// encapsulation key, drawing random bytes from a DRBG. +// +// The shared key must be kept secret. +func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) { + // The actual logic is in a separate function to outline this allocation. + var cc [CiphertextSize768]byte + return ek.encapsulate(&cc) +} + +func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (sharedKey, ciphertext []byte) { + var m [messageSize]byte + drbg.Read(m[:]) + // Note that the modulus check (step 2 of the encapsulation key check from + // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK. + //fips140.RecordApproved() + return kemEncaps(cc, ek, &m) +} + +// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for +// use in tests. +func (ek *EncapsulationKey768) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) { + cc := &[CiphertextSize768]byte{} + return kemEncaps(cc, ek, m) +} + +// kemEncaps generates a shared key and an associated ciphertext. +// +// It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17. +func kemEncaps(cc *[CiphertextSize768]byte, ek *EncapsulationKey768, m *[messageSize]byte) (K, c []byte) { + g := sha3.New512() + g.Write(m[:]) + g.Write(ek.h[:]) + G := g.Sum(nil) + K, r := G[:SharedKeySize], G[SharedKeySize:] + c = pkeEncrypt(cc, &ek.encryptionKey, m, r) + return K, c +} + +// NewEncapsulationKey768 parses an encapsulation key from its encoded form. +// If the encapsulation key is not valid, NewEncapsulationKey768 returns an error. +func NewEncapsulationKey768(encapsulationKey []byte) (*EncapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + ek := &EncapsulationKey768{} + return parseEK(ek, encapsulationKey) +} + +// parseEK parses an encryption key from its encoded form. +// +// It implements the initial stages of K-PKE.Encrypt according to FIPS 203, +// Algorithm 14. +func parseEK(ek *EncapsulationKey768, ekPKE []byte) (*EncapsulationKey768, error) { + if len(ekPKE) != EncapsulationKeySize768 { + return nil, errors.New("mlkem: invalid encapsulation key length") + } + + h := sha3.New256() + h.Write(ekPKE) + h.Sum(ek.h[:0]) + + for i := range ek.t { + var err error + ek.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) + if err != nil { + return nil, err + } + ekPKE = ekPKE[encodingSize12:] + } + copy(ek.ρ[:], ekPKE) + + for i := byte(0); i < k; i++ { + for j := byte(0); j < k; j++ { + ek.a[i*k+j] = sampleNTT(ek.ρ[:], j, i) + } + } + + return ek, nil +} + +// pkeEncrypt encrypt a plaintext message. +// +// It implements K-PKE.Encrypt according to FIPS 203, Algorithm 14, although the +// computation of t and AT is done in parseEK. +func pkeEncrypt(cc *[CiphertextSize768]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 (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { + if len(ciphertext) != CiphertextSize768 { + return nil, errors.New("mlkem: invalid ciphertext length") + } + c := (*[CiphertextSize768]byte)(ciphertext) + // Note that the hash check (step 3 of the decapsulation input check from + // FIPS 203, Section 7.3) is foregone as a DecapsulationKey is always + // validly generated by ML-KEM.KeyGen_internal. + return kemDecaps(dk, c), nil +} + +// kemDecaps produces a shared key from a ciphertext. +// +// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18. +func kemDecaps(dk *DecapsulationKey768, c *[CiphertextSize768]byte) (K []byte) { + //fips140.RecordApproved() + m := pkeDecrypt(&dk.decryptionKey, c) + g := sha3.New512() + g.Write(m[:]) + g.Write(dk.h[:]) + G := g.Sum(make([]byte, 0, 64)) + Kprime, r := G[:SharedKeySize], G[SharedKeySize:] + J := sha3.NewShake256() + J.Write(dk.z[:]) + J.Write(c[:]) + Kout := make([]byte, SharedKeySize) + J.Read(Kout) + var cc [CiphertextSize768]byte + c1 := pkeEncrypt(&cc, &dk.encryptionKey, (*[32]byte)(m), r) + + subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) + return Kout +} + +// pkeDecrypt decrypts a ciphertext. +// +// It implements K-PKE.Decrypt according to FIPS 203, Algorithm 15, +// although s is retained from kemKeyGen. +func pkeDecrypt(dx *decryptionKey, c *[CiphertextSize768]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) +} \ No newline at end of file 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/randutil/randutil.go b/randutil/randutil.go new file mode 100644 index 0000000..70a9e96 --- /dev/null +++ b/randutil/randutil.go @@ -0,0 +1,26 @@ +// 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 randutil contains internal randomness utilities for various +// crypto packages. +package randutil + +import ( + "io" + "math/rand/v2" +) + +// MaybeReadByte reads a single byte from r with 50% probability. This is used +// to ensure that callers do not depend on non-guaranteed behaviour, e.g. +// assuming that rsa.GenerateKey is deterministic w.r.t. a given random stream. +// +// This does not affect tests that pass a stream of fixed bytes as the random +// source (e.g. a zeroReader). +func MaybeReadByte(r io.Reader) { + if rand.Uint64()&1 == 1 { + return + } + var buf [1]byte + r.Read(buf[:]) +} \ No newline at end of file diff --git a/sha3/shake.go b/sha3/shake.go new file mode 100644 index 0000000..d4329ea --- /dev/null +++ b/sha3/shake.go @@ -0,0 +1,151 @@ +// Copyright 2014 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 sha3 + +import ( + "bytes" + //"crypto/internal/fips140" + "github.com/xtls/reality/byteorder" + "errors" + "math/bits" +) + +type SHAKE struct { + d Digest // SHA-3 state context and Read/Write operations + + // initBlock is the cSHAKE specific initialization set of bytes. It is initialized + // by newCShake function and stores concatenation of N followed by S, encoded + // by the method specified in 3.3 of [1]. + // It is stored here in order for Reset() to be able to put context into + // initial state. + initBlock []byte +} + +func bytepad(data []byte, rate int) []byte { + out := make([]byte, 0, 9+len(data)+rate-1) + out = append(out, leftEncode(uint64(rate))...) + out = append(out, data...) + if padlen := rate - len(out)%rate; padlen < rate { + out = append(out, make([]byte, padlen)...) + } + return out +} + +func leftEncode(x uint64) []byte { + // Let n be the smallest positive integer for which 2^(8n) > x. + n := (bits.Len64(x) + 7) / 8 + if n == 0 { + n = 1 + } + // Return n || x with n as a byte and x an n bytes in big-endian order. + b := make([]byte, 9) + byteorder.BEPutUint64(b[1:], x) + b = b[9-n-1:] + b[0] = byte(n) + return b +} + +func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) *SHAKE { + c := &SHAKE{d: Digest{rate: rate, outputLen: outputLen, dsbyte: dsbyte}} + c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes + c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...) + c.initBlock = append(c.initBlock, N...) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...) + c.initBlock = append(c.initBlock, S...) + c.Write(bytepad(c.initBlock, c.d.rate)) + return c +} + +func (s *SHAKE) BlockSize() int { return s.d.BlockSize() } +func (s *SHAKE) Size() int { return s.d.Size() } + +// Sum appends a portion of output to b and returns the resulting slice. The +// output length is selected to provide full-strength generic security: 32 bytes +// for SHAKE128 and 64 bytes for SHAKE256. It does not change the underlying +// state. It panics if any output has already been read. +func (s *SHAKE) Sum(in []byte) []byte { return s.d.Sum(in) } + +// Write absorbs more data into the hash's state. +// It panics if any output has already been read. +func (s *SHAKE) Write(p []byte) (n int, err error) { return s.d.Write(p) } + +func (s *SHAKE) Read(out []byte) (n int, err error) { + //fips140.RecordApproved() + // Note that read is not exposed on Digest since SHA-3 does not offer + // variable output length. It is only used internally by Sum. + return s.d.read(out) +} + +// Reset resets the hash to initial state. +func (s *SHAKE) Reset() { + s.d.Reset() + if len(s.initBlock) != 0 { + s.Write(bytepad(s.initBlock, s.d.rate)) + } +} + +// Clone returns a copy of the SHAKE context in its current state. +func (s *SHAKE) Clone() *SHAKE { + ret := *s + return &ret +} + +func (s *SHAKE) MarshalBinary() ([]byte, error) { + return s.AppendBinary(make([]byte, 0, marshaledSize+len(s.initBlock))) +} + +func (s *SHAKE) AppendBinary(b []byte) ([]byte, error) { + b, err := s.d.AppendBinary(b) + if err != nil { + return nil, err + } + b = append(b, s.initBlock...) + return b, nil +} + +func (s *SHAKE) UnmarshalBinary(b []byte) error { + if len(b) < marshaledSize { + return errors.New("sha3: invalid hash state") + } + if err := s.d.UnmarshalBinary(b[:marshaledSize]); err != nil { + return err + } + s.initBlock = bytes.Clone(b[marshaledSize:]) + return nil +} + +// NewShake128 creates a new SHAKE128 XOF. +func NewShake128() *SHAKE { + return &SHAKE{d: Digest{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}} +} + +// NewShake256 creates a new SHAKE256 XOF. +func NewShake256() *SHAKE { + return &SHAKE{d: Digest{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}} +} + +// NewCShake128 creates a new cSHAKE128 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewShake128. +func NewCShake128(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewShake128() + } + return newCShake(N, S, rateK256, 32, dsbyteCShake) +} + +// NewCShake256 creates a new cSHAKE256 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewShake256. +func NewCShake256(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewShake256() + } + return newCShake(N, S, rateK512, 64, dsbyteCShake) +} \ No newline at end of file From 6c71b461eed3d664474f2398911d1ad39854aec1 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 22:15:27 -0400 Subject: [PATCH 08/45] crypto/internal/fips/tls12: implement TLS 1.2 KDF For #69536 Change-Id: If2477c5249a7c7db45c1af05e715ae0b61e7d940 Reviewed-on: https://go-review.googlesource.com/c/go/+/626837 Reviewed-by: Roland Shoemaker Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Michael Knyszek Reviewed-by: Russ Cox --- prf.go | 64 ++++++++++++++++++++++----------------------- tls12/tls12.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 tls12/tls12.go 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/tls12/tls12.go b/tls12/tls12.go new file mode 100644 index 0000000..998ec87 --- /dev/null +++ b/tls12/tls12.go @@ -0,0 +1,70 @@ +// 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 ( + "github.com/xtls/reality/fips140" + "github.com/xtls/reality/hmac" + // "github.com/xtls/reality/sha256" + // "github.com/xtls/reality/sha512" +) + +// 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 fips140.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 fips140.Hash](hash func() H, result, secret, seed []byte) { + h := hmac.New(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 fips140.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 From 0f3d0f2e71f6790cc0edcb1565902f92b12f60b0 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 22:35:51 -0400 Subject: [PATCH 09/45] internal/byteorder: use canonical Go casing in names If Be and Le stand for big-endian and little-endian, then they should be BE and LE. Change-Id: I723e3962b8918da84791783d3c547638f1c9e8a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/627376 Reviewed-by: Robert Griesemer Auto-Submit: Russ Cox LUCI-TryBot-Result: Go LUCI --- handshake_client.go | 4 ++-- handshake_server.go | 5 +++-- handshake_server_tls13.go | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/handshake_client.go b/handshake_client.go index 9cd987a..e6a9420 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -13,7 +13,6 @@ import ( "crypto/rsa" "crypto/subtle" "crypto/x509" - "encoding/binary" "errors" "fmt" "hash" @@ -22,6 +21,7 @@ import ( "strings" "time" + "github.com/xtls/reality/byteorder" "github.com/xtls/reality/hpke" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" @@ -707,7 +707,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { return err } if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(binary.BigEndian.Uint16(skx.key[1:])) + c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) } msg, err = c.readHandshake(&hs.finishedHash) diff --git a/handshake_server.go b/handshake_server.go index dbc0551..3201520 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -12,12 +12,13 @@ import ( "crypto/rsa" "crypto/subtle" "crypto/x509" - "encoding/binary" "errors" "fmt" "hash" "io" "time" + + "github.com/xtls/reality/byteorder" ) // serverHandshakeState contains details of a server handshake in progress. @@ -579,7 +580,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } if skx != nil { if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(binary.BigEndian.Uint16(skx.key[1:])) + c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) } if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil { return err diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 1dce1c7..3f0f465 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -14,7 +14,6 @@ import ( "crypto/rsa" "crypto/sha512" "crypto/x509" - "encoding/binary" "errors" "hash" "io" @@ -22,6 +21,7 @@ import ( "slices" "time" + "github.com/xtls/reality/byteorder" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -953,7 +953,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error { if _, err := c.config.rand().Read(ageAdd); err != nil { return err } - m.ageAdd = binary.LittleEndian.Uint32(ageAdd) + m.ageAdd = byteorder.LEUint32(ageAdd) if earlyData { // RFC 9001, Section 4.6.1 From 9cb8914b9ffbeb5427459ad62d5b3bc55d47818a Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 23:10:11 -0400 Subject: [PATCH 10/45] crypto/tls: FIPS 140-3 mode Consolidates handling of FIPS 140-3 considerations for the tls package. Considerations specific to certificates are now handled in tls instead of x509 to limit the area-of-effect of FIPS as much as possible. Boringcrypto specific prefixes are renamed as appropriate. For #69536 Co-authored-by: Filippo Valsorda Change-Id: I1b1fef83c3599e4c9b98ad81db582ac93253030b Reviewed-on: https://go-review.googlesource.com/c/go/+/629675 Reviewed-by: Filippo Valsorda Reviewed-by: Dmitri Shuralyov Reviewed-by: Russ Cox Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: Go LUCI --- auth.go | 4 ++- common.go | 67 +++++++++++++++++++++++++++++++++++---- defaults.go | 4 ++- drbg/rand.go | 9 +++--- fips140tls/fipstls.go | 37 +++++++++++++++++++++ go.mod | 8 ++--- go.sum | 6 ++++ handshake_client.go | 21 +++++++++--- handshake_server.go | 6 +++- handshake_server_tls13.go | 3 +- notboring.go | 9 ------ 11 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 fips140tls/fipstls.go delete mode 100644 notboring.go diff --git a/auth.go b/auth.go index d595925..c389cd4 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,8 @@ import ( "fmt" "hash" "io" + + "github.com/xtls/reality/fips140tls" ) // verifyHandshakeSignature verifies a signature against pre-hashed @@ -242,7 +244,7 @@ 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) { + if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { continue } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { diff --git a/common.go b/common.go index 895ee85..61381c9 100644 --- a/common.go +++ b/common.go @@ -25,6 +25,8 @@ import ( "sync" "time" _ "unsafe" // for linkname + + "github.com/xtls/reality/fips140tls" ) const ( @@ -1085,12 +1087,12 @@ func (c *Config) time() time.Time { func (c *Config) cipherSuites() []uint16 { if c.CipherSuites == nil { - if needFIPS() { + if fips140tls.Required() { return defaultCipherSuitesFIPS } return defaultCipherSuites() } - if needFIPS() { + if fips140tls.Required() { cipherSuites := slices.Clone(c.CipherSuites) return slices.DeleteFunc(cipherSuites, func(id uint16) bool { return !slices.Contains(defaultCipherSuitesFIPS, id) @@ -1114,7 +1116,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(defaultSupportedVersionsFIPS, v) { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { @@ -1160,12 +1162,12 @@ func (c *Config) curvePreferences(version uint16) []CurveID { var curvePreferences []CurveID if c != nil && len(c.CurvePreferences) != 0 { curvePreferences = slices.Clone(c.CurvePreferences) - if needFIPS() { + if fips140tls.Required() { return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { return !slices.Contains(defaultCurvePreferencesFIPS, c) }) } - } else if needFIPS() { + } else if fips140tls.Required() { curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) } else { curvePreferences = defaultCurvePreferences() @@ -1637,7 +1639,7 @@ func unexpectedMessageError(wanted, got any) error { // supportedSignatureAlgorithms returns the supported signature algorithms. func supportedSignatureAlgorithms() []SignatureScheme { - if !needFIPS() { + if !fips140tls.Required() { return defaultSupportedSignatureAlgorithms } return defaultSupportedSignatureAlgorithmsFIPS @@ -1666,3 +1668,56 @@ 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 !fipsAllowCert(cert) { + return false + } + } + + return true +} + +func fipsAllowCert(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.go b/defaults.go index 19dc10e..f2d9ff3 100644 --- a/defaults.go +++ b/defaults.go @@ -89,7 +89,9 @@ var defaultCipherSuitesTLS13NoAES = []uint16{ TLS_AES_256_GCM_SHA384, } -// The FIPS-only policies below match BoringSSL's ssl_policy_fips_202205. +// The FIPS-only policies below 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 var defaultSupportedVersionsFIPS = []uint16{ VersionTLS12, diff --git a/drbg/rand.go b/drbg/rand.go index 7fe0b57..a3255fe 100644 --- a/drbg/rand.go +++ b/drbg/rand.go @@ -14,6 +14,7 @@ import ( "github.com/xtls/reality/randutil" // "github.com/xtls/reality/sysrand" + "crypto/fips140" "crypto/rand" "io" "sync" @@ -33,10 +34,10 @@ var drbgs = sync.Pool{ // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // Otherwise, it uses the operating system's random number generator. func Read(b []byte) { - // if !fips140.Enabled { - // rand.Read(b) - // return - // } + if !fips140.Enabled() { + rand.Read(b) + return + } // At every read, 128 random bits from the operating system are mixed as // additional input, to make the output as strong as non-FIPS randomness. 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/go.mod b/go.mod index f3e384f..ca25db1 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.37.0 + golang.org/x/sys v0.32.0 ) diff --git a/go.sum b/go.sum index 68bcfd9..be439aa 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ 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/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= diff --git a/handshake_client.go b/handshake_client.go index e6a9420..781cfb7 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -22,6 +22,7 @@ import ( "time" "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/hpke" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" @@ -142,7 +143,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if len(hello.supportedVersions) == 1 { hello.cipherSuites = nil } - if needFIPS() { + if fips140tls.Required() { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) @@ -1105,8 +1106,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} @@ -1123,8 +1129,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_server.go b/handshake_server.go index 3201520..6958c95 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -910,7 +910,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 3f0f465..be75a1f 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -22,6 +22,7 @@ import ( "time" "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -215,7 +216,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { preferenceList = defaultCipherSuitesTLS13NoAES } - if needFIPS() { + if fips140tls.Required() { preferenceList = defaultCipherSuitesTLS13FIPS } for _, suiteID := range preferenceList { 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 } From c14471f84318d07fe45d4de8c17e1b3b9256f4e4 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 23:11:48 -0400 Subject: [PATCH 11/45] all: fix some function names and typos in comment Change-Id: I07e7c8eaa5bd4bac0d576b2f2f4cd3f81b0b77a4 Reviewed-on: https://go-review.googlesource.com/c/go/+/630055 LUCI-TryBot-Result: Go LUCI Commit-Queue: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Reviewed-by: Russ Cox Auto-Submit: Ian Lance Taylor --- common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.go b/common.go index 61381c9..d088869 100644 --- a/common.go +++ b/common.go @@ -815,7 +815,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. // From 02afebcf3062d9d8ab79f75cc55281d5b98af23e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 4 May 2025 23:20:28 -0400 Subject: [PATCH 12/45] Update hpye.go to 760f228 --- hpke/hpye.go | 237 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 70 deletions(-) diff --git a/hpke/hpye.go b/hpke/hpye.go index 611c89a..b546ddc 100644 --- a/hpke/hpye.go +++ b/hpke/hpye.go @@ -9,13 +9,14 @@ 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" + + "github.com/xtls/reality/byteorder" ) // testingOnlyGenerateKey is only used during testing, to provide @@ -26,28 +27,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 = byteorder.BEAppendUint16(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 +55,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) { @@ -76,13 +76,16 @@ func newDHKem(kemID uint16) (*dhKEM, error) { return &dhKEM{ dh: suite.curve, kdf: hkdfKDF{suite.hash}, - suiteID: binary.BigEndian.AppendUint16([]byte("KEM"), kemID), + suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID), nSecret: suite.nSecret, }, 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 +107,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 +141,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,102 +157,165 @@ 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) - suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID) - suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID) + suiteID = byteorder.BEAppendUint16(suiteID, kemID) + suiteID = byteorder.BEAppendUint16(suiteID, kdfID) + suiteID = byteorder.BEAppendUint16(suiteID, aeadID) return suiteID } @@ -238,6 +327,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 } @@ -253,7 +350,7 @@ func (u uint128) bitLen() int { func (u uint128) bytes() []byte { b := make([]byte, 16) - binary.BigEndian.PutUint64(b[0:], u.hi) - binary.BigEndian.PutUint64(b[8:], u.lo) + byteorder.BEPutUint64(b[0:], u.hi) + byteorder.BEPutUint64(b[8:], u.lo) return b -} +} \ No newline at end of file From 32d3673ce6fd3871cb48464d479b71341199014d Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Mon, 5 May 2025 00:12:49 -0400 Subject: [PATCH 13/45] Simplify with public modules --- aes/aes.go | 2 - aes/aes_generic.go | 36 +-- aes/ctr.go | 148 ---------- aes/ctr_noasm.go | 21 -- byteorder/byteorder.go | 149 ---------- drbg/ctrdrbg.go | 143 ---------- drbg/rand.go | 103 ------- entropy/entropy.go | 29 -- fips140/hash.go | 32 --- gcm/gcm.go | 2 - gcm/gcm_generic.go | 13 +- gcm/gcm_nonces.go | 18 +- gcm/ghash.go | 15 +- handshake_client.go | 6 +- handshake_client_tls13.go | 2 +- handshake_server.go | 5 +- handshake_server_tls13.go | 8 +- hkdf/hkdf.go | 56 ---- hmac/hmac.go | 172 ----------- hpke/hpye.go | 17 +- key_schedule.go | 6 +- mlkem/field.go | 550 ------------------------------------ mlkem/mlkem768.go | 517 --------------------------------- randutil/randutil.go | 26 -- sha256/sha256.go | 231 --------------- sha256/sha256block.go | 128 --------- sha256/sha256block_noasm.go | 9 - sha3/hashes.go | 59 ---- sha3/keccakf.go | 434 ---------------------------- sha3/sha3.go | 235 --------------- sha3/sha3_noasm.go | 19 -- sha3/shake.go | 151 ---------- sha512/sha512.go | 301 -------------------- sha512/sha512block.go | 144 ---------- sha512/sha512block_noasm.go | 9 - subtle/constant_time.go | 97 ------- subtle/xor.go | 30 -- subtle/xor_generic.go | 62 ---- tls12/tls12.go | 14 +- tls13/tls13.go | 50 ++-- 40 files changed, 92 insertions(+), 3957 deletions(-) delete mode 100644 aes/ctr.go delete mode 100644 aes/ctr_noasm.go delete mode 100644 byteorder/byteorder.go delete mode 100644 drbg/ctrdrbg.go delete mode 100644 drbg/rand.go delete mode 100644 entropy/entropy.go delete mode 100644 fips140/hash.go delete mode 100644 hkdf/hkdf.go delete mode 100644 hmac/hmac.go delete mode 100644 mlkem/field.go delete mode 100644 mlkem/mlkem768.go delete mode 100644 randutil/randutil.go delete mode 100644 sha256/sha256.go delete mode 100644 sha256/sha256block.go delete mode 100644 sha256/sha256block_noasm.go delete mode 100644 sha3/hashes.go delete mode 100644 sha3/keccakf.go delete mode 100644 sha3/sha3.go delete mode 100644 sha3/sha3_noasm.go delete mode 100644 sha3/shake.go delete mode 100644 sha512/sha512.go delete mode 100644 sha512/sha512block.go delete mode 100644 sha512/sha512block_noasm.go delete mode 100644 subtle/constant_time.go delete mode 100644 subtle/xor.go delete mode 100644 subtle/xor_generic.go diff --git a/aes/aes.go b/aes/aes.go index 432251e..67e3484 100644 --- a/aes/aes.go +++ b/aes/aes.go @@ -5,8 +5,6 @@ package aes import ( - // "crypto/internal/fips140" - // "crypto/internal/fips140/alias" "strconv" "github.com/xtls/reality/alias" diff --git a/aes/aes_generic.go b/aes/aes_generic.go index 8a9a4d2..a269ae6 100644 --- a/aes/aes_generic.go +++ b/aes/aes_generic.go @@ -36,7 +36,7 @@ package aes -import "github.com/xtls/reality/byteorder" +import "encoding/binary" // Encrypt one block from src into dst, using the expanded key xk. func encryptBlockGeneric(c *blockExpanded, dst, src []byte) { @@ -44,10 +44,10 @@ func encryptBlockGeneric(c *blockExpanded, dst, src []byte) { xk := c.enc[:] _ = src[15] // early bounds check - s0 := byteorder.BEUint32(src[0:4]) - s1 := byteorder.BEUint32(src[4:8]) - s2 := byteorder.BEUint32(src[8:12]) - s3 := byteorder.BEUint32(src[12:16]) + 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] @@ -79,10 +79,10 @@ func encryptBlockGeneric(c *blockExpanded, dst, src []byte) { s3 ^= xk[k+3] _ = dst[15] // early bounds check - byteorder.BEPutUint32(dst[0:4], s0) - byteorder.BEPutUint32(dst[4:8], s1) - byteorder.BEPutUint32(dst[8:12], s2) - byteorder.BEPutUint32(dst[12:16], s3) + 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. @@ -91,10 +91,10 @@ func decryptBlockGeneric(c *blockExpanded, dst, src []byte) { xk := c.dec[:] _ = src[15] // early bounds check - s0 := byteorder.BEUint32(src[0:4]) - s1 := byteorder.BEUint32(src[4:8]) - s2 := byteorder.BEUint32(src[8:12]) - s3 := byteorder.BEUint32(src[12:16]) + 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] @@ -126,10 +126,10 @@ func decryptBlockGeneric(c *blockExpanded, dst, src []byte) { s3 ^= xk[k+3] _ = dst[15] // early bounds check - byteorder.BEPutUint32(dst[0:4], s0) - byteorder.BEPutUint32(dst[4:8], s1) - byteorder.BEPutUint32(dst[8:12], s2) - byteorder.BEPutUint32(dst[12:16], s3) + 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. @@ -152,7 +152,7 @@ func expandKeyGeneric(c *blockExpanded, key []byte) { var i int nk := len(key) / 4 for i = 0; i < nk; i++ { - c.enc[i] = byteorder.BEUint32(key[4*i:]) + c.enc[i] = binary.BigEndian.Uint32(key[4*i:]) } for ; i < c.roundKeysSize(); i++ { t := c.enc[i-1] diff --git a/aes/ctr.go b/aes/ctr.go deleted file mode 100644 index 89e2305..0000000 --- a/aes/ctr.go +++ /dev/null @@ -1,148 +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 aes - -import ( - //"crypto/internal/fips140" - "github.com/xtls/reality/alias" - "github.com/xtls/reality/subtle" - "github.com/xtls/reality/byteorder" - "math/bits" -) - -type CTR struct { - b Block - ivlo, ivhi uint64 // start counter as 64-bit limbs - offset uint64 // for XORKeyStream only -} - -func NewCTR(b *Block, iv []byte) *CTR { - // Allocate the CTR here, in an easily inlineable function, so - // the allocation can be done in the caller's stack frame - // instead of the heap. See issue 70499. - c := newCTR(b, iv) - return &c -} -func newCTR(b *Block, iv []byte) CTR { - if len(iv) != BlockSize { - panic("bad IV length") - } - - return CTR{ - b: *b, - ivlo: byteorder.BEUint64(iv[8:16]), - ivhi: byteorder.BEUint64(iv[0:8]), - offset: 0, - } -} - -func (c *CTR) XORKeyStream(dst, src []byte) { - c.XORKeyStreamAt(dst, src, c.offset) - - var carry uint64 - c.offset, carry = bits.Add64(c.offset, uint64(len(src)), 0) - if carry != 0 { - panic("crypto/aes: counter overflow") - } -} - -// RoundToBlock is used by CTR_DRBG, which discards the rightmost unused bits at -// each request. It rounds the offset up to the next block boundary. -func RoundToBlock(c *CTR) { - if remainder := c.offset % BlockSize; remainder != 0 { - var carry uint64 - c.offset, carry = bits.Add64(c.offset, BlockSize-remainder, 0) - if carry != 0 { - panic("crypto/aes: counter overflow") - } - } -} - -// XORKeyStreamAt behaves like XORKeyStream but keeps no state, and instead -// seeks into the keystream by the given bytes offset from the start (ignoring -// any XORKetStream calls). This allows for random access into the keystream, up -// to 16 EiB from the start. -func (c *CTR) XORKeyStreamAt(dst, src []byte, offset uint64) { - if len(dst) < len(src) { - panic("crypto/aes: len(dst) < len(src)") - } - dst = dst[:len(src)] - if alias.InexactOverlap(dst, src) { - panic("crypto/aes: invalid buffer overlap") - } - //fips140.RecordApproved() - - ivlo, ivhi := add128(c.ivlo, c.ivhi, offset/BlockSize) - - if blockOffset := offset % BlockSize; blockOffset != 0 { - // We have a partial block at the beginning. - var in, out [BlockSize]byte - copy(in[blockOffset:], src) - ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) - n := copy(dst, out[blockOffset:]) - src = src[n:] - dst = dst[n:] - ivlo, ivhi = add128(ivlo, ivhi, 1) - } - - for len(src) >= 8*BlockSize { - ctrBlocks8(&c.b, (*[8 * BlockSize]byte)(dst), (*[8 * BlockSize]byte)(src), ivlo, ivhi) - src = src[8*BlockSize:] - dst = dst[8*BlockSize:] - ivlo, ivhi = add128(ivlo, ivhi, 8) - } - - // The tail can have at most 7 = 4 + 2 + 1 blocks. - if len(src) >= 4*BlockSize { - ctrBlocks4(&c.b, (*[4 * BlockSize]byte)(dst), (*[4 * BlockSize]byte)(src), ivlo, ivhi) - src = src[4*BlockSize:] - dst = dst[4*BlockSize:] - ivlo, ivhi = add128(ivlo, ivhi, 4) - } - if len(src) >= 2*BlockSize { - ctrBlocks2(&c.b, (*[2 * BlockSize]byte)(dst), (*[2 * BlockSize]byte)(src), ivlo, ivhi) - src = src[2*BlockSize:] - dst = dst[2*BlockSize:] - ivlo, ivhi = add128(ivlo, ivhi, 2) - } - if len(src) >= 1*BlockSize { - ctrBlocks1(&c.b, (*[1 * BlockSize]byte)(dst), (*[1 * BlockSize]byte)(src), ivlo, ivhi) - src = src[1*BlockSize:] - dst = dst[1*BlockSize:] - ivlo, ivhi = add128(ivlo, ivhi, 1) - } - - if len(src) != 0 { - // We have a partial block at the end. - var in, out [BlockSize]byte - copy(in[:], src) - ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) - copy(dst, out[:]) - } -} - -// Each ctrBlocksN function XORs src with N blocks of counter keystream, and -// stores it in dst. src is loaded in full before storing dst, so they can -// overlap even inexactly. The starting counter value is passed in as a pair of -// little-endian 64-bit integers. - -func ctrBlocks(b *Block, dst, src []byte, ivlo, ivhi uint64) { - buf := make([]byte, len(src), 8*BlockSize) - for i := 0; i < len(buf); i += BlockSize { - byteorder.BEPutUint64(buf[i:], ivhi) - byteorder.BEPutUint64(buf[i+8:], ivlo) - ivlo, ivhi = add128(ivlo, ivhi, 1) - encryptBlock(b, buf[i:], buf[i:]) - } - // XOR into buf first, in case src and dst overlap (see above). - subtle.XORBytes(buf, src, buf) - copy(dst, buf) -} - -func add128(lo, hi uint64, x uint64) (uint64, uint64) { - lo, c := bits.Add64(lo, x, 0) - hi, _ = bits.Add64(hi, 0, c) - return lo, hi -} \ No newline at end of file diff --git a/aes/ctr_noasm.go b/aes/ctr_noasm.go deleted file mode 100644 index e93fc80..0000000 --- a/aes/ctr_noasm.go +++ /dev/null @@ -1,21 +0,0 @@ -// 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 - -func ctrBlocks1(b *Block, dst, src *[BlockSize]byte, ivlo, ivhi uint64) { - ctrBlocks(b, dst[:], src[:], ivlo, ivhi) -} - -func ctrBlocks2(b *Block, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) { - ctrBlocks(b, dst[:], src[:], ivlo, ivhi) -} - -func ctrBlocks4(b *Block, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) { - ctrBlocks(b, dst[:], src[:], ivlo, ivhi) -} - -func ctrBlocks8(b *Block, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) { - ctrBlocks(b, dst[:], src[:], ivlo, ivhi) -} \ No newline at end of file diff --git a/byteorder/byteorder.go b/byteorder/byteorder.go deleted file mode 100644 index 0af00a4..0000000 --- a/byteorder/byteorder.go +++ /dev/null @@ -1,149 +0,0 @@ -// 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 byteorder provides functions for decoding and encoding -// little and big endian integer types from/to byte slices. -package byteorder - -func LEUint16(b []byte) uint16 { - _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 - return uint16(b[0]) | uint16(b[1])<<8 -} - -func LEPutUint16(b []byte, v uint16) { - _ = b[1] // early bounds check to guarantee safety of writes below - b[0] = byte(v) - b[1] = byte(v >> 8) -} - -func LEAppendUint16(b []byte, v uint16) []byte { - return append(b, - byte(v), - byte(v>>8), - ) -} - -func LEUint32(b []byte) uint32 { - _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 -} - -func LEPutUint32(b []byte, v uint32) { - _ = b[3] // early bounds check to guarantee safety of writes below - b[0] = byte(v) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) -} - -func LEAppendUint32(b []byte, v uint32) []byte { - return append(b, - byte(v), - byte(v>>8), - byte(v>>16), - byte(v>>24), - ) -} - -func LEUint64(b []byte) uint64 { - _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | - uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 -} - -func LEPutUint64(b []byte, v uint64) { - _ = b[7] // early bounds check to guarantee safety of writes below - b[0] = byte(v) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) - b[4] = byte(v >> 32) - b[5] = byte(v >> 40) - b[6] = byte(v >> 48) - b[7] = byte(v >> 56) -} - -func LEAppendUint64(b []byte, v uint64) []byte { - return append(b, - byte(v), - byte(v>>8), - byte(v>>16), - byte(v>>24), - byte(v>>32), - byte(v>>40), - byte(v>>48), - byte(v>>56), - ) -} - -func BEUint16(b []byte) uint16 { - _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 - return uint16(b[1]) | uint16(b[0])<<8 -} - -func BEPutUint16(b []byte, v uint16) { - _ = b[1] // early bounds check to guarantee safety of writes below - b[0] = byte(v >> 8) - b[1] = byte(v) -} - -func BEAppendUint16(b []byte, v uint16) []byte { - return append(b, - byte(v>>8), - byte(v), - ) -} - -func BEUint32(b []byte) uint32 { - _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 - return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 -} - -func BEPutUint32(b []byte, v uint32) { - _ = b[3] // early bounds check to guarantee safety of writes below - b[0] = byte(v >> 24) - b[1] = byte(v >> 16) - b[2] = byte(v >> 8) - b[3] = byte(v) -} - -func BEAppendUint32(b []byte, v uint32) []byte { - return append(b, - byte(v>>24), - byte(v>>16), - byte(v>>8), - byte(v), - ) -} - -func BEUint64(b []byte) uint64 { - _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 - return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | - uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 -} - -func BEPutUint64(b []byte, v uint64) { - _ = b[7] // early bounds check to guarantee safety of writes below - b[0] = byte(v >> 56) - b[1] = byte(v >> 48) - b[2] = byte(v >> 40) - b[3] = byte(v >> 32) - b[4] = byte(v >> 24) - b[5] = byte(v >> 16) - b[6] = byte(v >> 8) - b[7] = byte(v) -} - -func BEAppendUint64(b []byte, v uint64) []byte { - return append(b, - byte(v>>56), - byte(v>>48), - byte(v>>40), - byte(v>>32), - byte(v>>24), - byte(v>>16), - byte(v>>8), - byte(v), - ) -} \ No newline at end of file diff --git a/drbg/ctrdrbg.go b/drbg/ctrdrbg.go deleted file mode 100644 index 9d4257e..0000000 --- a/drbg/ctrdrbg.go +++ /dev/null @@ -1,143 +0,0 @@ -// 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 drbg - -import ( - //"crypto/internal/fips140" - "github.com/xtls/reality/aes" - "github.com/xtls/reality/subtle" - "github.com/xtls/reality/byteorder" - "math/bits" -) - -// Counter is an SP 800-90A Rev. 1 CTR_DRBG instantiated with AES-256. -// -// Per Table 3, it has a security strength of 256 bits, a seed size of 384 bits, -// a counter length of 128 bits, a reseed interval of 2^48 requests, and a -// maximum request size of 2^19 bits (2^16 bytes, 64 KiB). -// -// We support a narrow range of parameters that fit the needs of our RNG: -// AES-256, no derivation function, no personalization string, no prediction -// resistance, and 384-bit additional input. -// -// WARNING: this type provides tightly scoped support for the DRBG -// functionality we need for FIPS 140-3 _only_. This type _should not_ be used -// outside of the FIPS 140-3 module for any other use. -// -// In particular, as documented, Counter does not support the derivation -// function, or personalization strings which are necessary for safely using -// this DRBG for generic purposes without leaking sensitive values. -type Counter struct { - // c is instantiated with K as the key and V as the counter. - c aes.CTR - - reseedCounter uint64 -} - -const ( - keySize = 256 / 8 - SeedSize = keySize + aes.BlockSize - reseedInterval = 1 << 48 - maxRequestSize = (1 << 19) / 8 -) - -func NewCounter(entropy *[SeedSize]byte) *Counter { - // CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1. - //fips140.RecordApproved() - - K := make([]byte, keySize) - V := make([]byte, aes.BlockSize) - - // V starts at 0, but is incremented in CTR_DRBG_Update before each use, - // unlike AES-CTR where it is incremented after each use. - V[len(V)-1] = 1 - - cipher, err := aes.New(K) - if err != nil { - panic(err) - } - - c := &Counter{} - c.c = *aes.NewCTR(cipher, V) - c.update(entropy) - c.reseedCounter = 1 - return c -} - -func (c *Counter) update(seed *[SeedSize]byte) { - // CTR_DRBG_Update, per Section 10.2.1.2. - - temp := make([]byte, SeedSize) - c.c.XORKeyStream(temp, seed[:]) - K := temp[:keySize] - V := temp[keySize:] - - // Again, we pre-increment V, like in NewCounter. - increment((*[aes.BlockSize]byte)(V)) - - cipher, err := aes.New(K) - if err != nil { - panic(err) - } - c.c = *aes.NewCTR(cipher, V) -} - -func increment(v *[aes.BlockSize]byte) { - hi := byteorder.BEUint64(v[:8]) - lo := byteorder.BEUint64(v[8:]) - lo, c := bits.Add64(lo, 1, 0) - hi, _ = bits.Add64(hi, 0, c) - byteorder.BEPutUint64(v[:8], hi) - byteorder.BEPutUint64(v[8:], lo) -} - -func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) { - // CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1. - //fips140.RecordApproved() - - var seed [SeedSize]byte - subtle.XORBytes(seed[:], entropy[:], additionalInput[:]) - c.update(&seed) - c.reseedCounter = 1 -} - -// Generate produces at most maxRequestSize bytes of random data in out. -func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) { - // CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1. - //fips140.RecordApproved() - - if len(out) > maxRequestSize { - panic("crypto/drbg: internal error: request size exceeds maximum") - } - - // Step 1. - if c.reseedCounter > reseedInterval { - return true - } - - // Step 2. - if additionalInput != nil { - c.update(additionalInput) - } else { - // If the additional input is null, the first CTR_DRBG_Update is - // skipped, but the additional input is replaced with an all-zero string - // for the second CTR_DRBG_Update. - additionalInput = new([SeedSize]byte) - } - - // Steps 3-5. - clear(out) - c.c.XORKeyStream(out, out) - aes.RoundToBlock(&c.c) - - // Step 6. - c.update(additionalInput) - - // Step 7. - c.reseedCounter++ - - // Step 8. - return false -} \ No newline at end of file diff --git a/drbg/rand.go b/drbg/rand.go deleted file mode 100644 index a3255fe..0000000 --- a/drbg/rand.go +++ /dev/null @@ -1,103 +0,0 @@ -// 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 drbg provides cryptographically secure random bytes -// usable by FIPS code. In FIPS mode it uses an SP 800-90A Rev. 1 -// Deterministic Random Bit Generator (DRBG). Otherwise, -// it uses the operating system's random number generator. -package drbg - -import ( - "github.com/xtls/reality/entropy" - // "crypto/internal/fips140" - "github.com/xtls/reality/randutil" - // "github.com/xtls/reality/sysrand" - - "crypto/fips140" - "crypto/rand" - "io" - "sync" -) - -var drbgs = sync.Pool{ - New: func() any { - var c *Counter - entropy.Depleted(func(seed *[48]byte) { - c = NewCounter(seed) - }) - return c - }, -} - -// Read fills b with cryptographically secure random bytes. In FIPS mode, it -// uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). -// Otherwise, it uses the operating system's random number generator. -func Read(b []byte) { - if !fips140.Enabled() { - rand.Read(b) - return - } - - // At every read, 128 random bits from the operating system are mixed as - // additional input, to make the output as strong as non-FIPS randomness. - // This is not credited as entropy for FIPS purposes, as allowed by Section - // 8.7.2: "Note that a DRBG does not rely on additional input to provide - // entropy, even though entropy could be provided in the additional input". - additionalInput := new([SeedSize]byte) - rand.Read(additionalInput[:16]) - - drbg := drbgs.Get().(*Counter) - defer drbgs.Put(drbg) - - for len(b) > 0 { - size := min(len(b), maxRequestSize) - if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { - // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in - // Section 9.3.2: if Generate reports a reseed is required, the - // additional input is passed to Reseed along with the entropy and - // then nulled before the next Generate call. - entropy.Depleted(func(seed *[48]byte) { - drbg.Reseed(seed, additionalInput) - }) - additionalInput = nil - continue - } - b = b[size:] - } -} - -// DefaultReader is a sentinel type, embedded in the default -// [crypto/rand.Reader], used to recognize it when passed to -// APIs that accept a rand io.Reader. -type DefaultReader interface{ defaultReader() } - -// ReadWithReader uses Reader to fill b with cryptographically secure random -// bytes. It is intended for use in APIs that expose a rand io.Reader. -// -// If Reader is not the default Reader from crypto/rand, -// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called. -func ReadWithReader(r io.Reader, b []byte) error { - if _, ok := r.(DefaultReader); ok { - Read(b) - return nil - } - - //fips140.RecordNonApproved() - randutil.MaybeReadByte(r) - _, err := io.ReadFull(r, b) - return err -} - -// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call -// [randutil.MaybeReadByte] on non-default Readers. -func ReadWithReaderDeterministic(r io.Reader, b []byte) error { - if _, ok := r.(DefaultReader); ok { - Read(b) - return nil - } - - //fips140.RecordNonApproved() - _, err := io.ReadFull(r, b) - return err -} \ No newline at end of file diff --git a/entropy/entropy.go b/entropy/entropy.go deleted file mode 100644 index 0bea141..0000000 --- a/entropy/entropy.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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 entropy provides the passive entropy source for the FIPS 140-3 -// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read]. -// -// This complies with IG 9.3.A, Additional Comment 12, which until January 1, -// 2026 allows new modules to meet an [earlier version] of Resolution 2(b): -// "A software module that contains an approved DRBG that receives a LOAD -// command (or its logical equivalent) with entropy obtained from [...] inside -// the physical perimeter of the operational environment of the module [...]." -// -// Distributions that have their own SP 800-90B entropy source should replace -// this package with their own implementation. -// -// [earlier version]: https://csrc.nist.gov/CSRC/media/Projects/cryptographic-module-validation-program/documents/IG%209.3.A%20Resolution%202b%5BMarch%2026%202024%5D.pdf -package entropy - -// "github.com/xtls/reality/sysrand" -import "crypto/rand" - -// Depleted notifies the entropy source that the entropy in the module is -// "depleted" and provides the callback for the LOAD command. -func Depleted(LOAD func(*[48]byte)) { - var entropy [48]byte - rand.Read(entropy[:]) - LOAD(&entropy) -} \ No newline at end of file diff --git a/fips140/hash.go b/fips140/hash.go deleted file mode 100644 index 482ddf1..0000000 --- a/fips140/hash.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 fips140 - -import "io" - -// Hash is the common interface implemented by all hash functions. It is a copy -// of [hash.Hash] from the standard library, to avoid depending on security -// definitions from outside of the module. -type Hash interface { - // Write (via the embedded io.Writer interface) adds more data to the - // running hash. It never returns an error. - io.Writer - - // Sum appends the current hash to b and returns the resulting slice. - // It does not change the underlying hash state. - Sum(b []byte) []byte - - // Reset resets the Hash to its initial state. - Reset() - - // Size returns the number of bytes Sum will return. - Size() int - - // BlockSize returns the hash's underlying block size. - // The Write method must be able to accept any amount - // of data, but it may operate more efficiently if all writes - // are a multiple of the block size. - BlockSize() int -} \ No newline at end of file diff --git a/gcm/gcm.go b/gcm/gcm.go index c3d4220..439779d 100644 --- a/gcm/gcm.go +++ b/gcm/gcm.go @@ -5,8 +5,6 @@ package gcm import ( - // "crypto/internal/fips140" - "errors" "github.com/xtls/reality/aes" diff --git a/gcm/gcm_generic.go b/gcm/gcm_generic.go index abb2d69..cd76068 100644 --- a/gcm/gcm_generic.go +++ b/gcm/gcm_generic.go @@ -5,9 +5,10 @@ package gcm import ( + "crypto/subtle" + "encoding/binary" + "github.com/xtls/reality/aes" - "github.com/xtls/reality/byteorder" - "github.com/xtls/reality/subtle" ) func sealGeneric(out []byte, g *GCM, nonce, plaintext, additionalData []byte) { @@ -58,7 +59,7 @@ func deriveCounterGeneric(H, counter *[gcmBlockSize]byte, nonce []byte) { counter[gcmBlockSize-1] = 1 } else { lenBlock := make([]byte, 16) - byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8) + binary.BigEndian.PutUint64(lenBlock[8:], uint64(len(nonce))*8) ghash(counter, H, nonce, lenBlock) } } @@ -89,7 +90,7 @@ func gcmCounterCryptGeneric(b *aes.Block, out, src []byte, counter *[gcmBlockSiz // and increments it. func gcmInc32(counterBlock *[gcmBlockSize]byte) { ctr := counterBlock[len(counterBlock)-4:] - byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1) + binary.BigEndian.PutUint32(ctr, binary.BigEndian.Uint32(ctr)+1) } // gcmAuthGeneric calculates GHASH(additionalData, ciphertext), masks the result @@ -97,8 +98,8 @@ func gcmInc32(counterBlock *[gcmBlockSize]byte) { func gcmAuthGeneric(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { checkGenericIsExpected() lenBlock := make([]byte, 16) - byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8) - byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8) + 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[:]) diff --git a/gcm/gcm_nonces.go b/gcm/gcm_nonces.go index e6d0958..e408760 100644 --- a/gcm/gcm_nonces.go +++ b/gcm/gcm_nonces.go @@ -5,14 +5,10 @@ package gcm import ( - // "crypto/internal/fips140" - // "crypto/internal/fips140/drbg" - + "encoding/binary" "math" "github.com/xtls/reality/aes" - // "github.com/xtls/reality/alias" - "github.com/xtls/reality/byteorder" ) // SealWithRandomNonce encrypts plaintext to out, and writes a random nonce to @@ -75,14 +71,14 @@ func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte { panic("crypto/cipher: incorrect nonce length given to GCM") } - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + 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 = byteorder.BEUint32(nonce[:4]) + g.fixedName = binary.BigEndian.Uint32(nonce[:4]) } - if g.fixedName != byteorder.BEUint32(nonce[:4]) { + if g.fixedName != binary.BigEndian.Uint32(nonce[:4]) { panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce") } counter -= g.start @@ -132,7 +128,7 @@ func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte { panic("crypto/cipher: incorrect nonce length given to GCM") } - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) // Ensure the counter is monotonically increasing. if counter == math.MaxUint64 { @@ -178,7 +174,7 @@ func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte { panic("crypto/cipher: incorrect nonce length given to GCM") } - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + 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 @@ -232,7 +228,7 @@ func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte { panic("crypto/cipher: incorrect nonce length given to GCM") } - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:]) if !g.ready { // In the first call we learn the start value. g.ready = true diff --git a/gcm/ghash.go b/gcm/ghash.go index d1edc42..7c0ffd0 100644 --- a/gcm/ghash.go +++ b/gcm/ghash.go @@ -5,8 +5,7 @@ package gcm import ( - // "crypto/internal/fips140" - "github.com/xtls/reality/byteorder" + "encoding/binary" ) // gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM @@ -46,8 +45,8 @@ func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) { // 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{ - byteorder.BEUint64(H[:8]), - byteorder.BEUint64(H[8:]), + binary.BigEndian.Uint64(H[:8]), + binary.BigEndian.Uint64(H[8:]), } productTable[reverseBits(1)] = x @@ -61,8 +60,8 @@ func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) { ghashUpdate(&productTable, &y, input) } - byteorder.BEPutUint64(out[:], y.low) - byteorder.BEPutUint64(out[8:], y.high) + 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. @@ -142,8 +141,8 @@ func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) { // 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 ^= byteorder.BEUint64(blocks) - y.high ^= byteorder.BEUint64(blocks[8:]) + y.low ^= binary.BigEndian.Uint64(blocks) + y.high ^= binary.BigEndian.Uint64(blocks[8:]) ghashMul(productTable, y) blocks = blocks[gcmBlockSize:] } diff --git a/handshake_client.go b/handshake_client.go index 781cfb7..4e01c93 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -10,9 +10,11 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/mlkem" "crypto/rsa" "crypto/subtle" "crypto/x509" + "encoding/binary" "errors" "fmt" "hash" @@ -21,10 +23,8 @@ import ( "strings" "time" - "github.com/xtls/reality/byteorder" "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/hpke" - "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -708,7 +708,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { return err } if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) + c.curveID = CurveID(binary.BigEndian.Uint16(skx.key[1:])) } msg, err = c.readHandshake(&hs.finishedHash) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index f23bc8f..b21c5b1 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -9,6 +9,7 @@ import ( "context" "crypto" "crypto/hmac" + "crypto/mlkem" "crypto/rsa" "crypto/subtle" "errors" @@ -16,7 +17,6 @@ import ( "slices" "time" - "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" "golang.org/x/crypto/hkdf" ) diff --git a/handshake_server.go b/handshake_server.go index 6958c95..74b686e 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -12,13 +12,12 @@ import ( "crypto/rsa" "crypto/subtle" "crypto/x509" + "encoding/binary" "errors" "fmt" "hash" "io" "time" - - "github.com/xtls/reality/byteorder" ) // serverHandshakeState contains details of a server handshake in progress. @@ -580,7 +579,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } if skx != nil { if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) + c.curveID = CurveID(binary.BigEndian.Uint16(skx.key[1:])) } if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil { return err diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index be75a1f..86b618c 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -10,20 +10,20 @@ import ( "crypto" "crypto/ed25519" "crypto/hmac" + "crypto/mlkem" "crypto/rand" "crypto/rsa" "crypto/sha512" "crypto/x509" + "encoding/binary" "errors" "hash" "io" "math/big" "slices" "time" - - "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140tls" - "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -954,7 +954,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error { if _, err := c.config.rand().Read(ageAdd); err != nil { return err } - m.ageAdd = byteorder.LEUint32(ageAdd) + m.ageAdd = binary.LittleEndian.Uint32(ageAdd) if earlyData { // RFC 9001, Section 4.6.1 diff --git a/hkdf/hkdf.go b/hkdf/hkdf.go deleted file mode 100644 index 0143eb8..0000000 --- a/hkdf/hkdf.go +++ /dev/null @@ -1,56 +0,0 @@ -// 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 hkdf - -import ( - "github.com/xtls/reality/fips140" - "github.com/xtls/reality/hmac" -) - -func Extract[H fips140.Hash](h func() H, secret, salt []byte) []byte { - if len(secret) < 112/8 { - // fips140.RecordNonApproved() - } - if salt == nil { - salt = make([]byte, h().Size()) - } - extractor := hmac.New(h, salt) - hmac.MarkAsUsedInKDF(extractor) - extractor.Write(secret) - - return extractor.Sum(nil) -} - -func Expand[H fips140.Hash](h func() H, pseudorandomKey []byte, info string, keyLen int) []byte { - out := make([]byte, 0, keyLen) - expander := hmac.New(h, pseudorandomKey) - hmac.MarkAsUsedInKDF(expander) - var counter uint8 - var buf []byte - - for len(out) < keyLen { - counter++ - if counter == 0 { - panic("hkdf: counter overflow") - } - if counter > 1 { - expander.Reset() - } - expander.Write(buf) - expander.Write([]byte(info)) - expander.Write([]byte{counter}) - buf = expander.Sum(buf[:0]) - remain := keyLen - len(out) - remain = min(remain, len(buf)) - out = append(out, buf[:remain]...) - } - - return out -} - -func Key[H fips140.Hash](h func() H, secret, salt []byte, info string, keyLen int) []byte { - prk := Extract(h, secret, salt) - return Expand(h, prk, info, keyLen) -} \ No newline at end of file diff --git a/hmac/hmac.go b/hmac/hmac.go deleted file mode 100644 index b4cc25e..0000000 --- a/hmac/hmac.go +++ /dev/null @@ -1,172 +0,0 @@ -// 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 hmac implements HMAC according to [FIPS 198-1]. -// -// [FIPS 198-1]: https://doi.org/10.6028/NIST.FIPS.198-1 -package hmac - -import ( - "github.com/xtls/reality/fips140" - "github.com/xtls/reality/sha256" - "github.com/xtls/reality/sha3" - "github.com/xtls/reality/sha512" -) - -// key is zero padded to the block size of the hash function -// ipad = 0x36 byte repeated for key length -// opad = 0x5c byte repeated for key length -// hmac = H([key ^ opad] H([key ^ ipad] text)) - -// marshalable is the combination of encoding.BinaryMarshaler and -// encoding.BinaryUnmarshaler. Their method definitions are repeated here to -// avoid a dependency on the encoding package. -type marshalable interface { - MarshalBinary() ([]byte, error) - UnmarshalBinary([]byte) error -} - -type HMAC struct { - opad, ipad []byte - outer, inner fips140.Hash - - // If marshaled is true, then opad and ipad do not contain a padded - // copy of the key, but rather the marshaled state of outer/inner after - // opad/ipad has been fed into it. - marshaled bool - - // forHKDF and keyLen are stored to inform the service indicator decision. - forHKDF bool - keyLen int -} - -func (h *HMAC) Sum(in []byte) []byte { - // Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for - // legacy use (i.e. verification only) and we don't support that. However, - // HKDF uses the HMAC key for the salt, which is allowed to be shorter. - if h.keyLen < 112/8 && !h.forHKDF { - // fips140.RecordNonApproved() - } - switch h.inner.(type) { - case *sha256.Digest, *sha512.Digest, *sha3.Digest: - default: - // fips140.RecordNonApproved() - } - - origLen := len(in) - in = h.inner.Sum(in) - - if h.marshaled { - if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil { - panic(err) - } - } else { - h.outer.Reset() - h.outer.Write(h.opad) - } - h.outer.Write(in[origLen:]) - return h.outer.Sum(in[:origLen]) -} - -func (h *HMAC) Write(p []byte) (n int, err error) { - return h.inner.Write(p) -} - -func (h *HMAC) Size() int { return h.outer.Size() } -func (h *HMAC) BlockSize() int { return h.inner.BlockSize() } - -func (h *HMAC) Reset() { - if h.marshaled { - if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil { - panic(err) - } - return - } - - h.inner.Reset() - h.inner.Write(h.ipad) - - // If the underlying hash is marshalable, we can save some time by saving a - // copy of the hash state now, and restoring it on future calls to Reset and - // Sum instead of writing ipad/opad every time. - // - // We do this on Reset to avoid slowing down the common single-use case. - // - // This is allowed by FIPS 198-1, Section 6: "Conceptually, the intermediate - // results of the compression function on the B-byte blocks (K0 ⊕ ipad) and - // (K0 ⊕ opad) can be precomputed once, at the time of generation of the key - // K, or before its first use. These intermediate results can be stored and - // then used to initialize H each time that a message needs to be - // authenticated using the same key. [...] These stored intermediate values - // shall be treated and protected in the same manner as secret keys." - marshalableInner, innerOK := h.inner.(marshalable) - if !innerOK { - return - } - marshalableOuter, outerOK := h.outer.(marshalable) - if !outerOK { - return - } - - imarshal, err := marshalableInner.MarshalBinary() - if err != nil { - return - } - - h.outer.Reset() - h.outer.Write(h.opad) - omarshal, err := marshalableOuter.MarshalBinary() - if err != nil { - return - } - - // Marshaling succeeded; save the marshaled state for later - h.ipad = imarshal - h.opad = omarshal - h.marshaled = true -} - -// New returns a new HMAC hash using the given [fips140.Hash] type and key. -func New[H fips140.Hash](h func() H, key []byte) *HMAC { - hm := &HMAC{keyLen: len(key)} - hm.outer = h() - hm.inner = h() - unique := true - func() { - defer func() { - // The comparison might panic if the underlying types are not comparable. - _ = recover() - }() - if hm.outer == hm.inner { - unique = false - } - }() - if !unique { - panic("crypto/hmac: hash generation function does not produce unique values") - } - blocksize := hm.inner.BlockSize() - hm.ipad = make([]byte, blocksize) - hm.opad = make([]byte, blocksize) - if len(key) > blocksize { - // If key is too big, hash it. - hm.outer.Write(key) - key = hm.outer.Sum(nil) - } - copy(hm.ipad, key) - copy(hm.opad, key) - for i := range hm.ipad { - hm.ipad[i] ^= 0x36 - } - for i := range hm.opad { - hm.opad[i] ^= 0x5c - } - hm.inner.Write(hm.ipad) - - return hm -} - -// MarkAsUsedInKDF records that this HMAC instance is used as part of a KDF. -func MarkAsUsedInKDF(h *HMAC) { - h.forHKDF = true -} \ No newline at end of file diff --git a/hpke/hpye.go b/hpke/hpye.go index b546ddc..9ef26be 100644 --- a/hpke/hpye.go +++ b/hpke/hpye.go @@ -11,12 +11,11 @@ import ( "crypto/ecdh" "crypto/hkdf" "crypto/rand" + "encoding/binary" "errors" "math/bits" "golang.org/x/crypto/chacha20poly1305" - - "github.com/xtls/reality/byteorder" ) // testingOnlyGenerateKey is only used during testing, to provide @@ -38,7 +37,7 @@ func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputK 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 = byteorder.BEAppendUint16(labeledInfo, length) + labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length) labeledInfo = append(labeledInfo, []byte("HPKE-v1")...) labeledInfo = append(labeledInfo, suiteID...) labeledInfo = append(labeledInfo, label...) @@ -76,7 +75,7 @@ func newDHKem(kemID uint16) (*dhKEM, error) { return &dhKEM{ dh: suite.curve, kdf: hkdfKDF{suite.hash}, - suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID), + suiteID: binary.BigEndian.AppendUint16([]byte("KEM"), kemID), nSecret: suite.nSecret, }, nil } @@ -313,9 +312,9 @@ func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) { func suiteID(kemID, kdfID, aeadID uint16) []byte { suiteID := make([]byte, 0, 4+2+2+2) suiteID = append(suiteID, []byte("HPKE")...) - suiteID = byteorder.BEAppendUint16(suiteID, kemID) - suiteID = byteorder.BEAppendUint16(suiteID, kdfID) - suiteID = byteorder.BEAppendUint16(suiteID, aeadID) + suiteID = binary.BigEndian.AppendUint16(suiteID, kemID) + suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID) + suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID) return suiteID } @@ -350,7 +349,7 @@ func (u uint128) bitLen() int { func (u uint128) bytes() []byte { b := make([]byte, 16) - byteorder.BEPutUint64(b[0:], u.hi) - byteorder.BEPutUint64(b[8:], u.lo) + 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 7113751..e367476 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -7,12 +7,12 @@ package reality import ( "crypto/ecdh" "crypto/hmac" + "crypto/mlkem" + "crypto/sha3" "errors" "hash" "io" - "github.com/xtls/reality/mlkem" - "github.com/xtls/reality/sha3" "github.com/xtls/reality/tls13" ) @@ -80,7 +80,7 @@ func kyberSharedSecret(c, K []byte) []byte { // Package mlkem 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 := sha3.NewSHAKE256() h.Write(K) ch := sha3.New256() ch.Write(c) diff --git a/mlkem/field.go b/mlkem/field.go deleted file mode 100644 index 4f94ea9..0000000 --- a/mlkem/field.go +++ /dev/null @@ -1,550 +0,0 @@ -// 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 mlkem - -import ( - "github.com/xtls/reality/sha3" - "github.com/xtls/reality/byteorder" - "errors" -) - -// 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, Definition 4.7. -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, 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, Definition 4.8. -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, 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, Section 2.4.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, Algorithm 5. -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 fulfills the "Modulus check" -// step of ML-KEM Encapsulation. -// -// It implements ByteDecode₁₂, according to FIPS 203, Algorithm 6. -func polyByteDecode[T ~[n]fieldElement](b []byte) (T, error) { - if len(b) != encodingSize12 { - return T{}, errors.New("mlkem: 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("mlkem: invalid polynomial encoding") - } - if f[i+1], err = fieldCheckReduced(uint16(d >> 12)); err != nil { - return T{}, errors.New("mlkem: 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, Definition 4.7, -// followed by ByteEncode₁, according to FIPS 203, Algorithm 5. -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, Algorithm 6, -// followed by Decompress₁, according to FIPS 203, Definition 4.8. -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, 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, Definition 4.7, -// followed by ByteEncode₄, according to FIPS 203, Algorithm 5. -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, Algorithm 6, -// followed by Decompress₄, according to FIPS 203, Definition 4.8. -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, Definition 4.7, -// followed by ByteEncode₁₀, according to FIPS 203, Algorithm 5. -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], 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, Algorithm 6, -// followed by Decompress₁₀, according to FIPS 203, Definition 4.8. -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 -} - -// ringCompressAndEncode appends an encoding of a ring element to s, -// compressing each coefficient to d bits. -// -// It implements Compress, according to FIPS 203, Definition 4.7, -// followed by ByteEncode, according to FIPS 203, Algorithm 5. -func ringCompressAndEncode(s []byte, f ringElement, d uint8) []byte { - var b byte - var bIdx uint8 - for i := 0; i < n; i++ { - c := compress(f[i], d) - var cIdx uint8 - for cIdx < d { - b |= byte(c>>cIdx) << bIdx - bits := min(8-bIdx, d-cIdx) - bIdx += bits - cIdx += bits - if bIdx == 8 { - s = append(s, b) - b = 0 - bIdx = 0 - } - } - } - if bIdx != 0 { - panic("mlkem: internal error: bitsFilled != 0") - } - return s -} - -// ringDecodeAndDecompress decodes an encoding of a ring element where -// each d bits are mapped to an equidistant distribution. -// -// It implements ByteDecode, according to FIPS 203, Algorithm 6, -// followed by Decompress, according to FIPS 203, Definition 4.8. -func ringDecodeAndDecompress(b []byte, d uint8) ringElement { - var f ringElement - var bIdx uint8 - for i := 0; i < n; i++ { - var c uint16 - var cIdx uint8 - for cIdx < d { - c |= uint16(b[0]>>bIdx) << cIdx - c &= (1 << d) - 1 - bits := min(8-bIdx, d-cIdx) - bIdx += bits - cIdx += bits - if bIdx == 8 { - b = b[1:] - bIdx = 0 - } - } - f[i] = fieldElement(decompress(c, d)) - } - if len(b) != 0 { - panic("mlkem: internal error: leftover bytes") - } - return f -} - -// ringCompressAndEncode5 appends a 160-byte encoding of a ring element to s, -// compressing eight coefficients per five bytes. -// -// It implements Compress₅, according to FIPS 203, Definition 4.7, -// followed by ByteEncode₅, according to FIPS 203, Algorithm 5. -func ringCompressAndEncode5(s []byte, f ringElement) []byte { - return ringCompressAndEncode(s, f, 5) -} - -// ringDecodeAndDecompress5 decodes a 160-byte encoding of a ring element where -// each five bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₅, according to FIPS 203, Algorithm 6, -// followed by Decompress₅, according to FIPS 203, Definition 4.8. -func ringDecodeAndDecompress5(bb *[encodingSize5]byte) ringElement { - return ringDecodeAndDecompress(bb[:], 5) -} - -// ringCompressAndEncode11 appends a 352-byte encoding of a ring element to s, -// compressing eight coefficients per eleven bytes. -// -// It implements Compress₁₁, according to FIPS 203, Definition 4.7, -// followed by ByteEncode₁₁, according to FIPS 203, Algorithm 5. -func ringCompressAndEncode11(s []byte, f ringElement) []byte { - return ringCompressAndEncode(s, f, 11) -} - -// ringDecodeAndDecompress11 decodes a 352-byte encoding of a ring element where -// each eleven bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₁₁, according to FIPS 203, Algorithm 6, -// followed by Decompress₁₁, according to FIPS 203, Definition 4.8. -func ringDecodeAndDecompress11(bb *[encodingSize11]byte) ringElement { - return ringDecodeAndDecompress(bb[:], 11) -} - -// samplePolyCBD draws a ringElement from the special Dη distribution given a -// stream of random bytes generated by the PRF function, according to FIPS 203, -// Algorithm 8 and Definition 4.3. -func samplePolyCBD(s []byte, b byte) ringElement { - prf := sha3.NewShake256() - prf.Write(s) - prf.Write([]byte{b}) - B := make([]byte, 64*2) // η = 2 - 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, Section 2.4.4. -type nttElement [n]fieldElement - -// gammas are the values ζ^2BitRev7(i)+1 mod q for each index i, according to -// FIPS 203, Appendix A (with negative values reduced to positive). -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, Algorithm 11. -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, according to FIPS -// 203, Appendix A. -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, Algorithm 9. -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, Algorithm 10. -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, -// Algorithm 7. -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 := byteorder.LEUint16(buf[off:]) & 0b1111_1111_1111 - d2 := byteorder.LEUint16(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 -} \ No newline at end of file diff --git a/mlkem/mlkem768.go b/mlkem/mlkem768.go deleted file mode 100644 index 2b4fc12..0000000 --- a/mlkem/mlkem768.go +++ /dev/null @@ -1,517 +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 mlkem implements the quantum-resistant key encapsulation method -// ML-KEM (formerly known as Kyber), as specified in [NIST FIPS 203]. -// -// [NIST FIPS 203]: https://doi.org/10.6028/NIST.FIPS.203 -package mlkem - -// 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 document. -// -// Reviewers unfamiliar with polynomials or linear algebra might find the -// background at https://words.filippo.io/kyber-math/ useful. -// -// This file implements the recommended parameter set ML-KEM-768. The ML-KEM-1024 -// parameter set implementation is auto-generated from this file. -// -//go:generate go run generate1024.go -input mlkem768.go -output mlkem1024.go - -import ( - "bytes" - //"github.com/xtls/reality/fips140" - "github.com/xtls/reality/drbg" - "github.com/xtls/reality/sha3" - "github.com/xtls/reality/subtle" - "errors" -) - -const ( - // ML-KEM global constants. - n = 256 - q = 3329 - - // encodingSizeX is the byte size of a ringElement or nttElement encoded - // by ByteEncode_X (FIPS 203, Algorithm 5). - encodingSize12 = n * 12 / 8 - encodingSize11 = n * 11 / 8 - encodingSize10 = n * 10 / 8 - encodingSize5 = n * 5 / 8 - encodingSize4 = n * 4 / 8 - encodingSize1 = n * 1 / 8 - - messageSize = encodingSize1 - - SharedKeySize = 32 - SeedSize = 32 + 32 -) - -// ML-KEM-768 parameters. -const ( - k = 3 - - CiphertextSize768 = k*encodingSize10 + encodingSize4 - EncapsulationKeySize768 = k*encodingSize12 + 32 - decapsulationKeySize768 = k*encodingSize12 + EncapsulationKeySize768 + 32 + 32 -) - -// ML-KEM-1024 parameters. -const ( - k1024 = 4 - - CiphertextSize1024 = k1024*encodingSize11 + encodingSize5 - EncapsulationKeySize1024 = k1024*encodingSize12 + 32 - decapsulationKeySize1024 = k1024*encodingSize12 + EncapsulationKeySize1024 + 32 + 32 -) - -// A DecapsulationKey768 is the secret key used to decapsulate a shared key from a -// ciphertext. It includes various precomputed values. -type DecapsulationKey768 struct { - d [32]byte // decapsulation key seed - z [32]byte // implicit rejection sampling seed - - ρ [32]byte // sampleNTT seed for A, stored for the encapsulation key - h [32]byte // H(ek), stored for ML-KEM.Decaps_internal - - encryptionKey - decryptionKey -} - -// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. -// -// The decapsulation key must be kept secret. -func (dk *DecapsulationKey768) Bytes() []byte { - var b [SeedSize]byte - copy(b[:], dk.d[:]) - copy(b[32:], dk.z[:]) - return b[:] -} - -// TestingOnlyExpandedBytes768 returns the decapsulation key as a byte slice -// using the full expanded NIST encoding. -// -// This should only be used for ACVP testing. For all other purposes prefer -// the Bytes method that returns the (much smaller) seed. -func TestingOnlyExpandedBytes768(dk *DecapsulationKey768) []byte { - b := make([]byte, 0, decapsulationKeySize768) - - // ByteEncode₁₂(s) - for i := range dk.s { - b = polyByteEncode(b, dk.s[i]) - } - - // ByteEncode₁₂(t) || ρ - for i := range dk.t { - b = polyByteEncode(b, dk.t[i]) - } - b = append(b, dk.ρ[:]...) - - // H(ek) || z - b = append(b, dk.h[:]...) - b = append(b, dk.z[:]...) - - return b -} - -// EncapsulationKey returns the public encapsulation key necessary to produce -// ciphertexts. -func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 { - return &EncapsulationKey768{ - ρ: dk.ρ, - h: dk.h, - encryptionKey: dk.encryptionKey, - } -} - -// An EncapsulationKey768 is the public key used to produce ciphertexts to be -// decapsulated by the corresponding [DecapsulationKey768]. -type EncapsulationKey768 struct { - ρ [32]byte // sampleNTT seed for A - h [32]byte // H(ek) - encryptionKey -} - -// Bytes returns the encapsulation key as a byte slice. -func (ek *EncapsulationKey768) Bytes() []byte { - // The actual logic is in a separate function to outline this allocation. - b := make([]byte, 0, EncapsulationKeySize768) - return ek.bytes(b) -} - -func (ek *EncapsulationKey768) bytes(b []byte) []byte { - for i := range ek.t { - b = polyByteEncode(b, ek.t[i]) - } - b = append(b, ek.ρ[:]...) - 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]) -} - -// GenerateKey768 generates a new decapsulation key, drawing random bytes from -// a DRBG. The decapsulation key must be kept secret. -func GenerateKey768() (*DecapsulationKey768, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey768{} - return generateKey(dk) -} - -func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) { - var d [32]byte - drbg.Read(d[:]) - var z [32]byte - drbg.Read(z[:]) - kemKeyGen(dk, &d, &z) - // if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // // This clearly can't happen, but FIPS 140-3 requires us to check. - // panic(err) - // } - //fips140.RecordApproved() - return dk, nil -} - -// GenerateKeyInternal768 is a derandomized version of GenerateKey768, -// exclusively for use in tests. -func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 { - dk := &DecapsulationKey768{} - kemKeyGen(dk, d, z) - return dk -} - -// NewDecapsulationKey768 parses a decapsulation key from a 64-byte -// seed in the "d || z" form. The seed must be uniformly random. -func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey768{} - return newKeyFromSeed(dk, seed) -} - -func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768, error) { - if len(seed) != SeedSize { - return nil, errors.New("mlkem: invalid seed length") - } - d := (*[32]byte)(seed[:32]) - z := (*[32]byte)(seed[32:]) - kemKeyGen(dk, d, z) - // if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // // This clearly can't happen, but FIPS 140-3 requires us to check. - // panic(err) - // } - //fips140.RecordApproved() - return dk, nil -} - -// TestingOnlyNewDecapsulationKey768 parses a decapsulation key from its expanded NIST format. -// -// Bytes() must not be called on the returned key, as it will not produce the -// original seed. -// -// This function should only be used for ACVP testing. Prefer NewDecapsulationKey768 for all -// other purposes. -func TestingOnlyNewDecapsulationKey768(b []byte) (*DecapsulationKey768, error) { - if len(b) != decapsulationKeySize768 { - return nil, errors.New("mlkem: invalid NIST decapsulation key length") - } - - dk := &DecapsulationKey768{} - for i := range dk.s { - var err error - dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12]) - if err != nil { - return nil, errors.New("mlkem: invalid secret key encoding") - } - b = b[encodingSize12:] - } - - ek, err := NewEncapsulationKey768(b[:EncapsulationKeySize768]) - if err != nil { - return nil, err - } - dk.ρ = ek.ρ - dk.h = ek.h - dk.encryptionKey = ek.encryptionKey - b = b[EncapsulationKeySize768:] - - if !bytes.Equal(dk.h[:], b[:32]) { - return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes") - } - b = b[32:] - - copy(dk.z[:], b) - - // Generate a random d value for use in Bytes(). This is a safety mechanism - // that avoids returning a broken key vs a random key if this function is - // called in contravention of the TestingOnlyNewDecapsulationKey768 function - // comment advising against it. - drbg.Read(dk.d[:]) - - return dk, nil -} - -// kemKeyGen generates a decapsulation key. -// -// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and -// K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save -// copies and allocations. -func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) { - dk.d = *d - dk.z = *z - - g := sha3.New512() - g.Write(d[:]) - g.Write([]byte{k}) // Module dimension as a domain separator. - G := g.Sum(make([]byte, 0, 64)) - ρ, σ := G[:32], G[32:] - dk.ρ = [32]byte(ρ) - - A := &dk.a - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - 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])) - } - } - - H := sha3.New256() - ek := dk.EncapsulationKey().Bytes() - H.Write(ek) - H.Sum(dk.h[:0]) -} - -// kemPCT performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A -// Additional Comment 1: "For key pairs generated for use with approved KEMs in -// FIPS 203, the PCT shall consist of applying the encapsulation key ek to -// encapsulate a shared secret K leading to ciphertext c, and then applying -// decapsulation key dk to retrieve the same shared secret K. The PCT passes if -// the two shared secret K values are equal. The PCT shall be performed either -// when keys are generated/imported, prior to the first exportation, or prior to -// the first operational use (if not exported before the first use)." -func kemPCT(dk *DecapsulationKey768) error { - ek := dk.EncapsulationKey() - K, c := ek.Encapsulate() - K1, err := dk.Decapsulate(c) - if err != nil { - return err - } - if subtle.ConstantTimeCompare(K, K1) != 1 { - return errors.New("mlkem: PCT failed") - } - return nil -} - -// Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from a DRBG. -// -// The shared key must be kept secret. -func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) { - // The actual logic is in a separate function to outline this allocation. - var cc [CiphertextSize768]byte - return ek.encapsulate(&cc) -} - -func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (sharedKey, ciphertext []byte) { - var m [messageSize]byte - drbg.Read(m[:]) - // Note that the modulus check (step 2 of the encapsulation key check from - // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK. - //fips140.RecordApproved() - return kemEncaps(cc, ek, &m) -} - -// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for -// use in tests. -func (ek *EncapsulationKey768) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) { - cc := &[CiphertextSize768]byte{} - return kemEncaps(cc, ek, m) -} - -// kemEncaps generates a shared key and an associated ciphertext. -// -// It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17. -func kemEncaps(cc *[CiphertextSize768]byte, ek *EncapsulationKey768, m *[messageSize]byte) (K, c []byte) { - g := sha3.New512() - g.Write(m[:]) - g.Write(ek.h[:]) - G := g.Sum(nil) - K, r := G[:SharedKeySize], G[SharedKeySize:] - c = pkeEncrypt(cc, &ek.encryptionKey, m, r) - return K, c -} - -// NewEncapsulationKey768 parses an encapsulation key from its encoded form. -// If the encapsulation key is not valid, NewEncapsulationKey768 returns an error. -func NewEncapsulationKey768(encapsulationKey []byte) (*EncapsulationKey768, error) { - // The actual logic is in a separate function to outline this allocation. - ek := &EncapsulationKey768{} - return parseEK(ek, encapsulationKey) -} - -// parseEK parses an encryption key from its encoded form. -// -// It implements the initial stages of K-PKE.Encrypt according to FIPS 203, -// Algorithm 14. -func parseEK(ek *EncapsulationKey768, ekPKE []byte) (*EncapsulationKey768, error) { - if len(ekPKE) != EncapsulationKeySize768 { - return nil, errors.New("mlkem: invalid encapsulation key length") - } - - h := sha3.New256() - h.Write(ekPKE) - h.Sum(ek.h[:0]) - - for i := range ek.t { - var err error - ek.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) - if err != nil { - return nil, err - } - ekPKE = ekPKE[encodingSize12:] - } - copy(ek.ρ[:], ekPKE) - - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - ek.a[i*k+j] = sampleNTT(ek.ρ[:], j, i) - } - } - - return ek, nil -} - -// pkeEncrypt encrypt a plaintext message. -// -// It implements K-PKE.Encrypt according to FIPS 203, Algorithm 14, although the -// computation of t and AT is done in parseEK. -func pkeEncrypt(cc *[CiphertextSize768]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 (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { - if len(ciphertext) != CiphertextSize768 { - return nil, errors.New("mlkem: invalid ciphertext length") - } - c := (*[CiphertextSize768]byte)(ciphertext) - // Note that the hash check (step 3 of the decapsulation input check from - // FIPS 203, Section 7.3) is foregone as a DecapsulationKey is always - // validly generated by ML-KEM.KeyGen_internal. - return kemDecaps(dk, c), nil -} - -// kemDecaps produces a shared key from a ciphertext. -// -// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18. -func kemDecaps(dk *DecapsulationKey768, c *[CiphertextSize768]byte) (K []byte) { - //fips140.RecordApproved() - m := pkeDecrypt(&dk.decryptionKey, c) - g := sha3.New512() - g.Write(m[:]) - g.Write(dk.h[:]) - G := g.Sum(make([]byte, 0, 64)) - Kprime, r := G[:SharedKeySize], G[SharedKeySize:] - J := sha3.NewShake256() - J.Write(dk.z[:]) - J.Write(c[:]) - Kout := make([]byte, SharedKeySize) - J.Read(Kout) - var cc [CiphertextSize768]byte - c1 := pkeEncrypt(&cc, &dk.encryptionKey, (*[32]byte)(m), r) - - subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) - return Kout -} - -// pkeDecrypt decrypts a ciphertext. -// -// It implements K-PKE.Decrypt according to FIPS 203, Algorithm 15, -// although s is retained from kemKeyGen. -func pkeDecrypt(dx *decryptionKey, c *[CiphertextSize768]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) -} \ No newline at end of file diff --git a/randutil/randutil.go b/randutil/randutil.go deleted file mode 100644 index 70a9e96..0000000 --- a/randutil/randutil.go +++ /dev/null @@ -1,26 +0,0 @@ -// 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 randutil contains internal randomness utilities for various -// crypto packages. -package randutil - -import ( - "io" - "math/rand/v2" -) - -// MaybeReadByte reads a single byte from r with 50% probability. This is used -// to ensure that callers do not depend on non-guaranteed behaviour, e.g. -// assuming that rsa.GenerateKey is deterministic w.r.t. a given random stream. -// -// This does not affect tests that pass a stream of fixed bytes as the random -// source (e.g. a zeroReader). -func MaybeReadByte(r io.Reader) { - if rand.Uint64()&1 == 1 { - return - } - var buf [1]byte - r.Read(buf[:]) -} \ No newline at end of file diff --git a/sha256/sha256.go b/sha256/sha256.go deleted file mode 100644 index d0e15b7..0000000 --- a/sha256/sha256.go +++ /dev/null @@ -1,231 +0,0 @@ -// 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 sha256 implements the SHA-224 and SHA-256 hash algorithms as defined -// in FIPS 180-4. -package sha256 - -import ( - //"github.com/xtls/reality" - "github.com/xtls/reality/byteorder" - "errors" -) - -// The size of a SHA-256 checksum in bytes. -const size = 32 - -// The size of a SHA-224 checksum in bytes. -const size224 = 28 - -// The block size of SHA-256 and SHA-224 in bytes. -const blockSize = 64 - -const ( - chunk = 64 - init0 = 0x6A09E667 - init1 = 0xBB67AE85 - init2 = 0x3C6EF372 - init3 = 0xA54FF53A - init4 = 0x510E527F - init5 = 0x9B05688C - init6 = 0x1F83D9AB - init7 = 0x5BE0CD19 - init0_224 = 0xC1059ED8 - init1_224 = 0x367CD507 - init2_224 = 0x3070DD17 - init3_224 = 0xF70E5939 - init4_224 = 0xFFC00B31 - init5_224 = 0x68581511 - init6_224 = 0x64F98FA7 - init7_224 = 0xBEFA4FA4 -) - -// Digest is a SHA-224 or SHA-256 [hash.Hash] implementation. -type Digest struct { - h [8]uint32 - x [chunk]byte - nx int - len uint64 - is224 bool // mark if this digest is SHA-224 -} - -const ( - magic224 = "sha\x02" - magic256 = "sha\x03" - marshaledSize = len(magic256) + 8*4 + chunk + 8 -) - -func (d *Digest) MarshalBinary() ([]byte, error) { - return d.AppendBinary(make([]byte, 0, marshaledSize)) -} - -func (d *Digest) AppendBinary(b []byte) ([]byte, error) { - if d.is224 { - b = append(b, magic224...) - } else { - b = append(b, magic256...) - } - b = byteorder.BEAppendUint32(b, d.h[0]) - b = byteorder.BEAppendUint32(b, d.h[1]) - b = byteorder.BEAppendUint32(b, d.h[2]) - b = byteorder.BEAppendUint32(b, d.h[3]) - b = byteorder.BEAppendUint32(b, d.h[4]) - b = byteorder.BEAppendUint32(b, d.h[5]) - b = byteorder.BEAppendUint32(b, d.h[6]) - b = byteorder.BEAppendUint32(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BEAppendUint64(b, d.len) - return b, nil -} - -func (d *Digest) UnmarshalBinary(b []byte) error { - if len(b) < len(magic224) || (d.is224 && string(b[:len(magic224)]) != magic224) || (!d.is224 && string(b[:len(magic256)]) != magic256) { - return errors.New("crypto/sha256: invalid hash state identifier") - } - if len(b) != marshaledSize { - return errors.New("crypto/sha256: invalid hash state size") - } - b = b[len(magic224):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b, d.h[4] = consumeUint32(b) - b, d.h[5] = consumeUint32(b) - b, d.h[6] = consumeUint32(b) - b, d.h[7] = consumeUint32(b) - b = b[copy(d.x[:], b):] - b, d.len = consumeUint64(b) - d.nx = int(d.len % chunk) - return nil -} - -func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BEUint64(b) -} - -func consumeUint32(b []byte) ([]byte, uint32) { - return b[4:], byteorder.BEUint32(b) -} - -func (d *Digest) Reset() { - if !d.is224 { - d.h[0] = init0 - d.h[1] = init1 - d.h[2] = init2 - d.h[3] = init3 - d.h[4] = init4 - d.h[5] = init5 - d.h[6] = init6 - d.h[7] = init7 - } else { - d.h[0] = init0_224 - d.h[1] = init1_224 - d.h[2] = init2_224 - d.h[3] = init3_224 - d.h[4] = init4_224 - d.h[5] = init5_224 - d.h[6] = init6_224 - d.h[7] = init7_224 - } - d.nx = 0 - d.len = 0 -} - -// New returns a new Digest computing the SHA-256 hash. -func New() *Digest { - d := new(Digest) - d.Reset() - return d -} - -// New224 returns a new Digest computing the SHA-224 hash. -func New224() *Digest { - d := new(Digest) - d.is224 = true - d.Reset() - return d -} - -func (d *Digest) Size() int { - if !d.is224 { - return size - } - return size224 -} - -func (d *Digest) BlockSize() int { return blockSize } - -func (d *Digest) Write(p []byte) (nn int, err error) { - nn = len(p) - d.len += uint64(nn) - if d.nx > 0 { - n := copy(d.x[d.nx:], p) - d.nx += n - if d.nx == chunk { - block(d, d.x[:]) - d.nx = 0 - } - p = p[n:] - } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(d, p[:n]) - p = p[n:] - } - if len(p) > 0 { - d.nx = copy(d.x[:], p) - } - return -} - -func (d *Digest) Sum(in []byte) []byte { - //fips140.RecordApproved() - // Make a copy of d so that caller can keep writing and summing. - d0 := *d - hash := d0.checkSum() - if d0.is224 { - return append(in, hash[:size224]...) - } - return append(in, hash[:]...) -} - -func (d *Digest) checkSum() [size]byte { - len := d.len - // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. - var tmp [64 + 8]byte // padding + length buffer - tmp[0] = 0x80 - var t uint64 - if len%64 < 56 { - t = 56 - len%64 - } else { - t = 64 + 56 - len%64 - } - - // Length in bits. - len <<= 3 - padlen := tmp[:t+8] - byteorder.BEPutUint64(padlen[t+0:], len) - d.Write(padlen) - - if d.nx != 0 { - panic("d.nx != 0") - } - - var digest [size]byte - - byteorder.BEPutUint32(digest[0:], d.h[0]) - byteorder.BEPutUint32(digest[4:], d.h[1]) - byteorder.BEPutUint32(digest[8:], d.h[2]) - byteorder.BEPutUint32(digest[12:], d.h[3]) - byteorder.BEPutUint32(digest[16:], d.h[4]) - byteorder.BEPutUint32(digest[20:], d.h[5]) - byteorder.BEPutUint32(digest[24:], d.h[6]) - if !d.is224 { - byteorder.BEPutUint32(digest[28:], d.h[7]) - } - - return digest -} \ No newline at end of file diff --git a/sha256/sha256block.go b/sha256/sha256block.go deleted file mode 100644 index 55fae63..0000000 --- a/sha256/sha256block.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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. - -// SHA256 block step. -// In its own file so that a faster assembly or C version -// can be substituted easily. - -package sha256 - -import "math/bits" - -var _K = [...]uint32{ - 0x428a2f98, - 0x71374491, - 0xb5c0fbcf, - 0xe9b5dba5, - 0x3956c25b, - 0x59f111f1, - 0x923f82a4, - 0xab1c5ed5, - 0xd807aa98, - 0x12835b01, - 0x243185be, - 0x550c7dc3, - 0x72be5d74, - 0x80deb1fe, - 0x9bdc06a7, - 0xc19bf174, - 0xe49b69c1, - 0xefbe4786, - 0x0fc19dc6, - 0x240ca1cc, - 0x2de92c6f, - 0x4a7484aa, - 0x5cb0a9dc, - 0x76f988da, - 0x983e5152, - 0xa831c66d, - 0xb00327c8, - 0xbf597fc7, - 0xc6e00bf3, - 0xd5a79147, - 0x06ca6351, - 0x14292967, - 0x27b70a85, - 0x2e1b2138, - 0x4d2c6dfc, - 0x53380d13, - 0x650a7354, - 0x766a0abb, - 0x81c2c92e, - 0x92722c85, - 0xa2bfe8a1, - 0xa81a664b, - 0xc24b8b70, - 0xc76c51a3, - 0xd192e819, - 0xd6990624, - 0xf40e3585, - 0x106aa070, - 0x19a4c116, - 0x1e376c08, - 0x2748774c, - 0x34b0bcb5, - 0x391c0cb3, - 0x4ed8aa4a, - 0x5b9cca4f, - 0x682e6ff3, - 0x748f82ee, - 0x78a5636f, - 0x84c87814, - 0x8cc70208, - 0x90befffa, - 0xa4506ceb, - 0xbef9a3f7, - 0xc67178f2, -} - -func blockGeneric(dig *Digest, p []byte) { - var w [64]uint32 - h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] - for len(p) >= chunk { - // Can interlace the computation of w with the - // rounds below if needed for speed. - for i := 0; i < 16; i++ { - j := i * 4 - w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3]) - } - for i := 16; i < 64; i++ { - v1 := w[i-2] - t1 := (bits.RotateLeft32(v1, -17)) ^ (bits.RotateLeft32(v1, -19)) ^ (v1 >> 10) - v2 := w[i-15] - t2 := (bits.RotateLeft32(v2, -7)) ^ (bits.RotateLeft32(v2, -18)) ^ (v2 >> 3) - w[i] = t1 + w[i-7] + t2 + w[i-16] - } - - a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 - - for i := 0; i < 64; i++ { - t1 := h + ((bits.RotateLeft32(e, -6)) ^ (bits.RotateLeft32(e, -11)) ^ (bits.RotateLeft32(e, -25))) + ((e & f) ^ (^e & g)) + _K[i] + w[i] - - t2 := ((bits.RotateLeft32(a, -2)) ^ (bits.RotateLeft32(a, -13)) ^ (bits.RotateLeft32(a, -22))) + ((a & b) ^ (a & c) ^ (b & c)) - - h = g - g = f - f = e - e = d + t1 - d = c - c = b - b = a - a = t1 + t2 - } - - h0 += a - h1 += b - h2 += c - h3 += d - h4 += e - h5 += f - h6 += g - h7 += h - - p = p[chunk:] - } - - dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 -} \ No newline at end of file diff --git a/sha256/sha256block_noasm.go b/sha256/sha256block_noasm.go deleted file mode 100644 index 119a998..0000000 --- a/sha256/sha256block_noasm.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 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 sha256 - -func block(dig *Digest, p []byte) { - blockGeneric(dig, p) -} \ No newline at end of file diff --git a/sha3/hashes.go b/sha3/hashes.go deleted file mode 100644 index b719ca1..0000000 --- a/sha3/hashes.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2014 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 sha3 - -// New224 returns a new Digest computing the SHA3-224 hash. -func New224() *Digest { - return &Digest{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3} -} - -// New256 returns a new Digest computing the SHA3-256 hash. -func New256() *Digest { - return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3} -} - -// New384 returns a new Digest computing the SHA3-384 hash. -func New384() *Digest { - return &Digest{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3} -} - -// New512 returns a new Digest computing the SHA3-512 hash. -func New512() *Digest { - return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3} -} - -// TODO(fips): do this in the stdlib crypto/sha3 package. -// -// crypto.RegisterHash(crypto.SHA3_224, New224) -// crypto.RegisterHash(crypto.SHA3_256, New256) -// crypto.RegisterHash(crypto.SHA3_384, New384) -// crypto.RegisterHash(crypto.SHA3_512, New512) - -const ( - dsbyteSHA3 = 0b00000110 - dsbyteKeccak = 0b00000001 - dsbyteShake = 0b00011111 - dsbyteCShake = 0b00000100 - - // rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in - // bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits. - rateK256 = (1600 - 256) / 8 - rateK448 = (1600 - 448) / 8 - rateK512 = (1600 - 512) / 8 - rateK768 = (1600 - 768) / 8 - rateK1024 = (1600 - 1024) / 8 -) - -// NewLegacyKeccak256 returns a new Digest computing the legacy, non-standard -// Keccak-256 hash. -func NewLegacyKeccak256() *Digest { - return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak} -} - -// NewLegacyKeccak512 returns a new Digest computing the legacy, non-standard -// Keccak-512 hash. -func NewLegacyKeccak512() *Digest { - return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak} -} \ No newline at end of file diff --git a/sha3/keccakf.go b/sha3/keccakf.go deleted file mode 100644 index 1a6ec83..0000000 --- a/sha3/keccakf.go +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright 2014 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 sha3 - -import ( - "encoding/binary" - - "github.com/xtls/reality/byteorder" - //"crypto/internal/fips140deps/cpu" - "math/bits" - "unsafe" -) - -// rc stores the round constants for use in the ι step. -var rc = [24]uint64{ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808A, - 0x8000000080008000, - 0x000000000000808B, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008A, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000A, - 0x000000008000808B, - 0x800000000000008B, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800A, - 0x800000008000000A, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -} - -// keccakF1600Generic applies the Keccak permutation. -func keccakF1600Generic(da *[200]byte) { - var a *[25]uint64 - //if cpu.BigEndian { - if binary.NativeEndian.Uint16([]byte{0x12, 0x34}) != uint16(0x3412) { - a = new([25]uint64) - for i := range a { - a[i] = byteorder.LEUint64(da[i*8:]) - } - defer func() { - for i := range a { - byteorder.LEPutUint64(da[i*8:], a[i]) - } - }() - } else { - a = (*[25]uint64)(unsafe.Pointer(da)) - } - - // Implementation translated from Keccak-inplace.c - // in the keccak reference code. - var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 - - for i := 0; i < 24; i += 4 { - // Combines the 5 steps in each round into 2 steps. - // Unrolls 4 rounds per loop and spreads some steps across rounds. - - // Round 1 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[6] ^ d1 - bc1 = bits.RotateLeft64(t, 44) - t = a[12] ^ d2 - bc2 = bits.RotateLeft64(t, 43) - t = a[18] ^ d3 - bc3 = bits.RotateLeft64(t, 21) - t = a[24] ^ d4 - bc4 = bits.RotateLeft64(t, 14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] - a[6] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc2 = bits.RotateLeft64(t, 3) - t = a[16] ^ d1 - bc3 = bits.RotateLeft64(t, 45) - t = a[22] ^ d2 - bc4 = bits.RotateLeft64(t, 61) - t = a[3] ^ d3 - bc0 = bits.RotateLeft64(t, 28) - t = a[9] ^ d4 - bc1 = bits.RotateLeft64(t, 20) - a[10] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc4 = bits.RotateLeft64(t, 18) - t = a[1] ^ d1 - bc0 = bits.RotateLeft64(t, 1) - t = a[7] ^ d2 - bc1 = bits.RotateLeft64(t, 6) - t = a[13] ^ d3 - bc2 = bits.RotateLeft64(t, 25) - t = a[19] ^ d4 - bc3 = bits.RotateLeft64(t, 8) - a[20] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc1 = bits.RotateLeft64(t, 36) - t = a[11] ^ d1 - bc2 = bits.RotateLeft64(t, 10) - t = a[17] ^ d2 - bc3 = bits.RotateLeft64(t, 15) - t = a[23] ^ d3 - bc4 = bits.RotateLeft64(t, 56) - t = a[4] ^ d4 - bc0 = bits.RotateLeft64(t, 27) - a[5] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc3 = bits.RotateLeft64(t, 41) - t = a[21] ^ d1 - bc4 = bits.RotateLeft64(t, 2) - t = a[2] ^ d2 - bc0 = bits.RotateLeft64(t, 62) - t = a[8] ^ d3 - bc1 = bits.RotateLeft64(t, 55) - t = a[14] ^ d4 - bc2 = bits.RotateLeft64(t, 39) - a[15] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - // Round 2 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[16] ^ d1 - bc1 = bits.RotateLeft64(t, 44) - t = a[7] ^ d2 - bc2 = bits.RotateLeft64(t, 43) - t = a[23] ^ d3 - bc3 = bits.RotateLeft64(t, 21) - t = a[14] ^ d4 - bc4 = bits.RotateLeft64(t, 14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] - a[16] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc2 = bits.RotateLeft64(t, 3) - t = a[11] ^ d1 - bc3 = bits.RotateLeft64(t, 45) - t = a[2] ^ d2 - bc4 = bits.RotateLeft64(t, 61) - t = a[18] ^ d3 - bc0 = bits.RotateLeft64(t, 28) - t = a[9] ^ d4 - bc1 = bits.RotateLeft64(t, 20) - a[20] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc4 = bits.RotateLeft64(t, 18) - t = a[6] ^ d1 - bc0 = bits.RotateLeft64(t, 1) - t = a[22] ^ d2 - bc1 = bits.RotateLeft64(t, 6) - t = a[13] ^ d3 - bc2 = bits.RotateLeft64(t, 25) - t = a[4] ^ d4 - bc3 = bits.RotateLeft64(t, 8) - a[15] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc1 = bits.RotateLeft64(t, 36) - t = a[1] ^ d1 - bc2 = bits.RotateLeft64(t, 10) - t = a[17] ^ d2 - bc3 = bits.RotateLeft64(t, 15) - t = a[8] ^ d3 - bc4 = bits.RotateLeft64(t, 56) - t = a[24] ^ d4 - bc0 = bits.RotateLeft64(t, 27) - a[10] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc3 = bits.RotateLeft64(t, 41) - t = a[21] ^ d1 - bc4 = bits.RotateLeft64(t, 2) - t = a[12] ^ d2 - bc0 = bits.RotateLeft64(t, 62) - t = a[3] ^ d3 - bc1 = bits.RotateLeft64(t, 55) - t = a[19] ^ d4 - bc2 = bits.RotateLeft64(t, 39) - a[5] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - // Round 3 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[11] ^ d1 - bc1 = bits.RotateLeft64(t, 44) - t = a[22] ^ d2 - bc2 = bits.RotateLeft64(t, 43) - t = a[8] ^ d3 - bc3 = bits.RotateLeft64(t, 21) - t = a[19] ^ d4 - bc4 = bits.RotateLeft64(t, 14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] - a[11] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc2 = bits.RotateLeft64(t, 3) - t = a[1] ^ d1 - bc3 = bits.RotateLeft64(t, 45) - t = a[12] ^ d2 - bc4 = bits.RotateLeft64(t, 61) - t = a[23] ^ d3 - bc0 = bits.RotateLeft64(t, 28) - t = a[9] ^ d4 - bc1 = bits.RotateLeft64(t, 20) - a[15] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc4 = bits.RotateLeft64(t, 18) - t = a[16] ^ d1 - bc0 = bits.RotateLeft64(t, 1) - t = a[2] ^ d2 - bc1 = bits.RotateLeft64(t, 6) - t = a[13] ^ d3 - bc2 = bits.RotateLeft64(t, 25) - t = a[24] ^ d4 - bc3 = bits.RotateLeft64(t, 8) - a[5] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc1 = bits.RotateLeft64(t, 36) - t = a[6] ^ d1 - bc2 = bits.RotateLeft64(t, 10) - t = a[17] ^ d2 - bc3 = bits.RotateLeft64(t, 15) - t = a[3] ^ d3 - bc4 = bits.RotateLeft64(t, 56) - t = a[14] ^ d4 - bc0 = bits.RotateLeft64(t, 27) - a[20] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc3 = bits.RotateLeft64(t, 41) - t = a[21] ^ d1 - bc4 = bits.RotateLeft64(t, 2) - t = a[7] ^ d2 - bc0 = bits.RotateLeft64(t, 62) - t = a[18] ^ d3 - bc1 = bits.RotateLeft64(t, 55) - t = a[4] ^ d4 - bc2 = bits.RotateLeft64(t, 39) - a[10] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - // Round 4 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[1] ^ d1 - bc1 = bits.RotateLeft64(t, 44) - t = a[2] ^ d2 - bc2 = bits.RotateLeft64(t, 43) - t = a[3] ^ d3 - bc3 = bits.RotateLeft64(t, 21) - t = a[4] ^ d4 - bc4 = bits.RotateLeft64(t, 14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] - a[1] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc2 = bits.RotateLeft64(t, 3) - t = a[6] ^ d1 - bc3 = bits.RotateLeft64(t, 45) - t = a[7] ^ d2 - bc4 = bits.RotateLeft64(t, 61) - t = a[8] ^ d3 - bc0 = bits.RotateLeft64(t, 28) - t = a[9] ^ d4 - bc1 = bits.RotateLeft64(t, 20) - a[5] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc4 = bits.RotateLeft64(t, 18) - t = a[11] ^ d1 - bc0 = bits.RotateLeft64(t, 1) - t = a[12] ^ d2 - bc1 = bits.RotateLeft64(t, 6) - t = a[13] ^ d3 - bc2 = bits.RotateLeft64(t, 25) - t = a[14] ^ d4 - bc3 = bits.RotateLeft64(t, 8) - a[10] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc1 = bits.RotateLeft64(t, 36) - t = a[16] ^ d1 - bc2 = bits.RotateLeft64(t, 10) - t = a[17] ^ d2 - bc3 = bits.RotateLeft64(t, 15) - t = a[18] ^ d3 - bc4 = bits.RotateLeft64(t, 56) - t = a[19] ^ d4 - bc0 = bits.RotateLeft64(t, 27) - a[15] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc3 = bits.RotateLeft64(t, 41) - t = a[21] ^ d1 - bc4 = bits.RotateLeft64(t, 2) - t = a[22] ^ d2 - bc0 = bits.RotateLeft64(t, 62) - t = a[23] ^ d3 - bc1 = bits.RotateLeft64(t, 55) - t = a[24] ^ d4 - bc2 = bits.RotateLeft64(t, 39) - a[20] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - } -} \ No newline at end of file diff --git a/sha3/sha3.go b/sha3/sha3.go deleted file mode 100644 index 7e4345d..0000000 --- a/sha3/sha3.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2014 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 sha3 implements the SHA-3 fixed-output-length hash functions and -// the SHAKE variable-output-length functions defined by [FIPS 202], as well as -// the cSHAKE extendable-output-length functions defined by [SP 800-185]. -// -// [FIPS 202]: https://doi.org/10.6028/NIST.FIPS.202 -// [SP 800-185]: https://doi.org/10.6028/NIST.SP.800-185 -package sha3 - -import ( - // "github.com/xtls/reality/fips140" - "github.com/xtls/reality/subtle" - "errors" -) - -// spongeDirection indicates the direction bytes are flowing through the sponge. -type spongeDirection int - -const ( - // spongeAbsorbing indicates that the sponge is absorbing input. - spongeAbsorbing spongeDirection = iota - // spongeSqueezing indicates that the sponge is being squeezed. - spongeSqueezing -) - -type Digest struct { - a [1600 / 8]byte // main state of the hash - - // a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR - // into before running the permutation. If squeezing, it's the remaining - // output to produce before running the permutation. - n, rate int - - // dsbyte contains the "domain separation" bits and the first bit of - // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the - // SHA-3 and SHAKE functions by appending bitstrings to the message. - // Using a little-endian bit-ordering convention, these are "01" for SHA-3 - // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the - // padding rule from section 5.1 is applied to pad the message to a multiple - // of the rate, which involves adding a "1" bit, zero or more "0" bits, and - // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, - // giving 00000110b (0x06) and 00011111b (0x1f). - // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf - // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and - // Extendable-Output Functions (May 2014)" - dsbyte byte - - outputLen int // the default output size in bytes - state spongeDirection // whether the sponge is absorbing or squeezing -} - -// BlockSize returns the rate of sponge underlying this hash function. -func (d *Digest) BlockSize() int { return d.rate } - -// Size returns the output size of the hash function in bytes. -func (d *Digest) Size() int { return d.outputLen } - -// Reset resets the Digest to its initial state. -func (d *Digest) Reset() { - // Zero the permutation's state. - for i := range d.a { - d.a[i] = 0 - } - d.state = spongeAbsorbing - d.n = 0 -} - -func (d *Digest) Clone() *Digest { - ret := *d - return &ret -} - -// permute applies the KeccakF-1600 permutation. -func (d *Digest) permute() { - keccakF1600(&d.a) - d.n = 0 -} - -// padAndPermute appends the domain separation bits in dsbyte, applies -// the multi-bitrate 10..1 padding rule, and permutes the state. -func (d *Digest) padAndPermute() { - // Pad with this instance's domain-separator bits. We know that there's - // at least one byte of space in the sponge because, if it were full, - // permute would have been called to empty it. dsbyte also contains the - // first one bit for the padding. See the comment in the state struct. - d.a[d.n] ^= d.dsbyte - // This adds the final one bit for the padding. Because of the way that - // bits are numbered from the LSB upwards, the final bit is the MSB of - // the last byte. - d.a[d.rate-1] ^= 0x80 - // Apply the permutation - d.permute() - d.state = spongeSqueezing -} - -// Write absorbs more data into the hash's state. -func (d *Digest) Write(p []byte) (n int, err error) { return d.write(p) } -func (d *Digest) writeGeneric(p []byte) (n int, err error) { - if d.state != spongeAbsorbing { - panic("sha3: Write after Read") - } - - n = len(p) - - for len(p) > 0 { - x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) - d.n += x - p = p[x:] - - // If the sponge is full, apply the permutation. - if d.n == d.rate { - d.permute() - } - } - - return -} - -// read squeezes an arbitrary number of bytes from the sponge. -func (d *Digest) readGeneric(out []byte) (n int, err error) { - // If we're still absorbing, pad and apply the permutation. - if d.state == spongeAbsorbing { - d.padAndPermute() - } - - n = len(out) - - // Now, do the squeezing. - for len(out) > 0 { - // Apply the permutation if we've squeezed the sponge dry. - if d.n == d.rate { - d.permute() - } - - x := copy(out, d.a[d.n:d.rate]) - d.n += x - out = out[x:] - } - - return -} - -// Sum appends the current hash to b and returns the resulting slice. -// It does not change the underlying hash state. -func (d *Digest) Sum(b []byte) []byte { - // fips140.RecordApproved() - return d.sum(b) -} - -func (d *Digest) sumGeneric(b []byte) []byte { - if d.state != spongeAbsorbing { - panic("sha3: Sum after Read") - } - - // Make a copy of the original hash so that caller can keep writing - // and summing. - dup := d.Clone() - hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation - dup.read(hash) - return append(b, hash...) -} - -const ( - magicSHA3 = "sha\x08" - magicShake = "sha\x09" - magicCShake = "sha\x0a" - magicKeccak = "sha\x0b" - // magic || rate || main state || n || sponge direction - marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1 -) - -func (d *Digest) MarshalBinary() ([]byte, error) { - return d.AppendBinary(make([]byte, 0, marshaledSize)) -} - -func (d *Digest) AppendBinary(b []byte) ([]byte, error) { - switch d.dsbyte { - case dsbyteSHA3: - b = append(b, magicSHA3...) - case dsbyteShake: - b = append(b, magicShake...) - case dsbyteCShake: - b = append(b, magicCShake...) - case dsbyteKeccak: - b = append(b, magicKeccak...) - default: - panic("unknown dsbyte") - } - // rate is at most 168, and n is at most rate. - b = append(b, byte(d.rate)) - b = append(b, d.a[:]...) - b = append(b, byte(d.n), byte(d.state)) - return b, nil -} - -func (d *Digest) UnmarshalBinary(b []byte) error { - if len(b) != marshaledSize { - return errors.New("sha3: invalid hash state") - } - - magic := string(b[:len(magicSHA3)]) - b = b[len(magicSHA3):] - switch { - case magic == magicSHA3 && d.dsbyte == dsbyteSHA3: - case magic == magicShake && d.dsbyte == dsbyteShake: - case magic == magicCShake && d.dsbyte == dsbyteCShake: - case magic == magicKeccak && d.dsbyte == dsbyteKeccak: - default: - return errors.New("sha3: invalid hash state identifier") - } - - rate := int(b[0]) - b = b[1:] - if rate != d.rate { - return errors.New("sha3: invalid hash state function") - } - - copy(d.a[:], b) - b = b[len(d.a):] - - n, state := int(b[0]), spongeDirection(b[1]) - if n > d.rate { - return errors.New("sha3: invalid hash state") - } - d.n = n - if state != spongeAbsorbing && state != spongeSqueezing { - return errors.New("sha3: invalid hash state") - } - d.state = state - - return nil -} \ No newline at end of file diff --git a/sha3/sha3_noasm.go b/sha3/sha3_noasm.go deleted file mode 100644 index eba7eca..0000000 --- a/sha3/sha3_noasm.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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 sha3 - -func keccakF1600(a *[200]byte) { - keccakF1600Generic(a) -} - -func (d *Digest) write(p []byte) (n int, err error) { - return d.writeGeneric(p) -} -func (d *Digest) read(out []byte) (n int, err error) { - return d.readGeneric(out) -} -func (d *Digest) sum(b []byte) []byte { - return d.sumGeneric(b) -} \ No newline at end of file diff --git a/sha3/shake.go b/sha3/shake.go deleted file mode 100644 index d4329ea..0000000 --- a/sha3/shake.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2014 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 sha3 - -import ( - "bytes" - //"crypto/internal/fips140" - "github.com/xtls/reality/byteorder" - "errors" - "math/bits" -) - -type SHAKE struct { - d Digest // SHA-3 state context and Read/Write operations - - // initBlock is the cSHAKE specific initialization set of bytes. It is initialized - // by newCShake function and stores concatenation of N followed by S, encoded - // by the method specified in 3.3 of [1]. - // It is stored here in order for Reset() to be able to put context into - // initial state. - initBlock []byte -} - -func bytepad(data []byte, rate int) []byte { - out := make([]byte, 0, 9+len(data)+rate-1) - out = append(out, leftEncode(uint64(rate))...) - out = append(out, data...) - if padlen := rate - len(out)%rate; padlen < rate { - out = append(out, make([]byte, padlen)...) - } - return out -} - -func leftEncode(x uint64) []byte { - // Let n be the smallest positive integer for which 2^(8n) > x. - n := (bits.Len64(x) + 7) / 8 - if n == 0 { - n = 1 - } - // Return n || x with n as a byte and x an n bytes in big-endian order. - b := make([]byte, 9) - byteorder.BEPutUint64(b[1:], x) - b = b[9-n-1:] - b[0] = byte(n) - return b -} - -func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) *SHAKE { - c := &SHAKE{d: Digest{rate: rate, outputLen: outputLen, dsbyte: dsbyte}} - c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes - c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...) - c.initBlock = append(c.initBlock, N...) - c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...) - c.initBlock = append(c.initBlock, S...) - c.Write(bytepad(c.initBlock, c.d.rate)) - return c -} - -func (s *SHAKE) BlockSize() int { return s.d.BlockSize() } -func (s *SHAKE) Size() int { return s.d.Size() } - -// Sum appends a portion of output to b and returns the resulting slice. The -// output length is selected to provide full-strength generic security: 32 bytes -// for SHAKE128 and 64 bytes for SHAKE256. It does not change the underlying -// state. It panics if any output has already been read. -func (s *SHAKE) Sum(in []byte) []byte { return s.d.Sum(in) } - -// Write absorbs more data into the hash's state. -// It panics if any output has already been read. -func (s *SHAKE) Write(p []byte) (n int, err error) { return s.d.Write(p) } - -func (s *SHAKE) Read(out []byte) (n int, err error) { - //fips140.RecordApproved() - // Note that read is not exposed on Digest since SHA-3 does not offer - // variable output length. It is only used internally by Sum. - return s.d.read(out) -} - -// Reset resets the hash to initial state. -func (s *SHAKE) Reset() { - s.d.Reset() - if len(s.initBlock) != 0 { - s.Write(bytepad(s.initBlock, s.d.rate)) - } -} - -// Clone returns a copy of the SHAKE context in its current state. -func (s *SHAKE) Clone() *SHAKE { - ret := *s - return &ret -} - -func (s *SHAKE) MarshalBinary() ([]byte, error) { - return s.AppendBinary(make([]byte, 0, marshaledSize+len(s.initBlock))) -} - -func (s *SHAKE) AppendBinary(b []byte) ([]byte, error) { - b, err := s.d.AppendBinary(b) - if err != nil { - return nil, err - } - b = append(b, s.initBlock...) - return b, nil -} - -func (s *SHAKE) UnmarshalBinary(b []byte) error { - if len(b) < marshaledSize { - return errors.New("sha3: invalid hash state") - } - if err := s.d.UnmarshalBinary(b[:marshaledSize]); err != nil { - return err - } - s.initBlock = bytes.Clone(b[marshaledSize:]) - return nil -} - -// NewShake128 creates a new SHAKE128 XOF. -func NewShake128() *SHAKE { - return &SHAKE{d: Digest{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}} -} - -// NewShake256 creates a new SHAKE256 XOF. -func NewShake256() *SHAKE { - return &SHAKE{d: Digest{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}} -} - -// NewCShake128 creates a new cSHAKE128 XOF. -// -// N is used to define functions based on cSHAKE, it can be empty when plain -// cSHAKE is desired. S is a customization byte string used for domain -// separation. When N and S are both empty, this is equivalent to NewShake128. -func NewCShake128(N, S []byte) *SHAKE { - if len(N) == 0 && len(S) == 0 { - return NewShake128() - } - return newCShake(N, S, rateK256, 32, dsbyteCShake) -} - -// NewCShake256 creates a new cSHAKE256 XOF. -// -// N is used to define functions based on cSHAKE, it can be empty when plain -// cSHAKE is desired. S is a customization byte string used for domain -// separation. When N and S are both empty, this is equivalent to NewShake256. -func NewCShake256(N, S []byte) *SHAKE { - if len(N) == 0 && len(S) == 0 { - return NewShake256() - } - return newCShake(N, S, rateK512, 64, dsbyteCShake) -} \ No newline at end of file diff --git a/sha512/sha512.go b/sha512/sha512.go deleted file mode 100644 index 9fbdb56..0000000 --- a/sha512/sha512.go +++ /dev/null @@ -1,301 +0,0 @@ -// 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 sha512 implements the SHA-384, SHA-512, SHA-512/224, and SHA-512/256 -// hash algorithms as defined in FIPS 180-4. -package sha512 - -import ( - // "github.com/xtls/reality/fips140" - "github.com/xtls/reality/byteorder" - "errors" -) - -const ( - // size512 is the size, in bytes, of a SHA-512 checksum. - size512 = 64 - - // size224 is the size, in bytes, of a SHA-512/224 checksum. - size224 = 28 - - // size256 is the size, in bytes, of a SHA-512/256 checksum. - size256 = 32 - - // size384 is the size, in bytes, of a SHA-384 checksum. - size384 = 48 - - // blockSize is the block size, in bytes, of the SHA-512/224, - // SHA-512/256, SHA-384 and SHA-512 hash functions. - blockSize = 128 -) - -const ( - chunk = 128 - init0 = 0x6a09e667f3bcc908 - init1 = 0xbb67ae8584caa73b - init2 = 0x3c6ef372fe94f82b - init3 = 0xa54ff53a5f1d36f1 - init4 = 0x510e527fade682d1 - init5 = 0x9b05688c2b3e6c1f - init6 = 0x1f83d9abfb41bd6b - init7 = 0x5be0cd19137e2179 - init0_224 = 0x8c3d37c819544da2 - init1_224 = 0x73e1996689dcd4d6 - init2_224 = 0x1dfab7ae32ff9c82 - init3_224 = 0x679dd514582f9fcf - init4_224 = 0x0f6d2b697bd44da8 - init5_224 = 0x77e36f7304c48942 - init6_224 = 0x3f9d85a86a1d36c8 - init7_224 = 0x1112e6ad91d692a1 - init0_256 = 0x22312194fc2bf72c - init1_256 = 0x9f555fa3c84c64c2 - init2_256 = 0x2393b86b6f53b151 - init3_256 = 0x963877195940eabd - init4_256 = 0x96283ee2a88effe3 - init5_256 = 0xbe5e1e2553863992 - init6_256 = 0x2b0199fc2c85b8aa - init7_256 = 0x0eb72ddc81c52ca2 - init0_384 = 0xcbbb9d5dc1059ed8 - init1_384 = 0x629a292a367cd507 - init2_384 = 0x9159015a3070dd17 - init3_384 = 0x152fecd8f70e5939 - init4_384 = 0x67332667ffc00b31 - init5_384 = 0x8eb44a8768581511 - init6_384 = 0xdb0c2e0d64f98fa7 - init7_384 = 0x47b5481dbefa4fa4 -) - -// Digest is a SHA-384, SHA-512, SHA-512/224, or SHA-512/256 [hash.Hash] -// implementation. -type Digest struct { - h [8]uint64 - x [chunk]byte - nx int - len uint64 - size int // size224, size256, size384, or size512 -} - -func (d *Digest) Reset() { - switch d.size { - case size384: - d.h[0] = init0_384 - d.h[1] = init1_384 - d.h[2] = init2_384 - d.h[3] = init3_384 - d.h[4] = init4_384 - d.h[5] = init5_384 - d.h[6] = init6_384 - d.h[7] = init7_384 - case size224: - d.h[0] = init0_224 - d.h[1] = init1_224 - d.h[2] = init2_224 - d.h[3] = init3_224 - d.h[4] = init4_224 - d.h[5] = init5_224 - d.h[6] = init6_224 - d.h[7] = init7_224 - case size256: - d.h[0] = init0_256 - d.h[1] = init1_256 - d.h[2] = init2_256 - d.h[3] = init3_256 - d.h[4] = init4_256 - d.h[5] = init5_256 - d.h[6] = init6_256 - d.h[7] = init7_256 - case size512: - d.h[0] = init0 - d.h[1] = init1 - d.h[2] = init2 - d.h[3] = init3 - d.h[4] = init4 - d.h[5] = init5 - d.h[6] = init6 - d.h[7] = init7 - default: - panic("unknown size") - } - d.nx = 0 - d.len = 0 -} - -const ( - magic384 = "sha\x04" - magic512_224 = "sha\x05" - magic512_256 = "sha\x06" - magic512 = "sha\x07" - marshaledSize = len(magic512) + 8*8 + chunk + 8 -) - -func (d *Digest) MarshalBinary() ([]byte, error) { - return d.AppendBinary(make([]byte, 0, marshaledSize)) -} - -func (d *Digest) AppendBinary(b []byte) ([]byte, error) { - switch d.size { - case size384: - b = append(b, magic384...) - case size224: - b = append(b, magic512_224...) - case size256: - b = append(b, magic512_256...) - case size512: - b = append(b, magic512...) - default: - panic("unknown size") - } - b = byteorder.BEAppendUint64(b, d.h[0]) - b = byteorder.BEAppendUint64(b, d.h[1]) - b = byteorder.BEAppendUint64(b, d.h[2]) - b = byteorder.BEAppendUint64(b, d.h[3]) - b = byteorder.BEAppendUint64(b, d.h[4]) - b = byteorder.BEAppendUint64(b, d.h[5]) - b = byteorder.BEAppendUint64(b, d.h[6]) - b = byteorder.BEAppendUint64(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BEAppendUint64(b, d.len) - return b, nil -} - -func (d *Digest) UnmarshalBinary(b []byte) error { - if len(b) < len(magic512) { - return errors.New("crypto/sha512: invalid hash state identifier") - } - switch { - case d.size == size384 && string(b[:len(magic384)]) == magic384: - case d.size == size224 && string(b[:len(magic512_224)]) == magic512_224: - case d.size == size256 && string(b[:len(magic512_256)]) == magic512_256: - case d.size == size512 && string(b[:len(magic512)]) == magic512: - default: - return errors.New("crypto/sha512: invalid hash state identifier") - } - if len(b) != marshaledSize { - return errors.New("crypto/sha512: invalid hash state size") - } - b = b[len(magic512):] - b, d.h[0] = consumeUint64(b) - b, d.h[1] = consumeUint64(b) - b, d.h[2] = consumeUint64(b) - b, d.h[3] = consumeUint64(b) - b, d.h[4] = consumeUint64(b) - b, d.h[5] = consumeUint64(b) - b, d.h[6] = consumeUint64(b) - b, d.h[7] = consumeUint64(b) - b = b[copy(d.x[:], b):] - b, d.len = consumeUint64(b) - d.nx = int(d.len % chunk) - return nil -} - -func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BEUint64(b) -} - -// New returns a new Digest computing the SHA-512 hash. -func New() *Digest { - d := &Digest{size: size512} - d.Reset() - return d -} - -// New512_224 returns a new Digest computing the SHA-512/224 hash. -func New512_224() *Digest { - d := &Digest{size: size224} - d.Reset() - return d -} - -// New512_256 returns a new Digest computing the SHA-512/256 hash. -func New512_256() *Digest { - d := &Digest{size: size256} - d.Reset() - return d -} - -// New384 returns a new Digest computing the SHA-384 hash. -func New384() *Digest { - d := &Digest{size: size384} - d.Reset() - return d -} - -func (d *Digest) Size() int { - return d.size -} - -func (d *Digest) BlockSize() int { return blockSize } - -func (d *Digest) Write(p []byte) (nn int, err error) { - nn = len(p) - d.len += uint64(nn) - if d.nx > 0 { - n := copy(d.x[d.nx:], p) - d.nx += n - if d.nx == chunk { - block(d, d.x[:]) - d.nx = 0 - } - p = p[n:] - } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(d, p[:n]) - p = p[n:] - } - if len(p) > 0 { - d.nx = copy(d.x[:], p) - } - return -} - -func (d *Digest) Sum(in []byte) []byte { - // fips140.RecordApproved() - // Make a copy of d so that caller can keep writing and summing. - d0 := new(Digest) - *d0 = *d - hash := d0.checkSum() - return append(in, hash[:d.size]...) -} - -func (d *Digest) checkSum() [size512]byte { - // Padding. Add a 1 bit and 0 bits until 112 bytes mod 128. - len := d.len - var tmp [128 + 16]byte // padding + length buffer - tmp[0] = 0x80 - var t uint64 - if len%128 < 112 { - t = 112 - len%128 - } else { - t = 128 + 112 - len%128 - } - - // Length in bits. - len <<= 3 - padlen := tmp[:t+16] - // Upper 64 bits are always zero, because len variable has type uint64, - // and tmp is already zeroed at that index, so we can skip updating it. - // byteorder.BEPutUint64(padlen[t+0:], 0) - byteorder.BEPutUint64(padlen[t+8:], len) - d.Write(padlen) - - if d.nx != 0 { - panic("d.nx != 0") - } - - var digest [size512]byte - byteorder.BEPutUint64(digest[0:], d.h[0]) - byteorder.BEPutUint64(digest[8:], d.h[1]) - byteorder.BEPutUint64(digest[16:], d.h[2]) - byteorder.BEPutUint64(digest[24:], d.h[3]) - byteorder.BEPutUint64(digest[32:], d.h[4]) - byteorder.BEPutUint64(digest[40:], d.h[5]) - if d.size != size384 { - byteorder.BEPutUint64(digest[48:], d.h[6]) - byteorder.BEPutUint64(digest[56:], d.h[7]) - } - - return digest -} \ No newline at end of file diff --git a/sha512/sha512block.go b/sha512/sha512block.go deleted file mode 100644 index feb6827..0000000 --- a/sha512/sha512block.go +++ /dev/null @@ -1,144 +0,0 @@ -// 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. - -// SHA512 block step. -// In its own file so that a faster assembly or C version -// can be substituted easily. - -package sha512 - -import "math/bits" - -var _K = [...]uint64{ - 0x428a2f98d728ae22, - 0x7137449123ef65cd, - 0xb5c0fbcfec4d3b2f, - 0xe9b5dba58189dbbc, - 0x3956c25bf348b538, - 0x59f111f1b605d019, - 0x923f82a4af194f9b, - 0xab1c5ed5da6d8118, - 0xd807aa98a3030242, - 0x12835b0145706fbe, - 0x243185be4ee4b28c, - 0x550c7dc3d5ffb4e2, - 0x72be5d74f27b896f, - 0x80deb1fe3b1696b1, - 0x9bdc06a725c71235, - 0xc19bf174cf692694, - 0xe49b69c19ef14ad2, - 0xefbe4786384f25e3, - 0x0fc19dc68b8cd5b5, - 0x240ca1cc77ac9c65, - 0x2de92c6f592b0275, - 0x4a7484aa6ea6e483, - 0x5cb0a9dcbd41fbd4, - 0x76f988da831153b5, - 0x983e5152ee66dfab, - 0xa831c66d2db43210, - 0xb00327c898fb213f, - 0xbf597fc7beef0ee4, - 0xc6e00bf33da88fc2, - 0xd5a79147930aa725, - 0x06ca6351e003826f, - 0x142929670a0e6e70, - 0x27b70a8546d22ffc, - 0x2e1b21385c26c926, - 0x4d2c6dfc5ac42aed, - 0x53380d139d95b3df, - 0x650a73548baf63de, - 0x766a0abb3c77b2a8, - 0x81c2c92e47edaee6, - 0x92722c851482353b, - 0xa2bfe8a14cf10364, - 0xa81a664bbc423001, - 0xc24b8b70d0f89791, - 0xc76c51a30654be30, - 0xd192e819d6ef5218, - 0xd69906245565a910, - 0xf40e35855771202a, - 0x106aa07032bbd1b8, - 0x19a4c116b8d2d0c8, - 0x1e376c085141ab53, - 0x2748774cdf8eeb99, - 0x34b0bcb5e19b48a8, - 0x391c0cb3c5c95a63, - 0x4ed8aa4ae3418acb, - 0x5b9cca4f7763e373, - 0x682e6ff3d6b2b8a3, - 0x748f82ee5defb2fc, - 0x78a5636f43172f60, - 0x84c87814a1f0ab72, - 0x8cc702081a6439ec, - 0x90befffa23631e28, - 0xa4506cebde82bde9, - 0xbef9a3f7b2c67915, - 0xc67178f2e372532b, - 0xca273eceea26619c, - 0xd186b8c721c0c207, - 0xeada7dd6cde0eb1e, - 0xf57d4f7fee6ed178, - 0x06f067aa72176fba, - 0x0a637dc5a2c898a6, - 0x113f9804bef90dae, - 0x1b710b35131c471b, - 0x28db77f523047d84, - 0x32caab7b40c72493, - 0x3c9ebe0a15c9bebc, - 0x431d67c49c100d4c, - 0x4cc5d4becb3e42b6, - 0x597f299cfc657e2a, - 0x5fcb6fab3ad6faec, - 0x6c44198c4a475817, -} - -func blockGeneric(dig *Digest, p []byte) { - var w [80]uint64 - h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] - for len(p) >= chunk { - for i := 0; i < 16; i++ { - j := i * 8 - w[i] = uint64(p[j])<<56 | uint64(p[j+1])<<48 | uint64(p[j+2])<<40 | uint64(p[j+3])<<32 | - uint64(p[j+4])<<24 | uint64(p[j+5])<<16 | uint64(p[j+6])<<8 | uint64(p[j+7]) - } - for i := 16; i < 80; i++ { - v1 := w[i-2] - t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6) - v2 := w[i-15] - t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7) - - w[i] = t1 + w[i-7] + t2 + w[i-16] - } - - a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 - - for i := 0; i < 80; i++ { - t1 := h + (bits.RotateLeft64(e, -14) ^ bits.RotateLeft64(e, -18) ^ bits.RotateLeft64(e, -41)) + ((e & f) ^ (^e & g)) + _K[i] + w[i] - - t2 := (bits.RotateLeft64(a, -28) ^ bits.RotateLeft64(a, -34) ^ bits.RotateLeft64(a, -39)) + ((a & b) ^ (a & c) ^ (b & c)) - - h = g - g = f - f = e - e = d + t1 - d = c - c = b - b = a - a = t1 + t2 - } - - h0 += a - h1 += b - h2 += c - h3 += d - h4 += e - h5 += f - h6 += g - h7 += h - - p = p[chunk:] - } - - dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 -} \ No newline at end of file diff --git a/sha512/sha512block_noasm.go b/sha512/sha512block_noasm.go deleted file mode 100644 index 1a3ada7..0000000 --- a/sha512/sha512block_noasm.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 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 sha512 - -func block(dig *Digest, p []byte) { - blockGeneric(dig, p) -} \ No newline at end of file diff --git a/subtle/constant_time.go b/subtle/constant_time.go deleted file mode 100644 index b021da3..0000000 --- a/subtle/constant_time.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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 subtle - -import ( - "math/bits" - - "github.com/xtls/reality/byteorder" -) - -// ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents -// and 0 otherwise. The time taken is a function of the length of the slices and -// is independent of the contents. If the lengths of x and y do not match it -// returns 0 immediately. -func ConstantTimeCompare(x, y []byte) int { - if len(x) != len(y) { - return 0 - } - - var v byte - - for i := 0; i < len(x); i++ { - v |= x[i] ^ y[i] - } - - return ConstantTimeByteEq(v, 0) -} - -// ConstantTimeLessOrEqBytes returns 1 if x <= y and 0 otherwise. The comparison -// is lexigraphical, or big-endian. The time taken is a function of the length of -// the slices and is independent of the contents. If the lengths of x and y do not -// match it returns 0 immediately. -func ConstantTimeLessOrEqBytes(x, y []byte) int { - if len(x) != len(y) { - return 0 - } - - // Do a constant time subtraction chain y - x. - // If there is no borrow at the end, then x <= y. - var b uint64 - for len(x) > 8 { - x0 := byteorder.BEUint64(x[len(x)-8:]) - y0 := byteorder.BEUint64(y[len(y)-8:]) - _, b = bits.Sub64(y0, x0, b) - x = x[:len(x)-8] - y = y[:len(y)-8] - } - if len(x) > 0 { - xb := make([]byte, 8) - yb := make([]byte, 8) - copy(xb[8-len(x):], x) - copy(yb[8-len(y):], y) - x0 := byteorder.BEUint64(xb) - y0 := byteorder.BEUint64(yb) - _, b = bits.Sub64(y0, x0, b) - } - return int(b ^ 1) -} - -// ConstantTimeSelect returns x if v == 1 and y if v == 0. -// Its behavior is undefined if v takes any other value. -func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y } - -// ConstantTimeByteEq returns 1 if x == y and 0 otherwise. -func ConstantTimeByteEq(x, y uint8) int { - return int((uint32(x^y) - 1) >> 31) -} - -// ConstantTimeEq returns 1 if x == y and 0 otherwise. -func ConstantTimeEq(x, y int32) int { - return int((uint64(uint32(x^y)) - 1) >> 63) -} - -// ConstantTimeCopy copies the contents of y into x (a slice of equal length) -// if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v -// takes any other value. -func ConstantTimeCopy(v int, x, y []byte) { - if len(x) != len(y) { - panic("subtle: slices have different lengths") - } - - xmask := byte(v - 1) - ymask := byte(^(v - 1)) - for i := 0; i < len(x); i++ { - x[i] = x[i]&xmask | y[i]&ymask - } -} - -// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. -// Its behavior is undefined if x or y are negative or > 2**31 - 1. -func ConstantTimeLessOrEq(x, y int) int { - x32 := int32(x) - y32 := int32(y) - return int(((x32 - y32 - 1) >> 31) & 1) -} \ No newline at end of file diff --git a/subtle/xor.go b/subtle/xor.go deleted file mode 100644 index bb7ac02..0000000 --- a/subtle/xor.go +++ /dev/null @@ -1,30 +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 file. - -package subtle - -import "github.com/xtls/reality/alias" - -// XORBytes sets dst[i] = x[i] ^ y[i] for all i < n = min(len(x), len(y)), -// returning n, the number of bytes written to dst. -// -// If dst does not have length at least n, -// XORBytes panics without writing anything to dst. -// -// dst and x or y may overlap exactly or not at all, -// otherwise XORBytes may panic. -func XORBytes(dst, x, y []byte) int { - n := min(len(x), len(y)) - if n == 0 { - return 0 - } - if n > len(dst) { - panic("subtle.XORBytes: dst too short") - } - if alias.InexactOverlap(dst[:n], x[:n]) || alias.InexactOverlap(dst[:n], y[:n]) { - panic("subtle.XORBytes: invalid overlap") - } - xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific - return n -} \ No newline at end of file diff --git a/subtle/xor_generic.go b/subtle/xor_generic.go deleted file mode 100644 index 706e5af..0000000 --- a/subtle/xor_generic.go +++ /dev/null @@ -1,62 +0,0 @@ -// 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 subtle - -import ( - "runtime" - "unsafe" -) - -const wordSize = unsafe.Sizeof(uintptr(0)) - -const supportsUnaligned = runtime.GOARCH == "386" || - runtime.GOARCH == "amd64" || - runtime.GOARCH == "ppc64" || - runtime.GOARCH == "ppc64le" || - runtime.GOARCH == "s390x" - -func xorBytes(dstb, xb, yb *byte, n int) { - // xorBytes assembly is written using pointers and n. Back to slices. - dst := unsafe.Slice(dstb, n) - x := unsafe.Slice(xb, n) - y := unsafe.Slice(yb, n) - - if supportsUnaligned || aligned(dstb, xb, yb) { - xorLoop(words(dst), words(x), words(y)) - if uintptr(n)%wordSize == 0 { - return - } - done := n &^ int(wordSize-1) - dst = dst[done:] - x = x[done:] - y = y[done:] - } - xorLoop(dst, x, y) -} - -// aligned reports whether dst, x, and y are all word-aligned pointers. -func aligned(dst, x, y *byte) bool { - return (uintptr(unsafe.Pointer(dst))|uintptr(unsafe.Pointer(x))|uintptr(unsafe.Pointer(y)))&(wordSize-1) == 0 -} - -// words returns a []uintptr pointing at the same data as x, -// with any trailing partial word removed. -func words(x []byte) []uintptr { - n := uintptr(len(x)) / wordSize - if n == 0 { - // Avoid creating a *uintptr that refers to data smaller than a uintptr; - // see issue 59334. - return nil - } - return unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), n) -} - -func xorLoop[T byte | uintptr](dst, x, y []T) { - x = x[:len(dst)] // remove bounds check in loop - y = y[:len(dst)] // remove bounds check in loop - for i := range dst { - dst[i] = x[i] ^ y[i] - } -} \ No newline at end of file diff --git a/tls12/tls12.go b/tls12/tls12.go index 998ec87..751e93f 100644 --- a/tls12/tls12.go +++ b/tls12/tls12.go @@ -5,15 +5,13 @@ package tls12 import ( - "github.com/xtls/reality/fips140" - "github.com/xtls/reality/hmac" - // "github.com/xtls/reality/sha256" - // "github.com/xtls/reality/sha512" + "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 fips140.Hash](hash func() H, secret []byte, label string, seed []byte, keyLen int) []byte { +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) @@ -24,8 +22,8 @@ func PRF[H fips140.Hash](hash func() H, secret []byte, label string, seed []byte } // pHash implements the P_hash function, as defined in RFC 5246, Section 5. -func pHash[H fips140.Hash](hash func() H, result, secret, seed []byte) { - h := hmac.New(hash, secret) +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) @@ -48,7 +46,7 @@ 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 fips140.Hash](hash func() H, preMasterSecret, transcript []byte) []byte { +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() diff --git a/tls13/tls13.go b/tls13/tls13.go index 90e8ad2..f2f0f9d 100644 --- a/tls13/tls13.go +++ b/tls13/tls13.go @@ -7,9 +7,9 @@ package tls13 import ( - "github.com/xtls/reality/byteorder" - "github.com/xtls/reality/fips140" - "github.com/xtls/reality/hkdf" + "crypto/hkdf" + "encoding/binary" + "hash" ) // We don't set the service indicator in this package but we delegate that to @@ -17,7 +17,7 @@ import ( // its own. // ExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. -func ExpandLabel[H fips140.Hash](hash func() H, secret []byte, label string, context []byte, length int) []byte { +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 @@ -30,23 +30,25 @@ func ExpandLabel[H fips140.Hash](hash func() H, secret []byte, label string, con panic("tls13: label or context too long") } hkdfLabel := make([]byte, 0, 2+1+len("tls13 ")+len(label)+1+len(context)) - hkdfLabel = byteorder.BEAppendUint16(hkdfLabel, uint16(length)) + 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...) - return hkdf.Expand(hash, secret, string(hkdfLabel), length) + b, _ := hkdf.Expand(hash, secret, string(hkdfLabel), length) + return b } -func extract[H fips140.Hash](hash func() H, newSecret, currentSecret []byte) []byte { +func extract[H hash.Hash](hash func() H, newSecret, currentSecret []byte) []byte { if newSecret == nil { newSecret = make([]byte, hash().Size()) } - return hkdf.Extract(hash, newSecret, currentSecret) + b, _ := hkdf.Extract(hash, newSecret, currentSecret) + return b } -func deriveSecret[H fips140.Hash](hash func() H, secret []byte, label string, transcript fips140.Hash) []byte { +func deriveSecret[H hash.Hash](hash func() H, secret []byte, label string, transcript hash.Hash) []byte { if transcript == nil { transcript = hash() } @@ -67,13 +69,13 @@ const ( type EarlySecret struct { secret []byte - hash func() fips140.Hash + hash func() hash.Hash } -func NewEarlySecret[H fips140.Hash](hash func() H, psk []byte) *EarlySecret { +func NewEarlySecret[H hash.Hash](h func() H, psk []byte) *EarlySecret { return &EarlySecret{ - secret: extract(hash, psk, nil), - hash: func() fips140.Hash { return hash() }, + secret: extract(h, psk, nil), + hash: func() hash.Hash { return h() }, } } @@ -83,13 +85,13 @@ func (s *EarlySecret) ResumptionBinderKey() []byte { // ClientEarlyTrafficSecret derives the client_early_traffic_secret from the // early secret and the transcript up to the ClientHello. -func (s *EarlySecret) ClientEarlyTrafficSecret(transcript fips140.Hash) []byte { +func (s *EarlySecret) ClientEarlyTrafficSecret(transcript hash.Hash) []byte { return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript) } type HandshakeSecret struct { secret []byte - hash func() fips140.Hash + hash func() hash.Hash } func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret { @@ -102,19 +104,19 @@ func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret { // ClientHandshakeTrafficSecret derives the client_handshake_traffic_secret from // the handshake secret and the transcript up to the ServerHello. -func (s *HandshakeSecret) ClientHandshakeTrafficSecret(transcript fips140.Hash) []byte { +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 fips140.Hash) []byte { +func (s *HandshakeSecret) ServerHandshakeTrafficSecret(transcript hash.Hash) []byte { return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript) } type MasterSecret struct { secret []byte - hash func() fips140.Hash + hash func() hash.Hash } func (s *HandshakeSecret) MasterSecret() *MasterSecret { @@ -127,30 +129,30 @@ func (s *HandshakeSecret) MasterSecret() *MasterSecret { // 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 fips140.Hash) []byte { +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 fips140.Hash) []byte { +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 fips140.Hash) []byte { +func (s *MasterSecret) ResumptionMasterSecret(transcript hash.Hash) []byte { return deriveSecret(s.hash, s.secret, resumptionLabel, transcript) } type ExporterMasterSecret struct { secret []byte - hash func() fips140.Hash + 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 fips140.Hash) *ExporterMasterSecret { +func (s *MasterSecret) ExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret { return &ExporterMasterSecret{ secret: deriveSecret(s.hash, s.secret, exporterLabel, transcript), hash: s.hash, @@ -159,7 +161,7 @@ func (s *MasterSecret) ExporterMasterSecret(transcript fips140.Hash) *ExporterMa // EarlyExporterMasterSecret derives the exporter_master_secret from the early secret // and the transcript up to the ClientHello. -func (s *EarlySecret) EarlyExporterMasterSecret(transcript fips140.Hash) *ExporterMasterSecret { +func (s *EarlySecret) EarlyExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret { return &ExporterMasterSecret{ secret: deriveSecret(s.hash, s.secret, earlyExporterLabel, transcript), hash: s.hash, From cfe61417e9a10ed41bb72e5666c1cc7e6bfc0976 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 14:31:47 -0400 Subject: [PATCH 14/45] crypto/tls: add server-side ECH Adds support for server-side ECH. We make a couple of implementation decisions that are not completely in-line with the spec. In particular, we don't enforce that the SNI matches the ECHConfig public_name, and we implement a hybrid shared/backend mode (rather than shared or split mode, as described in Section 7). Both of these match the behavior of BoringSSL. The hybrid server mode will either act as a shared mode server, where-in the server accepts "outer" client hellos and unwraps them before processing the "inner" hello, or accepts bare "inner" hellos initially. This lets the server operate either transparently as a shared mode server, or a backend server, in Section 7 terminology. This seems like the best implementation choice for a TLS library. Fixes #68500 Change-Id: Ife69db7c1886610742e95e76b0ca92587e6d7ed4 Reviewed-on: https://go-review.googlesource.com/c/go/+/623576 Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Auto-Submit: Roland Shoemaker Reviewed-by: Dmitri Shuralyov --- common.go | 48 +++- ech.go | 472 +++++++++++++++++++++++++++++++++----- handshake_client.go | 8 +- handshake_client_tls13.go | 8 +- handshake_messages.go | 6 +- handshake_server.go | 37 ++- handshake_server_tls13.go | 100 ++++++++ tls.go | 2 +- 8 files changed, 597 insertions(+), 84 deletions(-) diff --git a/common.go b/common.go index d088869..82f1d00 100644 --- a/common.go +++ b/common.go @@ -805,8 +805,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. @@ -824,9 +826,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 Client Hello. 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 @@ -838,6 +842,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 @@ -851,6 +869,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 (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002). + 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. @@ -938,6 +975,7 @@ func (c *Config) Clone() *Config { KeyLogWriter: c.KeyLogWriter, EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList, EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify, + EncryptedClientHelloKeys: c.EncryptedClientHelloKeys, sessionTicketKeys: c.sessionTicketKeys, autoSessionTicketKeys: c.autoSessionTicketKeys, } diff --git a/ech.go b/ech.go index 56477fb..9428cc0 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 @@ -41,12 +56,77 @@ type echConfig struct { var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList") +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{}, errMalformedECHConfig + } + if !s.ReadUint16(&ec.Length) { + return false, echConfig{}, errMalformedECHConfig + } + if len(ec.raw) < int(ec.Length)+4 { + return false, echConfig{}, errMalformedECHConfig + } + 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{}, errMalformedECHConfig + } + if !s.ReadUint16(&ec.KemID) { + return false, echConfig{}, errMalformedECHConfig + } + if !readUint16LengthPrefixed(&s, &ec.PublicKey) { + return false, echConfig{}, errMalformedECHConfig + } + var cipherSuites cryptobyte.String + if !s.ReadUint16LengthPrefixed(&cipherSuites) { + return false, echConfig{}, errMalformedECHConfig + } + for !cipherSuites.Empty() { + var c echCipher + if !cipherSuites.ReadUint16(&c.KDFID) { + return false, echConfig{}, errMalformedECHConfig + } + if !cipherSuites.ReadUint16(&c.AEADID) { + return false, echConfig{}, errMalformedECHConfig + } + ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) + } + if !s.ReadUint8(&ec.MaxNameLength) { + return false, echConfig{}, errMalformedECHConfig + } + var publicName cryptobyte.String + if !s.ReadUint8LengthPrefixed(&publicName) { + return false, echConfig{}, errMalformedECHConfig + } + ec.PublicName = publicName + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) { + return false, echConfig{}, errMalformedECHConfig + } + for !extensions.Empty() { + var e echExtension + if !extensions.ReadUint16(&e.Type) { + return false, echConfig{}, errMalformedECHConfig + } + if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { + return false, echConfig{}, errMalformedECHConfig + } + 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 @@ -56,69 +136,18 @@ func parseECHConfigList(data []byte) ([]echConfig, error) { } 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 +225,175 @@ 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, errors.New("tls: client sent invalid encrypted_client_hello extension") + } + + if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) { + return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions") + } + + return inner, nil +} + +func decryptECHPayload(context *hpke.Receipient, 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 +405,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 +479,154 @@ type ECHRejectionError struct { func (e *ECHRejectionError) Error() string { return "tls: server rejected ECH" +} + +var errMalformedECHExt = errors.New("tls: malformed 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 = errMalformedECHExt + 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 { + c.sendAlert(alertDecodeError) + return nil, nil, errors.New("tls: client sent invalid encrypted_client_hello extension") + } + + 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.SetupReceipient(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, errors.New("tls: client sent invalid encrypted_client_hello extension") + } + + 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/handshake_client.go b/handshake_client.go index 4e01c93..c2c9212 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -42,7 +42,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") @@ -201,7 +201,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") @@ -217,7 +217,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 @@ -246,7 +246,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 diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index b21c5b1..f72209a 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -39,7 +39,7 @@ type clientHandshakeStateTLS13 struct { transcript hash.Hash 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, @@ -105,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 { @@ -288,7 +288,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 @@ -604,7 +604,7 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error { } 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") + return errors.New("tls: server sent encrypted client hello retry configs after accepting encrypted client hello") } return nil diff --git a/handshake_messages.go b/handshake_messages.go index 7ae20ea..8c56bca 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -97,7 +97,7 @@ type clientHelloMsg struct { pskBinders [][]byte quicTransportParameters []byte encryptedClientHello []byte - // extensions are only populated on the server-side of a handshake + // extensions are only populated on the servers-ide of a handshake extensions []uint16 } @@ -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 74b686e..881329a 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -40,7 +40,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 +50,7 @@ func (c *Conn) serverHandshake(ctx context.Context) error { c: c, ctx: ctx, clientHello: clientHello, + echContext: ech, } return hs.handshake() } @@ -130,17 +131,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 +160,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 } @@ -163,13 +174,25 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { 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 { diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 86b618c..9a428c6 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -9,6 +9,7 @@ import ( "context" "crypto" "crypto/ed25519" + "crypto/hkdf" "crypto/hmac" "crypto/mlkem" "crypto/rand" @@ -24,6 +25,7 @@ import ( "time" "github.com/xtls/reality/fips140tls" + "github.com/xtls/reality/hpke" "github.com/xtls/reality/tls13" ) @@ -32,6 +34,18 @@ import ( // messages cause too much work in session ticket decryption attempts. const maxClientPSKIdentities = 5 +type echServerContext struct { + hpkeContext *hpke.Receipient + 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 @@ -50,6 +64,7 @@ type serverHandshakeStateTLS13 struct { trafficSecret []byte // client_application_traffic_secret_0 transcript hash.Hash clientFinished []byte + echContext *echServerContext } var ( @@ -584,6 +599,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 + } + b, _ := hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil) + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + b, + "hrr ech accept confirmation", + confTranscript.Sum(nil), + 8, + ) + helloRetryRequest.encryptedClientHello = acceptConfirmation + } + if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil { return nil, err } @@ -604,6 +636,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") @@ -691,9 +762,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 + b, _ := hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil) + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + b, + "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 } @@ -744,6 +834,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 } diff --git a/tls.go b/tls.go index b50b42a..3016766 100644 --- a/tls.go +++ b/tls.go @@ -161,7 +161,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 } From c3f6b7dc5f7da9dcc9c67e43e56425fe21ec35cf Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 14:34:55 -0400 Subject: [PATCH 15/45] crypto/internal/hpke: rename Receipient to Recipient receipient -> recipient Change-Id: I9ed5937acf0f3808283e35221f8b4f41408eee7c GitHub-Last-Rev: 0ed5ff7 GitHub-Pull-Request: #73131 Reviewed-on: https://go-review.googlesource.com/c/go/+/662175 Reviewed-by: Carlos Amedee Auto-Submit: Jorropo Auto-Submit: Filippo Valsorda Reviewed-by: Jorropo LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Dmitri Shuralyov Reviewed-by: Daniel McCarney --- ech.go | 4 ++-- handshake_server_tls13.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ech.go b/ech.go index 9428cc0..608f21b 100644 --- a/ech.go +++ b/ech.go @@ -389,7 +389,7 @@ func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHello return inner, nil } -func decryptECHPayload(context *hpke.Receipient, hello, payload []byte) ([]byte, error) { +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) } @@ -577,7 +577,7 @@ func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *e return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err) } info := append([]byte("tls ech\x00"), echKey.Config...) - hpkeContext, err := hpke.SetupReceipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap) + hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap) if err != nil { // attempt next trial decryption continue diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 9a428c6..3c560c9 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -35,7 +35,7 @@ import ( const maxClientPSKIdentities = 5 type echServerContext struct { - hpkeContext *hpke.Receipient + hpkeContext *hpke.Recipient configID uint8 ciphersuite echCipher transcript hash.Hash From cedab7cc24277431b577d3a3c290b9ac4dfe3c23 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:06:04 -0400 Subject: [PATCH 16/45] crypto/tls: implement X25519MLKEM768 This makes three related changes that work particularly well together and would require significant extra work to do separately: it replaces X25519Kyber768Draft00 with X25519MLKEM768, it makes CurvePreferences ordering crypto/tls-selected, and applies a preference to PQ key exchange methods over key shares (to mitigate downgrades). TestHandshakeServerUnsupportedKeyShare was removed because we are not rejecting unsupported key shares anymore (nor do we select them, and rejecting them actively is a MAY). It would have been nice to keep the test to check we still continue successfully, but testClientHelloFailure is broken in the face of any server-side behavior which requires writing any other messages back to the client, or reading them. Updates #69985 Fixes #69393 Change-Id: I58de76f5b8742a9bd4543fd7907c48e038507b19 Reviewed-on: https://go-review.googlesource.com/c/go/+/630775 Reviewed-by: Roland Shoemaker Reviewed-by: Dmitri Shuralyov Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: Go LUCI --- common.go | 62 ++++++++++++++------------- common_string.go | 6 +-- defaults.go | 7 +-- handshake_client.go | 25 ++++++----- handshake_client_tls13.go | 29 ++++++------- handshake_server.go | 2 +- handshake_server_tls13.go | 89 ++++++++++++++++++++++++--------------- key_schedule.go | 36 +--------------- 8 files changed, 125 insertions(+), 131 deletions(-) diff --git a/common.go b/common.go index 82f1d00..e5e25bb 100644 --- a/common.go +++ b/common.go @@ -145,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 @@ -419,9 +423,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. @@ -775,14 +782,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. @@ -1198,23 +1206,19 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { func (c *Config) curvePreferences(version uint16) []CurveID { var curvePreferences []CurveID - if c != nil && len(c.CurvePreferences) != 0 { - curvePreferences = slices.Clone(c.CurvePreferences) - if fips140tls.Required() { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return !slices.Contains(defaultCurvePreferencesFIPS, c) - }) - } - } else if fips140tls.Required() { + if fips140tls.Required() { curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) } else { curvePreferences = defaultCurvePreferences() } - if version < VersionTLS13 { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return c == x25519Kyber768Draft00 + if c != nil && len(c.CurvePreferences) != 0 { + curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { + return !slices.Contains(c.CurvePreferences, x) }) } + if version < VersionTLS13 { + curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange) + } return curvePreferences } 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/defaults.go b/defaults.go index f2d9ff3..01fb9cc 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 diff --git a/handshake_client.go b/handshake_client.go index c2c9212..5884a4f 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -20,6 +20,7 @@ import ( "hash" "io" "net" + "slices" "strings" "time" @@ -156,7 +157,9 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli } 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 @@ -165,18 +168,20 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli if _, err := io.ReadFull(config.rand(), seed); err != nil { return nil, nil, nil, err } - keyShareKeys.kyber, err = mlkem.NewDecapsulationKey768(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().Bytes()...)}, - {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 { @@ -704,7 +709,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 */ { diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index f72209a..d599244 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -322,12 +322,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") @@ -480,12 +479,12 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c := hs.c ecdhePeerData := hs.serverHello.serverShare.data - if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if len(ecdhePeerData) != x25519PublicKeySize+mlkem.CiphertextSize768 { + 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 { @@ -497,17 +496,17 @@ 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 diff --git a/handshake_server.go b/handshake_server.go index 881329a..33c528f 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -690,7 +690,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 { diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 3c560c9..80ed0a5 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -22,6 +22,7 @@ import ( "io" "math/big" "slices" + "sort" "time" "github.com/xtls/reality/fips140tls" @@ -248,37 +249,45 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { 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 { @@ -290,13 +299,13 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ecdhGroup := selectedGroup ecdhData := clientKeyShare.data - if selectedGroup == x25519Kyber768Draft00 { + if selectedGroup == X25519MLKEM768 { ecdhGroup = X25519 - if len(ecdhData) != x25519PublicKeySize+mlkem.EncapsulationKeySize768 { + 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) @@ -318,14 +327,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...) + ciphertext, mlkemSharedSecret := 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) diff --git a/key_schedule.go b/key_schedule.go index e367476..332936c 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -8,7 +8,6 @@ import ( "crypto/ecdh" "crypto/hmac" "crypto/mlkem" - "crypto/sha3" "errors" "hash" "io" @@ -54,40 +53,7 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip type keySharePrivateKeys struct { curveID CurveID ecdhe *ecdh.PrivateKey - kyber *mlkem.DecapsulationKey768 -} - -// kyberDecapsulate implements decapsulation according to Kyber Round 3. -func kyberDecapsulate(dk *mlkem.DecapsulationKey768, c []byte) ([]byte, error) { - K, err := dk.Decapsulate(c) - if err != nil { - return nil, err - } - return kyberSharedSecret(c, K), nil -} - -// kyberEncapsulate implements encapsulation according to Kyber Round 3. -func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - k, err := mlkem.NewEncapsulationKey768(ek) - if err != nil { - return nil, nil, err - } - c, ss = k.Encapsulate() - return c, kyberSharedSecret(c, ss), nil -} - -func kyberSharedSecret(c, K []byte) []byte { - // Package mlkem 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.New256() - ch.Write(c) - h.Write(ch.Sum(nil)) - out := make([]byte, 32) - h.Read(out) - return out + mlkem *mlkem.DecapsulationKey768 } const x25519PublicKeySize = 32 From a102c2f5afa8927ca1b4491a724632ce731fcd50 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:07:07 -0400 Subject: [PATCH 17/45] crypto/tls: fix a broken link Change-Id: I5d4baa454487a810f138334b613b412a5273311e Reviewed-on: https://go-review.googlesource.com/c/go/+/631875 Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda Reviewed-by: Russ Cox --- cipher_suites.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cipher_suites.go b/cipher_suites.go index 68640e7..9016ec2 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -235,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 From 4711d35b15b4da1438b49e6b71b6bf65e387e356 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:08:48 -0400 Subject: [PATCH 18/45] crypto/tls: normalize spelling of "ClientHello" in comments Change-Id: I2b62fb37ae390c42682354eaa2a9d03159563b6d Reviewed-on: https://go-review.googlesource.com/c/go/+/637179 Reviewed-by: Daniel McCarney Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: David Chase --- common.go | 4 ++-- handshake_client.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common.go b/common.go index e5e25bb..1f56bbf 100644 --- a/common.go +++ b/common.go @@ -456,7 +456,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 @@ -835,7 +835,7 @@ type Config struct { // EncryptedClientHelloRejectionVerify, if not nil, is called when ECH is // rejected by the remote server, in order to verify the ECH provider - // certificate in the outer Client Hello. If it returns a non-nil error, the + // 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. diff --git a/handshake_client.go b/handshake_client.go index 5884a4f..de2afed 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -937,7 +937,7 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { } // 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 { From ca2258490f9a0e7919bca1155543fbde0046465f Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:11:17 -0400 Subject: [PATCH 19/45] crypto/mlkem: swap order of return values of Encapsulate Per FIPS 203 (https://csrc.nist.gov/pubs/fips/203/final), the order of return values should be sharedKey, ciphertext. This commit simply swaps those return values and updates any consumers of the Encapsulate() method to respect the new order. Fixes #70950 Change-Id: I2a0d605e3baf7fe69510d60d3d35bbac18f883c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/638376 LUCI-TryBot-Result: Go LUCI Reviewed-by: Austin Clements Auto-Submit: Ian Lance Taylor Reviewed-by: Filippo Valsorda Reviewed-by: Cherry Mui --- handshake_server_tls13.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 80ed0a5..07bf06e 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -333,7 +333,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid X25519MLKEM768 client key share") } - ciphertext, mlkemSharedSecret := k.Encapsulate() + 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 From 670d92937cdfe4af9dc3298f9c63854d28fd87e3 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:14:04 -0400 Subject: [PATCH 20/45] crypto/tls: fix misspelling in comment Change-Id: Ie36a19ed6d6922e68f98e43745a417a24f8a7828 GitHub-Last-Rev: 6fb32e3 GitHub-Pull-Request: #71060 Reviewed-on: https://go-review.googlesource.com/c/go/+/639215 Auto-Submit: Ian Lance Taylor Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Ian Lance Taylor --- handshake_messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handshake_messages.go b/handshake_messages.go index 8c56bca..bcd9a83 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -97,7 +97,7 @@ type clientHelloMsg struct { pskBinders [][]byte quicTransportParameters []byte encryptedClientHello []byte - // extensions are only populated on the servers-ide of a handshake + // extensions are only populated on the server-side of a handshake extensions []uint16 } From 84d21a00065f1386f428b747bca511c1c10aeed6 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:17:34 -0400 Subject: [PATCH 21/45] crypto/tls: properly return ECH retry configs When ECH is rejected, properly take retry configs from the encrypted extensions message. Also fix the bogo shim to properly test for this behavior. We should properly map the full BoringSSL -> Go errors so that we don't run into a similar failure in the future, but this is left for a follow up CL. Fixes #70915 Change-Id: Icc1878ff6f87df059e7b83e0a431f50f1fea833c Reviewed-on: https://go-review.googlesource.com/c/go/+/638583 Reviewed-by: Damien Neil Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI --- handshake_client.go | 1 + handshake_client_tls13.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/handshake_client.go b/handshake_client.go index de2afed..73ef3b8 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -260,6 +260,7 @@ type echClientContext struct { kdfID uint16 aeadID uint16 echRejected bool + retryConfigs []byte } func (c *Conn) clientHandshake(ctx context.Context) (err error) { diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index d599244..2623a2e 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -85,7 +85,6 @@ 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]) @@ -114,9 +113,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 } } @@ -155,7 +151,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) @@ -601,9 +597,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 encrypted client hello retry configs after accepting encrypted client hello") + 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 From c25bcef61f3b97316bef66bd6a5510b2265b4e80 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:23:34 -0400 Subject: [PATCH 22/45] crypto/tls: send illegal_parameter on invalid ECHClientHello.type The spec indicates that if a client sends an invalid ECHClientHello.type in ClientHelloOuter, the server will abort the handshake with a decode_error alert. Define errInvalidECHExt for invalid ECHClientHello.type. If parseECHExt returns an errInvalidECHExt error, Conn now sends an illegal_parameter alert. Fixes #71061. Change-Id: I240241fe8bbe3e77d6ad1af989794647bfa2ff87 GitHub-Last-Rev: 3d6c233 GitHub-Pull-Request: #71062 Reviewed-on: https://go-review.googlesource.com/c/go/+/639235 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- ech.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ech.go b/ech.go index 608f21b..83fdc34 100644 --- a/ech.go +++ b/ech.go @@ -379,7 +379,7 @@ func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHello } if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) { - return nil, errors.New("tls: client sent invalid encrypted_client_hello extension") + return nil, errInvalidECHExt } if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) { @@ -482,6 +482,7 @@ func (e *ECHRejectionError) Error() string { } 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 @@ -508,7 +509,7 @@ func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, return echType, cs, 0, nil, nil, nil } if echType != outerECHExt { - err = errMalformedECHExt + err = errInvalidECHExt return } if !s.ReadUint16(&cs.KDFID) { @@ -550,8 +551,13 @@ func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([ func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) { echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello) if err != nil { - c.sendAlert(alertDecodeError) - return nil, nil, errors.New("tls: client sent invalid encrypted_client_hello extension") + if errors.Is(err, errInvalidECHExt) { + c.sendAlert(alertIllegalParameter) + } else { + c.sendAlert(alertDecodeError) + } + + return nil, nil, errInvalidECHExt } if echType == innerECHExt { @@ -598,7 +604,7 @@ func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *e echInner, err := decodeInnerClientHello(outer, encodedInner) if err != nil { c.sendAlert(alertIllegalParameter) - return nil, nil, errors.New("tls: client sent invalid encrypted_client_hello extension") + return nil, nil, errInvalidECHExt } c.echAccepted = true From 34e6b086a0d57e614982fb3a7c5db75d421180ed Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:25:00 -0400 Subject: [PATCH 23/45] all: use slices.Contains to simplify code Change-Id: I9ef075bbb0e3c65f3c2a9d49e599ef50b18aa9be Reviewed-on: https://go-review.googlesource.com/c/go/+/639535 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Ian Lance Taylor Reviewed-by: Ian Lance Taylor --- handshake_server_tls13.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 07bf06e..8d3e3a2 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -1004,12 +1004,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 { From 9caf3f4dfc7e0089d27812493b04b1cb64c5d04b Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:28:42 -0400 Subject: [PATCH 24/45] crypto/tls: document FIPS 140-3 mode behavior Change-Id: I6a6a465612cf76d148b9758ee3fcdc8606497830 Reviewed-on: https://go-review.googlesource.com/c/go/+/648835 Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Ian Lance Taylor --- tls.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tls.go b/tls.go index 3016766..8de6f54 100644 --- a/tls.go +++ b/tls.go @@ -2,8 +2,20 @@ // 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 protocol versions, cipher suites, signature algorithms, and +// key exchange algorithms approved by NIST SP 800-52r2 are implemented. +// Others are silently ignored and not negotiated. +// +// [FIPS 140-3 mode]: https://go.dev/doc/security/fips140 package reality // BUG(agl): The crypto/tls package only implements some countermeasures From 6244b56531fb1a4cbea0623433f6f9ac5fdb6dba Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:29:33 -0400 Subject: [PATCH 25/45] crypto/tls: fix incorrect EncryptedClientHelloKey comment Updates #71706 Change-Id: Id689ec476eb3f76500dbf59d716a4376749de700 Reviewed-on: https://go-review.googlesource.com/c/go/+/650719 Reviewed-by: Daniel McCarney Auto-Submit: Roland Shoemaker Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI --- common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.go b/common.go index 1f56bbf..930c27a 100644 --- a/common.go +++ b/common.go @@ -884,7 +884,7 @@ type EncryptedClientHelloKey struct { // 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 (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002). + // 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]. From 82d61a9469330363444ae6f535fcbaa6b378f534 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:33:25 -0400 Subject: [PATCH 26/45] crypto/tls: improve ech parsing errors Make the errors we return when parsing an ECHConfig slightly more verbose. Fixes #71706 Change-Id: Id138fd9defec71ce492a490a71af4981cb9ede51 Reviewed-on: https://go-review.googlesource.com/c/go/+/650720 Auto-Submit: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Reviewed-by: Michael Knyszek --- ech.go | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/ech.go b/ech.go index 83fdc34..67a8a28 100644 --- a/ech.go +++ b/ech.go @@ -54,19 +54,30 @@ 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{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"version"} } if !s.ReadUint16(&ec.Length) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"length"} } if len(ec.raw) < int(ec.Length)+4 { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"length"} } ec.raw = ec.raw[:ec.Length+4] if ec.Version != extensionEncryptedClientHello { @@ -74,47 +85,47 @@ func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) { return true, echConfig{}, nil } if !s.ReadUint8(&ec.ConfigID) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"config_id"} } if !s.ReadUint16(&ec.KemID) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"kem_id"} } if !readUint16LengthPrefixed(&s, &ec.PublicKey) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"public_key"} } var cipherSuites cryptobyte.String if !s.ReadUint16LengthPrefixed(&cipherSuites) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"cipher_suites"} } for !cipherSuites.Empty() { var c echCipher if !cipherSuites.ReadUint16(&c.KDFID) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"cipher_suites kdf_id"} } if !cipherSuites.ReadUint16(&c.AEADID) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"cipher_suites aead_id"} } ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) } if !s.ReadUint8(&ec.MaxNameLength) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"maximum_name_length"} } var publicName cryptobyte.String if !s.ReadUint8LengthPrefixed(&publicName) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"public_name"} } ec.PublicName = publicName var extensions cryptobyte.String if !s.ReadUint16LengthPrefixed(&extensions) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"extensions"} } for !extensions.Empty() { var e echExtension if !extensions.ReadUint16(&e.Type) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"extensions type"} } if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { - return false, echConfig{}, errMalformedECHConfig + return false, echConfig{}, &echConfigErr{"extensions data"} } ec.Extensions = append(ec.Extensions, e) } @@ -129,10 +140,10 @@ func parseECHConfigList(data []byte) ([]echConfig, error) { s := cryptobyte.String(data) 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 { From f1ede5e6ea4eea0269fc246545afdfbf7d1b5539 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:37:22 -0400 Subject: [PATCH 27/45] crypto/tls: require EMS in FIPS 140-3 mode See Implementation Guidance D.Q. Change-Id: I6a6a465607da94f2bb249934f0561ae04a55e7b7 Reviewed-on: https://go-review.googlesource.com/c/go/+/650575 Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker --- handshake_client.go | 9 +++++++++ handshake_server.go | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/handshake_client.go b/handshake_client.go index 73ef3b8..e1cf0bc 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -464,6 +464,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 } @@ -774,6 +779,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) } diff --git a/handshake_server.go b/handshake_server.go index 33c528f..333b179 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. @@ -512,6 +514,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 @@ -698,6 +704,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) } From 7433b09af080a4448b22022c7708f357e1836b94 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:39:46 -0400 Subject: [PATCH 28/45] crypto/tls: reject empty TLS 1.3 session ticket While not clearly motivated by normative language in RFC 8446 it seems clear that an empty opaque ticket value is non-operable, and so we should reject it with an appropriate alert/error. This allows removing the SendEmptySessionTicket-TLS13 BoGo test from the bogo excluded tests configuration. Fixes #70513 Updates #72006 Change-Id: I589b34e86fb1eb27a349a230e920c22284597cde Reviewed-on: https://go-review.googlesource.com/c/go/+/650735 Reviewed-by: Filippo Valsorda Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Auto-Submit: Daniel McCarney --- handshake_client_tls13.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 2623a2e..295e527 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -870,6 +870,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) From 399f0e14086216fbaa87e1aa33e306eb6c56204d Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:41:07 -0400 Subject: [PATCH 29/45] crypto/tls: ignore TLS 1.3 user canceled alerts When encountering alertUserCanceled in a TLS 1.3 handshake, ignore the alert and retry reading a record. This matches existing logic for how TLS 1.2 alertLevelWarning alerts are handled. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For broader context, 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). With the crypto/tls behaviour changed to match peer implementations we can now enable the "SendUserCanceledAlerts-TLS13" BoGo test. "SendUserCanceledAlerts-TooMany-TLS13" remains ignored, because like "SendWarningAlerts*" fixing the test requires some general spam protocol message enhancements be done first. Updates #72006 Change-Id: I570c1fa674b5a4760836c514d35ee17f746fe28d Reviewed-on: https://go-review.googlesource.com/c/go/+/650716 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- conn.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/conn.go b/conn.go index 3db488d..6f792cd 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] { From b0d08910926599eb34c4b54ab71bdee414017605 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:42:49 -0400 Subject: [PATCH 30/45] crypto/tls: align cert decode alert w/ BSSL For malformed client/server certificates in a TLS handshake send a decode_error alert, matching BoringSSL behaviour. Previously crypto/tls used a bad_certificate alert for this purpose. The TLS specification is imprecise enough to allow this to be considered a spec. justified choice, but since all other places in the protocol encourage using decode_error for structurally malformed messages we may as well do the same here and get some extra cross-impl consistency for free. This also allows un-ignoring the BoGo GarbageCertificate-[Client|Server]-[TLS12|TLS13] tests. Updates #72006 Change-Id: Ide45ba1602816e71c3289a60e77587266c3b9036 Reviewed-on: https://go-review.googlesource.com/c/go/+/652995 LUCI-TryBot-Result: Go LUCI Reviewed-by: Junyang Shao Reviewed-by: Roland Shoemaker --- handshake_client.go | 2 +- handshake_server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/handshake_client.go b/handshake_client.go index e1cf0bc..1be44d8 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -1089,7 +1089,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 { diff --git a/handshake_server.go b/handshake_server.go index 333b179..1216368 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -896,7 +896,7 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { 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 { From dce8d41932c0eb020b7b674693579d4edd3052f8 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:44:05 -0400 Subject: [PATCH 31/45] crypto/tls: reject TLS 1.3 compat session ID in TLS 1.2 If we weren't resuming an existing session, and we constructed a TLS 1.3 compatible client hello, ensure the server doesn't echo back the made up compatibility session ID if we end up handshaking for TLS 1.2. As part of an effort to make the initial stages of a TLS 1.3 handshake compatible with TLS 1.2 middleboxes, TLS 1.3 requires that the client hello contain a non-empty legacy_session_id value. For anti-ossification purposes it's recommended this ID be randomly generated. This is the strategy the crypto/tls package takes. When we follow this approach, but then end up negotiating TLS 1.2, the server should not have echoed back that random ID to us. It's impossible for the server to have had a session with a matching ID and so it is misbehaving and it's prudent for our side to abort the handshake. See RFC 8446 Section 4.1.2 for more detail: https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2 Adopting this behaviour allows un-ignoring the BoGo EchoTLS13CompatibilitySessionID testcase. Updates #72006 Change-Id: I1e52075177a13a7aa103b45498eae38d8a4c34b9 Reviewed-on: https://go-review.googlesource.com/c/go/+/652997 LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Reviewed-by: Junyang Shao Reviewed-by: Filippo Valsorda --- handshake_client.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/handshake_client.go b/handshake_client.go index 1be44d8..9670b8f 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -559,6 +559,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 From cffe49f9812c34c02a0d45e3bc8226021a2d7d13 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 15:48:12 -0400 Subject: [PATCH 32/45] crypto/tls: allow P-521 in FIPS 140-3 mode and Go+BoringCrypto Partially reverts CL 587296, restoring the Go+BoringCrypto 1.23 behavior in terms of supported curves. Updates #71757 Change-Id: I6a6a465651a8407056fd0fae091d10a945b37997 Reviewed-on: https://go-review.googlesource.com/c/go/+/657095 LUCI-TryBot-Result: Go LUCI Reviewed-by: Daniel McCarney Reviewed-by: David Chase Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda --- defaults.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/defaults.go b/defaults.go index 01fb9cc..f63d947 100644 --- a/defaults.go +++ b/defaults.go @@ -91,7 +91,8 @@ var defaultCipherSuitesTLS13NoAES = []uint16{ } // The FIPS-only policies below match BoringSSL's -// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2. +// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2, with +// minor changes per https://go.dev/issue/71757. // https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa var defaultSupportedVersionsFIPS = []uint16{ @@ -101,7 +102,7 @@ var defaultSupportedVersionsFIPS = []uint16{ // defaultCurvePreferencesFIPS are the FIPS-allowed curves, // in preference order (most preferable first). -var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384} +var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384, CurveP521} // defaultSupportedSignatureAlgorithmsFIPS currently are a subset of // defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1. @@ -114,6 +115,7 @@ var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{ PKCS1WithSHA384, ECDSAWithP384AndSHA384, PKCS1WithSHA512, + ECDSAWithP521AndSHA512, } // defaultCipherSuitesFIPS are the FIPS-allowed cipher suites. From 12fa20f9e0eadb266e05461d2b6d174d915f1003 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 16:53:52 -0400 Subject: [PATCH 33/45] =?UTF-8?q?crypto/tls:=20add=20ConnectionState.Curve?= =?UTF-8?q?ID=20This=20required=20adding=20a=20new=20field=20to=20SessionS?= =?UTF-8?q?tate=20for=20TLS=201.0=E2=80=931.2,=20since=20the=20key=20excha?= =?UTF-8?q?nge=20is=20not=20repeated=20on=20resumption.=20The=20additional?= =?UTF-8?q?=20field=20is=20unfortunately=20not=20backwards=20compatible=20?= =?UTF-8?q?because=20current=20Go=20versions=20check=20that=20the=20encodi?= =?UTF-8?q?ng=20has=20no=20extra=20data=20at=20the=20end,=20but=20will=20c?= =?UTF-8?q?ause=20cross-version=20tickets=20to=20be=20ignored.=20Relaxed?= =?UTF-8?q?=20that=20so=20we=20can=20add=20fields=20in=20a=20backwards=20c?= =?UTF-8?q?ompatible=20way=20the=20next=20time.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the cipher suite, we check that the session's is still acceptable per the Config. That would arguably make sense here, too: if a Config for example requires PQ, we should reject resumptions of connections that didn't use PQ. However, that only applies to pre-TLS 1.3 connections, since in TLS 1.3 we always do a fresh key exchange on resumption. Since PQ is the only main differentiator between key exchanges (aside from off-by-default non-PFS RSA, which are controlled by the cipher suite in TLS 1.0–1.2) and it's PQ-only, we can skip that check. Fixes #67516 Change-Id: I6a6a465681a6292edf66c7b8df8f4aba4171a76b Reviewed-on: https://go-review.googlesource.com/c/go/+/653315 Reviewed-by: David Chase Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- common.go | 9 ++++--- conn.go | 3 +-- handshake_client.go | 4 ++- handshake_server.go | 1 + ticket.go | 65 ++++++++++++++++++++++++++------------------- 5 files changed, 47 insertions(+), 35 deletions(-) diff --git a/common.go b/common.go index 930c27a..1ae7a37 100644 --- a/common.go +++ b/common.go @@ -247,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 @@ -304,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 diff --git a/conn.go b/conn.go index 6f792cd..14522a0 100644 --- a/conn.go +++ b/conn.go @@ -1694,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/handshake_client.go b/handshake_client.go index 9670b8f..522da0f 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -271,6 +271,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 { @@ -951,10 +952,11 @@ 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 } diff --git a/handshake_server.go b/handshake_server.go index 1216368..2c20a63 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -526,6 +526,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 } 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, } } From 45b1647aebacaeec2d98ce58ec39931e47c492e3 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 17:13:05 -0400 Subject: [PATCH 34/45] crypto/tls: clean up supported/default/allowed parameters Cleaned up a lot of the plumbing to make it consistently follow this logic: clone the preference order; filter by user preference; filter by FIPS policy. There should be no behavior changes. Updates #71757 Change-Id: I6a6a4656eb02e56d079f0a22f98212275a400000 Reviewed-on: https://go-review.googlesource.com/c/go/+/657096 LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney Reviewed-by: David Chase Reviewed-by: Junyang Shao --- auth.go | 27 ++++++------ cipher_suites.go | 14 ++++--- common.go | 88 ++++++++++++++++----------------------- defaults.go | 56 +++++-------------------- defaults_boring.go | 67 +++++++++++++++++++++++++++++ handshake_client.go | 26 ++++-------- handshake_server.go | 18 +------- handshake_server_tls13.go | 4 +- tls.go | 2 +- 9 files changed, 146 insertions(+), 156 deletions(-) create mode 100644 defaults_boring.go diff --git a/auth.go b/auth.go index c389cd4..e8630f0 100644 --- a/auth.go +++ b/auth.go @@ -15,8 +15,7 @@ import ( "fmt" "hash" "io" - - "github.com/xtls/reality/fips140tls" + "slices" ) // verifyHandshakeSignature verifies a signature against pre-hashed @@ -169,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 { @@ -217,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 } @@ -244,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 fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { - continue - } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { return preferredAlg, nil } diff --git a/cipher_suites.go b/cipher_suites.go index 9016ec2..57ce64c 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -78,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}, @@ -387,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] diff --git a/common.go b/common.go index 1ae7a37..6e224b5 100644 --- a/common.go +++ b/common.go @@ -1132,20 +1132,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 fips140tls.Required() { - return defaultCipherSuitesFIPS - } - return defaultCipherSuites() - } - if fips140tls.Required() { - 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{ @@ -1163,7 +1171,7 @@ const roleServer = false func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { - if fips140tls.Required() && !slices.Contains(defaultSupportedVersionsFIPS, v) { + if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { @@ -1206,11 +1214,11 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { } func (c *Config) curvePreferences(version uint16) []CurveID { - var curvePreferences []CurveID + curvePreferences := defaultCurvePreferences() if fips140tls.Required() { - curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) - } else { - curvePreferences = defaultCurvePreferences() + curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { + return !slices.Contains(allowedCurvePreferencesFIPS, x) + }) } if c != nil && len(c.CurvePreferences) != 0 { curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { @@ -1224,23 +1232,16 @@ func (c *Config) curvePreferences(version uint16) []CurveID { } 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 @@ -1361,7 +1362,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 } @@ -1438,7 +1439,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 } @@ -1682,19 +1683,14 @@ func unexpectedMessageError(wanted, got any) error { // supportedSignatureAlgorithms returns the supported signature algorithms. func supportedSignatureAlgorithms() []SignatureScheme { - if !fips140tls.Required() { - 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. @@ -1743,24 +1739,10 @@ func fipsAllowChain(chain []*x509.Certificate) bool { } for _, cert := range chain { - if !fipsAllowCert(cert) { + if !isCertificateAllowedFIPS(cert) { return false } } return true -} - -func fipsAllowCert(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.go b/defaults.go index f63d947..e65af2a 100644 --- a/defaults.go +++ b/defaults.go @@ -45,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] @@ -88,46 +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_compliance_policy_fips_202205, which is based on NIST SP 800-52r2, with -// minor changes per https://go.dev/issue/71757. -// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa - -var defaultSupportedVersionsFIPS = []uint16{ - VersionTLS12, - VersionTLS13, -} - -// defaultCurvePreferencesFIPS are the FIPS-allowed curves, -// in preference order (most preferable first). -var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384, CurveP521} - -// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of -// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1. -var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{ - PSSWithSHA256, - PSSWithSHA384, - PSSWithSHA512, - PKCS1WithSHA256, - ECDSAWithP256AndSHA256, - PKCS1WithSHA384, - ECDSAWithP384AndSHA384, - PKCS1WithSHA512, - ECDSAWithP521AndSHA512, -} - -// 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..bb0f71b --- /dev/null +++ b/defaults_boring.go @@ -0,0 +1,67 @@ +// 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. + +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/handshake_client.go b/handshake_client.go index 522da0f..b3c7cd2 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -94,24 +94,12 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli 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) @@ -145,7 +133,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli hello.cipherSuites = nil } if fips140tls.Required() { - hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) + hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...) } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) } else { diff --git a/handshake_server.go b/handshake_server.go index 2c20a63..fd26c4f 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -369,21 +369,7 @@ func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, support 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 { @@ -484,7 +470,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 } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 8d3e3a2..3d51b52 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -229,11 +229,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 fips140tls.Required() { - preferenceList = defaultCipherSuitesTLS13FIPS + preferenceList = allowedCipherSuitesTLS13FIPS } for _, suiteID := range preferenceList { hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID) diff --git a/tls.go b/tls.go index 8de6f54..c1e6ebd 100644 --- a/tls.go +++ b/tls.go @@ -188,7 +188,7 @@ 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, _ = gcm.NewGCMForTLS13(block.(*fipsaes.Block)) } else { From a2893060d9e865312970edf3486c6a5314231df6 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:09:16 -0400 Subject: [PATCH 35/45] crypto/tls: relax native FIPS 140-3 mode We are going to stick to BoringSSL's policy for Go+BoringCrypto, but when using the native FIPS 140-3 module we can allow Ed25519, ML-KEM, and P-521. NIST SP 800-52r2 is stricter, but it only applies to some entities, so they can restrict the profile with Config. Fixes #71757 Change-Id: I6a6a4656eb02e56d079f0a22f98212275a40a679 Reviewed-on: https://go-review.googlesource.com/c/go/+/650576 LUCI-TryBot-Result: Go LUCI Reviewed-by: Junyang Shao Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney Reviewed-by: David Chase --- defaults_boring.go | 2 ++ defaults_fips140.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ tls.go | 11 ++++--- 3 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 defaults_fips140.go diff --git a/defaults_boring.go b/defaults_boring.go index bb0f71b..c953408 100644 --- a/defaults_boring.go +++ b/defaults_boring.go @@ -2,6 +2,8 @@ // 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 ( 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/tls.go b/tls.go index c1e6ebd..2b47035 100644 --- a/tls.go +++ b/tls.go @@ -10,10 +10,13 @@ // // # FIPS 140-3 mode // -// When the program is in [FIPS 140-3 mode], this package behaves as if -// only protocol versions, cipher suites, signature algorithms, and -// key exchange algorithms approved by NIST SP 800-52r2 are implemented. -// Others are silently ignored and not negotiated. +// 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 From fd9059aef165afe9bc115ee5f331a1dcf37050ee Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:11:32 -0400 Subject: [PATCH 36/45] crypto/tls: fix ECH compatibility Previously, the code only checked supportedVersions[0] for TLS 1.3 However, Chromium-based browsers may list TLS 1.3 at different positions, causing ECH failures. This fix: Iterates through supportedVersions to accept connections as long as TLS 1.3 is present. Improves ECH compatibility, ensuring Chrome, Edge, and other browsers work properly. Fixes #71642 Change-Id: I32f4219fb6654d5cc22c7f33497c6142c0acb4f2 Reviewed-on: https://go-review.googlesource.com/c/go/+/648015 LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Reviewed-by: David Chase Reviewed-by: Daniel McCarney --- ech.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/ech.go b/ech.go index 67a8a28..e2bf0a7 100644 --- a/ech.go +++ b/ech.go @@ -393,8 +393,28 @@ func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHello return nil, errInvalidECHExt } - if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) { - return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions") + 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 From 989cf77b4e70bdb79b28271dc7f9394e59a48ddf Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:12:35 -0400 Subject: [PATCH 37/45] crypto/tls: add missing RUnlock in ticketKeys If GetConfigForClient returns a tls.Config that has SessionTicketsDisabled set, the TLS server handshake currently leaves the Config's internal RWMutex read locked after calculating the ticketKeys to use for the handshake. Change-Id: I07e450a0d2edda9e80f51fc2c20af633aa271684 GitHub-Last-Rev: 693d7ac GitHub-Pull-Request: #68607 Reviewed-on: https://go-review.googlesource.com/c/go/+/601335 LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao Reviewed-by: Cherry Mui Auto-Submit: Sean Liao Reviewed-by: Roland Shoemaker --- common.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common.go b/common.go index 6e224b5..f8243f1 100644 --- a/common.go +++ b/common.go @@ -1039,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() From 5052e9a93c7487c86796509c3e1818bb78613f82 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:30:06 -0400 Subject: [PATCH 38/45] crypto/tls: use crypto/hkdf For consistency, prefer crypto/hkdf over crypto/internal/fips140/hkdf. Both should have the same behavior given the constrained use of HKDF in TLS. Change-Id: Ia982b9f7a6ea66537d748eb5ecae1ac1eade68a5 Reviewed-on: https://go-review.googlesource.com/c/go/+/658217 LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee Reviewed-by: Roland Shoemaker --- go.mod | 4 ++-- go.sum | 4 ++++ handshake_client_tls13.go | 28 +++++++++++++++------------- handshake_server_tls13.go | 28 ++++++++++++++-------------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index ca25db1..b19ccbf 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.24 require ( github.com/pires/go-proxyproto v0.8.1 - golang.org/x/crypto v0.37.0 - golang.org/x/sys v0.32.0 + golang.org/x/crypto v0.38.0 + golang.org/x/sys v0.33.0 ) diff --git a/go.sum b/go.sum index be439aa..6f4f082 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,11 @@ 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_tls13.go b/handshake_client_tls13.go index 295e527..f2f88b5 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "crypto" + "crypto/hkdf" "crypto/hmac" "crypto/mlkem" "crypto/rsa" @@ -18,7 +19,6 @@ import ( "time" "github.com/xtls/reality/tls13" - "golang.org/x/crypto/hkdf" ) type clientHandshakeStateTLS13 struct { @@ -90,12 +90,13 @@ func (hs *clientHandshakeStateTLS13) handshake() error { confTranscript.Write(hs.serverHello.original[:30]) confTranscript.Write(make([]byte, 8)) confTranscript.Write(hs.serverHello.original[38:]) - acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, - hkdf.Extract(hs.suite.hash.New, 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 @@ -264,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 := tls13.ExpandLabel(hs.suite.hash.New, - hkdf.Extract(hs.suite.hash.New, 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 diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 3d51b52..119c699 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -625,13 +625,13 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) if err := transcriptMsg(helloRetryRequest, confTranscript); err != nil { return nil, err } - b, _ := hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil) - acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, - b, - "hrr ech accept confirmation", - confTranscript.Sum(nil), - 8, - ) + 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 } @@ -789,13 +789,13 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { return err } // compute the acceptance message - b, _ := hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil) - acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, - b, - "ech accept confirmation", - echTranscript.Sum(nil), - 8, - ) + 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) } From 5b2edd4705ee28c32cba5fb7f488f1b22bdbb48f Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:33:34 -0400 Subject: [PATCH 39/45] crypto/tls: add offered cipher suites to the handshake error This change makes debugging easier if the server handshake fails because the client only offers unsupported algorithms. Change-Id: I7daac173a16af2e073aec3d9b59709560f540c6f Reviewed-on: https://go-review.googlesource.com/c/go/+/631555 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Roland Shoemaker Auto-Submit: Nicola Murino --- handshake_server.go | 3 ++- handshake_server_tls13.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/handshake_server.go b/handshake_server.go index fd26c4f..03d0553 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -374,7 +374,8 @@ func (hs *serverHandshakeState) pickCipherSuite() error { 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 diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 119c699..2242e5c 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -18,6 +18,7 @@ import ( "crypto/x509" "encoding/binary" "errors" + "fmt" "hash" "io" "math/big" @@ -243,7 +244,8 @@ 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 From 69ea598173ec6430efa9172237c968bf59468703 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:37:34 -0400 Subject: [PATCH 40/45] crypto/tls: use runtime.AddCleanup instead of runtime.SetFinalizer Replace the usage of runtime.SetFinalizer with runtime.AddCleanup in the certificate cache. Updates #70907 Change-Id: Ieab6ff88dbc4083f11c1b475f11bd61521dbc638 Reviewed-on: https://go-review.googlesource.com/c/go/+/664275 Auto-Submit: Carlos Amedee LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- cache.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 } From ce70d0748ea8c08b51aa1240978f2844af107c0e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:39:22 -0400 Subject: [PATCH 41/45] crypto/tls: use illegal param alert for bad compression Previously if the clientHandshakeState for the TLS 1.2 client code encountered a server helo message that contained a compression method other than compressionNone, we would emit an unexpected message alert. Instead, it seems more appropriate to return an illegal parameter alert. The server hello message _was_ expected, it just contained a bad parameter option. Making this change also allows enabling the InvalidCompressionMethod bogo test. Updates #72006 Change-Id: I27a2cd231e4b8762b0d9e2dbd3d8ddd5b87fd5c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/669155 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- handshake_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handshake_client.go b/handshake_client.go index b3c7cd2..4ad6aa2 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -882,7 +882,7 @@ 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") } From eef41364b342b01b53f4f3c728ba1dc5fed22bee Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:41:59 -0400 Subject: [PATCH 42/45] crypto/tls: update TLS 1.3 client compression validation Unlike in earlier TLS versions, in TLS 1.3 when processing a server hello the legacy_compression_method MUST have the value 0. It is no longer a parameter that offers a choice of compression method. With this in mind, it seems more appropriate to return a decode error when we encounter a non-zero compression method in a server hello message. We haven't found a parameter value we reject, we've found a message that doesn't decode according to its specification. Making this change also aligns with BoringSSL and allows enabling the TLS13-HRR-InvalidCompressionMethod bogo test. Updates #72006 Change-Id: I27a2cd231e4b8762b0d9e2dbd3d8ddd5b87fd5c4 Reviewed-on: https://go-review.googlesource.com/c/go/+/669156 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- handshake_client_tls13.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index f2f88b5..df14c3e 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -197,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) From b5e214e5fed4e401ba77135998c24b62ac8ad382 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:46:21 -0400 Subject: [PATCH 43/45] crypto/tls: err for unsupported point format configs If a client or server explicitly offers point formats, and the point formats don't include the uncompressed format, then error. This matches BoringSSL and Rustls behaviour and allows enabling the PointFormat-Client-MissingUncompressed bogo test. Updates #72006 Change-Id: I27a2cd231e4b8762b0d9e2dbd3d8ddd5b87fd5c5 Reviewed-on: https://go-review.googlesource.com/c/go/+/669157 Reviewed-by: Roland Shoemaker Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- common.go | 6 +++++- handshake_client.go | 13 +++++++++++++ handshake_server.go | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/common.go b/common.go index f8243f1..ed987ac 100644 --- a/common.go +++ b/common.go @@ -1394,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")) } diff --git a/handshake_client.go b/handshake_client.go index 4ad6aa2..de5c420 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -886,6 +886,19 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { 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 { diff --git a/handshake_server.go b/handshake_server.go index 03d0553..3d5ea81 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -268,7 +268,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 @@ -339,7 +343,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) { @@ -349,10 +353,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 @@ -361,9 +367,11 @@ 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 { From db7cbf40cc9c9a088e07e521553c35ad12a3836e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:49:02 -0400 Subject: [PATCH 44/45] crypto/tls: fix TLS <1.3 client cert required alert Previously for protocol versions older than TLS 1.3 our server handshake implementation sent an alertBadCertificate alert in the case where the server TLS config indicates a client cert is required and none was received. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit updates the relevant logic to instead send alertHandshakeFailure in these circumstances. For TLS 1.2, RFC 5246 §7.4.6 unambiguously describes this as the correct alert: If the client does not send any certificates, the server MAY at its discretion either continue the handshake without client authentication, or respond with a fatal handshake_failure alert. The TLS 1.1 and 1.0 specs also describe using this alert (RFC 4346 §7.4.6 and RFC 2246 §7.4.6) both say: If client authentication is required by the server for the handshake to continue, it may respond with a fatal handshake failure alert. Making this correction also allows enabling the RequireAnyClientCertificate-TLS1* bogo tests. Updates #72006 Change-Id: I27a2cd231e4b8762b0d9e2dbd3d8ddd5b87fd5c8 Reviewed-on: https://go-review.googlesource.com/c/go/+/671195 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Roland Shoemaker --- handshake_server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handshake_server.go b/handshake_server.go index 3d5ea81..7aec036 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -885,7 +885,7 @@ 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)) @@ -908,7 +908,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") } From 20f151b00a8432e158b66e3937cf1a9dbebe9028 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sat, 10 May 2025 23:50:17 -0400 Subject: [PATCH 45/45] crypto/tls: handle client hello version too high If the client hello legacy version is >= TLS 1.3, and no supported_versions extension is sent, negotiate TLS 1.2 or lower when supported. On the topic of supported version negotiation RFC 8446 4.2.1 indicates TLS 1.3 implementations MUST send a supported_versions extension with a list of their supported protocol versions. The crypto/tls package enforces this when the client hello legacy version indicates TLS 1.3 (0x0304), aborting the handshake with an alertMissingExtension alert if no supported_versions were received. However, section 4.2.1 indicates different behaviour should be used when the extension is not present and TLS 1.2 or prior are supported: If this extension is not present, servers which are compliant with this specification and which also support TLS 1.2 MUST negotiate TLS 1.2 or prior as specified in [RFC5246], even if ClientHello.legacy_version is 0x0304 or later. This commit updates the client hello processing logic to allow this behaviour. If no supported_versions extension was received we ignore the legacy version being >= TLS 1.3 and instead negotiate a lower supported version if the server configuration allows. This fix in turn allows enabling the BoGo ClientHelloVersionTooHigh, MinorVersionTolerance, and MajorVersionTolerance tests. Updates #72006 Change-Id: I27a2cd231e4b8762b0d9e2dbd3d8ddd5b87fd5c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/671235 Reviewed-by: Cherry Mui Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- handshake_server.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/handshake_server.go b/handshake_server.go index 7aec036..7f98cb8 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -170,7 +170,15 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer 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)