mirror of
https://github.com/XTLS/REALITY.git
synced 2025-08-22 14:38:35 +00:00
commit
ce2747b9b0
131
aes/aes.go
Normal file
131
aes/aes.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package aes
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/reality/alias"
|
||||
)
|
||||
|
||||
// BlockSize is the AES block size in bytes.
|
||||
const BlockSize = 16
|
||||
|
||||
// A Block is an instance of AES using a particular key.
|
||||
// It is safe for concurrent use.
|
||||
type Block struct {
|
||||
block
|
||||
}
|
||||
|
||||
// blockExpanded is the block type used for all architectures except s390x,
|
||||
// which feeds the raw key directly to its instructions.
|
||||
type blockExpanded struct {
|
||||
rounds int
|
||||
// Round keys, where only the first (rounds + 1) × (128 ÷ 32) words are used.
|
||||
enc [60]uint32
|
||||
dec [60]uint32
|
||||
}
|
||||
|
||||
const (
|
||||
// AES-128 has 128-bit keys, 10 rounds, and uses 11 128-bit round keys
|
||||
// (11×128÷32 = 44 32-bit words).
|
||||
|
||||
// AES-192 has 192-bit keys, 12 rounds, and uses 13 128-bit round keys
|
||||
// (13×128÷32 = 52 32-bit words).
|
||||
|
||||
// AES-256 has 256-bit keys, 14 rounds, and uses 15 128-bit round keys
|
||||
// (15×128÷32 = 60 32-bit words).
|
||||
|
||||
aes128KeySize = 16
|
||||
aes192KeySize = 24
|
||||
aes256KeySize = 32
|
||||
|
||||
aes128Rounds = 10
|
||||
aes192Rounds = 12
|
||||
aes256Rounds = 14
|
||||
)
|
||||
|
||||
// roundKeysSize returns the number of uint32 of c.end or c.dec that are used.
|
||||
func (b *blockExpanded) roundKeysSize() int {
|
||||
return (b.rounds + 1) * (128 / 32)
|
||||
}
|
||||
|
||||
type KeySizeError int
|
||||
|
||||
func (k KeySizeError) Error() string {
|
||||
return "crypto/aes: invalid key size " + strconv.Itoa(int(k))
|
||||
}
|
||||
|
||||
// New creates and returns a new [cipher.Block] implementation.
|
||||
// The key argument should be the AES key, either 16, 24, or 32 bytes to select
|
||||
// AES-128, AES-192, or AES-256.
|
||||
func New(key []byte) (*Block, error) {
|
||||
// This call is outline to let the allocation happen on the parent stack.
|
||||
return newOutlined(&Block{}, key)
|
||||
}
|
||||
|
||||
// newOutlined is marked go:noinline to avoid it inlining into New, and making New
|
||||
// too complex to inline itself.
|
||||
//
|
||||
//go:noinline
|
||||
func newOutlined(b *Block, key []byte) (*Block, error) {
|
||||
switch len(key) {
|
||||
case aes128KeySize, aes192KeySize, aes256KeySize:
|
||||
default:
|
||||
return nil, KeySizeError(len(key))
|
||||
}
|
||||
return newBlock(b, key), nil
|
||||
}
|
||||
|
||||
func newBlockExpanded(c *blockExpanded, key []byte) {
|
||||
switch len(key) {
|
||||
case aes128KeySize:
|
||||
c.rounds = aes128Rounds
|
||||
case aes192KeySize:
|
||||
c.rounds = aes192Rounds
|
||||
case aes256KeySize:
|
||||
c.rounds = aes256Rounds
|
||||
}
|
||||
expandKeyGeneric(c, key)
|
||||
}
|
||||
|
||||
func (c *Block) BlockSize() int { return BlockSize }
|
||||
|
||||
func (c *Block) Encrypt(dst, src []byte) {
|
||||
// AES-ECB is not approved in FIPS 140-3 mode.
|
||||
// fips140.RecordNonApproved()
|
||||
if len(src) < BlockSize {
|
||||
panic("crypto/aes: input not full block")
|
||||
}
|
||||
if len(dst) < BlockSize {
|
||||
panic("crypto/aes: output not full block")
|
||||
}
|
||||
if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) {
|
||||
panic("crypto/aes: invalid buffer overlap")
|
||||
}
|
||||
encryptBlock(c, dst, src)
|
||||
}
|
||||
|
||||
func (c *Block) Decrypt(dst, src []byte) {
|
||||
// AES-ECB is not approved in FIPS 140-3 mode.
|
||||
// fips140.RecordNonApproved()
|
||||
if len(src) < BlockSize {
|
||||
panic("crypto/aes: input not full block")
|
||||
}
|
||||
if len(dst) < BlockSize {
|
||||
panic("crypto/aes: output not full block")
|
||||
}
|
||||
if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) {
|
||||
panic("crypto/aes: invalid buffer overlap")
|
||||
}
|
||||
decryptBlock(c, dst, src)
|
||||
}
|
||||
|
||||
// EncryptBlockInternal applies the AES encryption function to one block.
|
||||
//
|
||||
// It is an internal function meant only for the gcm package.
|
||||
func EncryptBlockInternal(c *Block, dst, src []byte) {
|
||||
encryptBlock(c, dst, src)
|
||||
}
|
181
aes/aes_generic.go
Normal file
181
aes/aes_generic.go
Normal file
@ -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 <vincent.rijmen@esat.kuleuven.ac.be>
|
||||
// @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be>
|
||||
// @author Paulo Barreto <paulo.barreto@terra.com.br>
|
||||
//
|
||||
// This code is hereby placed in the public domain.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
|
||||
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// See FIPS 197 for specification, and see Daemen and Rijmen's Rijndael submission
|
||||
// for implementation details.
|
||||
// https://csrc.nist.gov/csrc/media/publications/fips/197/final/documents/fips-197.pdf
|
||||
// https://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf
|
||||
|
||||
package aes
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// Encrypt one block from src into dst, using the expanded key xk.
|
||||
func encryptBlockGeneric(c *blockExpanded, dst, src []byte) {
|
||||
checkGenericIsExpected()
|
||||
xk := c.enc[:]
|
||||
|
||||
_ = src[15] // early bounds check
|
||||
s0 := binary.BigEndian.Uint32(src[0:4])
|
||||
s1 := binary.BigEndian.Uint32(src[4:8])
|
||||
s2 := binary.BigEndian.Uint32(src[8:12])
|
||||
s3 := binary.BigEndian.Uint32(src[12:16])
|
||||
|
||||
// First round just XORs input with key.
|
||||
s0 ^= xk[0]
|
||||
s1 ^= xk[1]
|
||||
s2 ^= xk[2]
|
||||
s3 ^= xk[3]
|
||||
|
||||
// Middle rounds shuffle using tables.
|
||||
k := 4
|
||||
var t0, t1, t2, t3 uint32
|
||||
for r := 0; r < c.rounds-1; r++ {
|
||||
t0 = xk[k+0] ^ te0[uint8(s0>>24)] ^ te1[uint8(s1>>16)] ^ te2[uint8(s2>>8)] ^ te3[uint8(s3)]
|
||||
t1 = xk[k+1] ^ te0[uint8(s1>>24)] ^ te1[uint8(s2>>16)] ^ te2[uint8(s3>>8)] ^ te3[uint8(s0)]
|
||||
t2 = xk[k+2] ^ te0[uint8(s2>>24)] ^ te1[uint8(s3>>16)] ^ te2[uint8(s0>>8)] ^ te3[uint8(s1)]
|
||||
t3 = xk[k+3] ^ te0[uint8(s3>>24)] ^ te1[uint8(s0>>16)] ^ te2[uint8(s1>>8)] ^ te3[uint8(s2)]
|
||||
k += 4
|
||||
s0, s1, s2, s3 = t0, t1, t2, t3
|
||||
}
|
||||
|
||||
// Last round uses s-box directly and XORs to produce output.
|
||||
s0 = uint32(sbox0[t0>>24])<<24 | uint32(sbox0[t1>>16&0xff])<<16 | uint32(sbox0[t2>>8&0xff])<<8 | uint32(sbox0[t3&0xff])
|
||||
s1 = uint32(sbox0[t1>>24])<<24 | uint32(sbox0[t2>>16&0xff])<<16 | uint32(sbox0[t3>>8&0xff])<<8 | uint32(sbox0[t0&0xff])
|
||||
s2 = uint32(sbox0[t2>>24])<<24 | uint32(sbox0[t3>>16&0xff])<<16 | uint32(sbox0[t0>>8&0xff])<<8 | uint32(sbox0[t1&0xff])
|
||||
s3 = uint32(sbox0[t3>>24])<<24 | uint32(sbox0[t0>>16&0xff])<<16 | uint32(sbox0[t1>>8&0xff])<<8 | uint32(sbox0[t2&0xff])
|
||||
|
||||
s0 ^= xk[k+0]
|
||||
s1 ^= xk[k+1]
|
||||
s2 ^= xk[k+2]
|
||||
s3 ^= xk[k+3]
|
||||
|
||||
_ = dst[15] // early bounds check
|
||||
binary.BigEndian.PutUint32(dst[0:4], s0)
|
||||
binary.BigEndian.PutUint32(dst[4:8], s1)
|
||||
binary.BigEndian.PutUint32(dst[8:12], s2)
|
||||
binary.BigEndian.PutUint32(dst[12:16], s3)
|
||||
}
|
||||
|
||||
// Decrypt one block from src into dst, using the expanded key xk.
|
||||
func decryptBlockGeneric(c *blockExpanded, dst, src []byte) {
|
||||
checkGenericIsExpected()
|
||||
xk := c.dec[:]
|
||||
|
||||
_ = src[15] // early bounds check
|
||||
s0 := binary.BigEndian.Uint32(src[0:4])
|
||||
s1 := binary.BigEndian.Uint32(src[4:8])
|
||||
s2 := binary.BigEndian.Uint32(src[8:12])
|
||||
s3 := binary.BigEndian.Uint32(src[12:16])
|
||||
|
||||
// First round just XORs input with key.
|
||||
s0 ^= xk[0]
|
||||
s1 ^= xk[1]
|
||||
s2 ^= xk[2]
|
||||
s3 ^= xk[3]
|
||||
|
||||
// Middle rounds shuffle using tables.
|
||||
k := 4
|
||||
var t0, t1, t2, t3 uint32
|
||||
for r := 0; r < c.rounds-1; r++ {
|
||||
t0 = xk[k+0] ^ td0[uint8(s0>>24)] ^ td1[uint8(s3>>16)] ^ td2[uint8(s2>>8)] ^ td3[uint8(s1)]
|
||||
t1 = xk[k+1] ^ td0[uint8(s1>>24)] ^ td1[uint8(s0>>16)] ^ td2[uint8(s3>>8)] ^ td3[uint8(s2)]
|
||||
t2 = xk[k+2] ^ td0[uint8(s2>>24)] ^ td1[uint8(s1>>16)] ^ td2[uint8(s0>>8)] ^ td3[uint8(s3)]
|
||||
t3 = xk[k+3] ^ td0[uint8(s3>>24)] ^ td1[uint8(s2>>16)] ^ td2[uint8(s1>>8)] ^ td3[uint8(s0)]
|
||||
k += 4
|
||||
s0, s1, s2, s3 = t0, t1, t2, t3
|
||||
}
|
||||
|
||||
// Last round uses s-box directly and XORs to produce output.
|
||||
s0 = uint32(sbox1[t0>>24])<<24 | uint32(sbox1[t3>>16&0xff])<<16 | uint32(sbox1[t2>>8&0xff])<<8 | uint32(sbox1[t1&0xff])
|
||||
s1 = uint32(sbox1[t1>>24])<<24 | uint32(sbox1[t0>>16&0xff])<<16 | uint32(sbox1[t3>>8&0xff])<<8 | uint32(sbox1[t2&0xff])
|
||||
s2 = uint32(sbox1[t2>>24])<<24 | uint32(sbox1[t1>>16&0xff])<<16 | uint32(sbox1[t0>>8&0xff])<<8 | uint32(sbox1[t3&0xff])
|
||||
s3 = uint32(sbox1[t3>>24])<<24 | uint32(sbox1[t2>>16&0xff])<<16 | uint32(sbox1[t1>>8&0xff])<<8 | uint32(sbox1[t0&0xff])
|
||||
|
||||
s0 ^= xk[k+0]
|
||||
s1 ^= xk[k+1]
|
||||
s2 ^= xk[k+2]
|
||||
s3 ^= xk[k+3]
|
||||
|
||||
_ = dst[15] // early bounds check
|
||||
binary.BigEndian.PutUint32(dst[0:4], s0)
|
||||
binary.BigEndian.PutUint32(dst[4:8], s1)
|
||||
binary.BigEndian.PutUint32(dst[8:12], s2)
|
||||
binary.BigEndian.PutUint32(dst[12:16], s3)
|
||||
}
|
||||
|
||||
// Apply sbox0 to each byte in w.
|
||||
func subw(w uint32) uint32 {
|
||||
return uint32(sbox0[w>>24])<<24 |
|
||||
uint32(sbox0[w>>16&0xff])<<16 |
|
||||
uint32(sbox0[w>>8&0xff])<<8 |
|
||||
uint32(sbox0[w&0xff])
|
||||
}
|
||||
|
||||
// Rotate
|
||||
func rotw(w uint32) uint32 { return w<<8 | w>>24 }
|
||||
|
||||
// Key expansion algorithm. See FIPS-197, Figure 11.
|
||||
// Their rcon[i] is our powx[i-1] << 24.
|
||||
func expandKeyGeneric(c *blockExpanded, key []byte) {
|
||||
checkGenericIsExpected()
|
||||
|
||||
// Encryption key setup.
|
||||
var i int
|
||||
nk := len(key) / 4
|
||||
for i = 0; i < nk; i++ {
|
||||
c.enc[i] = binary.BigEndian.Uint32(key[4*i:])
|
||||
}
|
||||
for ; i < c.roundKeysSize(); i++ {
|
||||
t := c.enc[i-1]
|
||||
if i%nk == 0 {
|
||||
t = subw(rotw(t)) ^ (uint32(powx[i/nk-1]) << 24)
|
||||
} else if nk > 6 && i%nk == 4 {
|
||||
t = subw(t)
|
||||
}
|
||||
c.enc[i] = c.enc[i-nk] ^ t
|
||||
}
|
||||
|
||||
// Derive decryption key from encryption key.
|
||||
// Reverse the 4-word round key sets from enc to produce dec.
|
||||
// All sets but the first and last get the MixColumn transform applied.
|
||||
n := c.roundKeysSize()
|
||||
for i := 0; i < n; i += 4 {
|
||||
ei := n - i - 4
|
||||
for j := 0; j < 4; j++ {
|
||||
x := c.enc[ei+j]
|
||||
if i > 0 && i+4 < n {
|
||||
x = td0[sbox0[x>>24]] ^ td1[sbox0[x>>16&0xff]] ^ td2[sbox0[x>>8&0xff]] ^ td3[sbox0[x&0xff]]
|
||||
}
|
||||
c.dec[i+j] = x
|
||||
}
|
||||
}
|
||||
}
|
24
aes/aes_noasm.go
Normal file
24
aes/aes_noasm.go
Normal file
@ -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() {}
|
356
aes/const.go
Normal file
356
aes/const.go
Normal file
@ -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,
|
||||
}
|
30
alias/alias.go
Normal file
30
alias/alias.go
Normal file
@ -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)
|
||||
}
|
25
auth.go
25
auth.go
@ -15,6 +15,7 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// verifyHandshakeSignature verifies a signature against pre-hashed
|
||||
@ -167,9 +168,6 @@ var rsaSignatureSchemes = []struct {
|
||||
// signatureSchemesForCertificate returns the list of supported SignatureSchemes
|
||||
// for a given certificate, based on the public key and the protocol version,
|
||||
// and optionally filtered by its explicit SupportedSignatureAlgorithms.
|
||||
//
|
||||
// This function must be kept in sync with supportedSignatureAlgorithms.
|
||||
// FIPS filtering is applied in the caller, selectSignatureScheme.
|
||||
func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme {
|
||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
@ -215,14 +213,18 @@ func signatureSchemesForCertificate(version uint16, cert *Certificate) []Signatu
|
||||
}
|
||||
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
var filteredSigAlgs []SignatureScheme
|
||||
for _, sigAlg := range sigAlgs {
|
||||
if isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) {
|
||||
filteredSigAlgs = append(filteredSigAlgs, sigAlg)
|
||||
}
|
||||
}
|
||||
return filteredSigAlgs
|
||||
sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool {
|
||||
return !isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms)
|
||||
})
|
||||
}
|
||||
|
||||
// Filter out any unsupported signature algorithms, for example due to
|
||||
// FIPS 140-3 policy, or any downstream changes to defaults.go.
|
||||
supportedAlgs := supportedSignatureAlgorithms()
|
||||
sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool {
|
||||
return !isSupportedSignatureAlgorithm(sigAlg, supportedAlgs)
|
||||
})
|
||||
|
||||
return sigAlgs
|
||||
}
|
||||
|
||||
@ -242,9 +244,6 @@ func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureSche
|
||||
// Pick signature scheme in the peer's preference order, as our
|
||||
// preference order is not configurable.
|
||||
for _, preferredAlg := range peerAlgs {
|
||||
if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) {
|
||||
continue
|
||||
}
|
||||
if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) {
|
||||
return preferredAlg, nil
|
||||
}
|
||||
|
14
cache.go
14
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
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,9 @@ import (
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/sys/cpu"
|
||||
|
||||
"github.com/xtls/reality/gcm"
|
||||
fipsaes "github.com/xtls/reality/aes"
|
||||
)
|
||||
|
||||
// CipherSuite is a TLS cipher suite. Note that most functions in this package
|
||||
@ -75,8 +78,8 @@ func CipherSuites() []*CipherSuite {
|
||||
// Most applications should not use the cipher suites in this list, and should
|
||||
// only use those returned by [CipherSuites].
|
||||
func InsecureCipherSuites() []*CipherSuite {
|
||||
// This list includes RC4, CBC_SHA256, and 3DES cipher suites. See
|
||||
// cipherSuitesPreferenceOrder for details.
|
||||
// This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher
|
||||
// suites. See cipherSuitesPreferenceOrder for details.
|
||||
return []*CipherSuite{
|
||||
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
|
||||
@ -232,7 +235,7 @@ var cipherSuitesTLS13 = []*cipherSuiteTLS13{ // TODO: replace with a map.
|
||||
// - Anything else comes before CBC_SHA256
|
||||
//
|
||||
// SHA-256 variants of the CBC ciphersuites don't implement any Lucky13
|
||||
// countermeasures. See http://www.isg.rhul.ac.uk/tls/Lucky13.html and
|
||||
// countermeasures. See https://www.isg.rhul.ac.uk/tls/Lucky13.html and
|
||||
// https://www.imperialviolet.org/2013/02/04/luckythirteen.html.
|
||||
//
|
||||
// - Anything else comes before 3DES
|
||||
@ -364,15 +367,13 @@ var tdesCiphers = map[uint16]bool{
|
||||
}
|
||||
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
// Keep in sync with crypto/internal/fips/aes/gcm.supportsAESGCM.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
||||
hasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
var aesgcmCiphers = map[uint16]bool{
|
||||
@ -386,9 +387,13 @@ var aesgcmCiphers = map[uint16]bool{
|
||||
TLS_AES_256_GCM_SHA384: true,
|
||||
}
|
||||
|
||||
// aesgcmPreferred returns whether the first known cipher in the preference list
|
||||
// is an AES-GCM cipher, implying the peer has hardware support for it.
|
||||
func aesgcmPreferred(ciphers []uint16) bool {
|
||||
// isAESGCMPreferred returns whether we have hardware support for AES-GCM, and the
|
||||
// first known cipher in the peer's preference list is an AES-GCM cipher,
|
||||
// implying the peer also has hardware support for it.
|
||||
func isAESGCMPreferred(ciphers []uint16) bool {
|
||||
if !hasAESGCMHardwareSupport {
|
||||
return false
|
||||
}
|
||||
for _, cID := range ciphers {
|
||||
if c := cipherSuiteByID(cID); c != nil {
|
||||
return aesgcmCiphers[cID]
|
||||
@ -511,7 +516,7 @@ func aeadAESGCM(key, noncePrefix []byte) aead {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
aead, err := cipher.NewGCM(aes)
|
||||
aead, err := gcm.NewGCMForTLS12(aes.(*fipsaes.Block))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -539,7 +544,7 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
aead, err := cipher.NewGCM(aes)
|
||||
aead, err := gcm.NewGCMForTLS13(aes.(*fipsaes.Block))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
229
common.go
229
common.go
@ -25,6 +25,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
_ "unsafe" // for linkname
|
||||
|
||||
"github.com/xtls/reality/fips140tls"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -147,13 +149,17 @@ const (
|
||||
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
|
||||
X25519MLKEM768 CurveID = 4588
|
||||
)
|
||||
|
||||
func isTLS13OnlyKeyExchange(curve CurveID) bool {
|
||||
return curve == X25519MLKEM768
|
||||
}
|
||||
|
||||
func isPQKeyExchange(curve CurveID) bool {
|
||||
return curve == X25519MLKEM768
|
||||
}
|
||||
|
||||
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
|
||||
type keyShare struct {
|
||||
group CurveID
|
||||
@ -241,6 +247,11 @@ type ConnectionState struct {
|
||||
// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_AES_128_GCM_SHA256).
|
||||
CipherSuite uint16
|
||||
|
||||
// CurveID is the key exchange mechanism used for the connection. The name
|
||||
// refers to elliptic curves for legacy reasons, see [CurveID]. If a legacy
|
||||
// RSA key exchange is used, this value is zero.
|
||||
CurveID CurveID
|
||||
|
||||
// NegotiatedProtocol is the application protocol negotiated with ALPN.
|
||||
NegotiatedProtocol string
|
||||
|
||||
@ -298,10 +309,6 @@ type ConnectionState struct {
|
||||
|
||||
// testingOnlyDidHRR is true if a HelloRetryRequest was sent/received.
|
||||
testingOnlyDidHRR bool
|
||||
|
||||
// testingOnlyCurveID is the selected CurveID, or zero if an RSA exchanges
|
||||
// is performed.
|
||||
testingOnlyCurveID CurveID
|
||||
}
|
||||
|
||||
// ExportKeyingMaterial returns length bytes of exported key material in a new
|
||||
@ -417,9 +424,12 @@ type ClientHelloInfo struct {
|
||||
// client is using SNI (see RFC 4366, Section 3.1).
|
||||
ServerName string
|
||||
|
||||
// SupportedCurves lists the elliptic curves supported by the client.
|
||||
// SupportedCurves is set only if the Supported Elliptic Curves
|
||||
// Extension is being used (see RFC 4492, Section 5.1.1).
|
||||
// SupportedCurves lists the key exchange mechanisms supported by the
|
||||
// client. It was renamed to "supported groups" in TLS 1.3, see RFC 8446,
|
||||
// Section 4.2.7 and [CurveID].
|
||||
//
|
||||
// SupportedCurves may be nil in TLS 1.2 and lower if the Supported Elliptic
|
||||
// Curves Extension is not being used (see RFC 4492, Section 5.1.1).
|
||||
SupportedCurves []CurveID
|
||||
|
||||
// SupportedPoints lists the point formats supported by the client.
|
||||
@ -447,7 +457,7 @@ type ClientHelloInfo struct {
|
||||
SupportedVersions []uint16
|
||||
|
||||
// Extensions lists the IDs of the extensions presented by the client
|
||||
// in the client hello.
|
||||
// in the ClientHello.
|
||||
Extensions []uint16
|
||||
|
||||
// Conn is the underlying net.Conn for the connection. Do not read
|
||||
@ -773,14 +783,15 @@ type Config struct {
|
||||
// which is currently TLS 1.3.
|
||||
MaxVersion uint16
|
||||
|
||||
// CurvePreferences contains the elliptic curves that will be used in
|
||||
// an ECDHE handshake, in preference order. If empty, the default will
|
||||
// be used. The client will use the first preference as the type for
|
||||
// its key share in TLS 1.3. This may change in the future.
|
||||
// CurvePreferences contains a set of supported key exchange mechanisms.
|
||||
// The name refers to elliptic curves for legacy reasons, see [CurveID].
|
||||
// The order of the list is ignored, and key exchange mechanisms are chosen
|
||||
// from this list using an internal preference order. If empty, the default
|
||||
// will be used.
|
||||
//
|
||||
// From Go 1.23, the default includes the X25519Kyber768Draft00 hybrid
|
||||
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
|
||||
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
|
||||
// or use the GODEBUG=tlskyber=0 environment variable.
|
||||
// or use the GODEBUG=tlsmlkem=0 environment variable.
|
||||
CurvePreferences []CurveID
|
||||
|
||||
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
|
||||
@ -803,8 +814,10 @@ type Config struct {
|
||||
|
||||
// EncryptedClientHelloConfigList is a serialized ECHConfigList. If
|
||||
// provided, clients will attempt to connect to servers using Encrypted
|
||||
// Client Hello (ECH) using one of the provided ECHConfigs. Servers
|
||||
// currently ignore this field.
|
||||
// Client Hello (ECH) using one of the provided ECHConfigs.
|
||||
//
|
||||
// Servers do not use this field. In order to configure ECH for servers, see
|
||||
// the EncryptedClientHelloKeys field.
|
||||
//
|
||||
// If the list contains no valid ECH configs, the handshake will fail
|
||||
// and return an error.
|
||||
@ -813,7 +826,7 @@ type Config struct {
|
||||
// be VersionTLS13.
|
||||
//
|
||||
// When EncryptedClientHelloConfigList is set, the handshake will only
|
||||
// succeed if ECH is sucessfully negotiated. If the server rejects ECH,
|
||||
// succeed if ECH is successfully negotiated. If the server rejects ECH,
|
||||
// an ECHRejectionError error will be returned, which may contain a new
|
||||
// ECHConfigList that the server suggests using.
|
||||
//
|
||||
@ -822,9 +835,11 @@ type Config struct {
|
||||
EncryptedClientHelloConfigList []byte
|
||||
|
||||
// EncryptedClientHelloRejectionVerify, if not nil, is called when ECH is
|
||||
// rejected, in order to verify the ECH provider certificate in the outer
|
||||
// Client Hello. If it returns a non-nil error, the handshake is aborted and
|
||||
// that error results.
|
||||
// rejected by the remote server, in order to verify the ECH provider
|
||||
// certificate in the outer ClientHello. If it returns a non-nil error, the
|
||||
// handshake is aborted and that error results.
|
||||
//
|
||||
// On the server side this field is not used.
|
||||
//
|
||||
// Unlike VerifyPeerCertificate and VerifyConnection, normal certificate
|
||||
// verification will not be performed before calling
|
||||
@ -836,6 +851,20 @@ type Config struct {
|
||||
// when ECH is rejected, even if set, and InsecureSkipVerify is ignored.
|
||||
EncryptedClientHelloRejectionVerify func(ConnectionState) error
|
||||
|
||||
// EncryptedClientHelloKeys are the ECH keys to use when a client
|
||||
// attempts ECH.
|
||||
//
|
||||
// If EncryptedClientHelloKeys is set, MinVersion, if set, must be
|
||||
// VersionTLS13.
|
||||
//
|
||||
// If a client attempts ECH, but it is rejected by the server, the server
|
||||
// will send a list of configs to retry based on the set of
|
||||
// EncryptedClientHelloKeys which have the SendAsRetry field set.
|
||||
//
|
||||
// On the client side, this field is ignored. In order to configure ECH for
|
||||
// clients, see the EncryptedClientHelloConfigList field.
|
||||
EncryptedClientHelloKeys []EncryptedClientHelloKey
|
||||
|
||||
// mutex protects sessionTicketKeys and autoSessionTicketKeys.
|
||||
mutex sync.RWMutex
|
||||
// sessionTicketKeys contains zero or more ticket keys. If set, it means
|
||||
@ -849,6 +878,25 @@ type Config struct {
|
||||
autoSessionTicketKeys []ticketKey
|
||||
}
|
||||
|
||||
// EncryptedClientHelloKey holds a private key that is associated
|
||||
// with a specific ECH config known to a client.
|
||||
type EncryptedClientHelloKey struct {
|
||||
// Config should be a marshalled ECHConfig associated with PrivateKey. This
|
||||
// must match the config provided to clients byte-for-byte. The config
|
||||
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
|
||||
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
|
||||
// AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003).
|
||||
Config []byte
|
||||
// PrivateKey should be a marshalled private key. Currently, we expect
|
||||
// this to be the output of [ecdh.PrivateKey.Bytes].
|
||||
PrivateKey []byte
|
||||
// SendAsRetry indicates if Config should be sent as part of the list of
|
||||
// retry configs when ECH is requested by the client but rejected by the
|
||||
// server.
|
||||
SendAsRetry bool
|
||||
}
|
||||
|
||||
|
||||
const (
|
||||
// ticketKeyLifetime is how long a ticket key remains valid and can be used to
|
||||
// resume a client connection.
|
||||
@ -936,6 +984,7 @@ func (c *Config) Clone() *Config {
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify,
|
||||
EncryptedClientHelloKeys: c.EncryptedClientHelloKeys,
|
||||
sessionTicketKeys: c.sessionTicketKeys,
|
||||
autoSessionTicketKeys: c.autoSessionTicketKeys,
|
||||
}
|
||||
@ -990,6 +1039,7 @@ func (c *Config) ticketKeys(configForClient *Config) []ticketKey {
|
||||
if configForClient != nil {
|
||||
configForClient.mutex.RLock()
|
||||
if configForClient.SessionTicketsDisabled {
|
||||
configForClient.mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
configForClient.initLegacySessionTicketKeyRLocked()
|
||||
@ -1083,20 +1133,28 @@ func (c *Config) time() time.Time {
|
||||
return t()
|
||||
}
|
||||
|
||||
func (c *Config) cipherSuites() []uint16 {
|
||||
func (c *Config) cipherSuites(aesGCMPreferred bool) []uint16 {
|
||||
var cipherSuites []uint16
|
||||
if c.CipherSuites == nil {
|
||||
if needFIPS() {
|
||||
return defaultCipherSuitesFIPS
|
||||
}
|
||||
return defaultCipherSuites()
|
||||
}
|
||||
if needFIPS() {
|
||||
cipherSuites := slices.Clone(c.CipherSuites)
|
||||
return slices.DeleteFunc(cipherSuites, func(id uint16) bool {
|
||||
return !slices.Contains(defaultCipherSuitesFIPS, id)
|
||||
cipherSuites = defaultCipherSuites(aesGCMPreferred)
|
||||
} else {
|
||||
cipherSuites = supportedCipherSuites(aesGCMPreferred)
|
||||
cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool {
|
||||
return !slices.Contains(c.CipherSuites, id)
|
||||
})
|
||||
}
|
||||
return c.CipherSuites
|
||||
if fips140tls.Required() {
|
||||
cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool {
|
||||
return !slices.Contains(allowedCipherSuitesFIPS, id)
|
||||
})
|
||||
}
|
||||
return cipherSuites
|
||||
}
|
||||
|
||||
// supportedCipherSuites returns the supported TLS 1.0–1.2 cipher suites in an
|
||||
// undefined order. For preference ordering, use [Config.cipherSuites].
|
||||
func (c *Config) supportedCipherSuites() []uint16 {
|
||||
return c.cipherSuites(false)
|
||||
}
|
||||
|
||||
var supportedVersions = []uint16{
|
||||
@ -1114,7 +1172,7 @@ const roleServer = false
|
||||
func (c *Config) supportedVersions(isClient bool) []uint16 {
|
||||
versions := make([]uint16, 0, len(supportedVersions))
|
||||
for _, v := range supportedVersions {
|
||||
if needFIPS() && !slices.Contains(defaultSupportedVersionsFIPS, v) {
|
||||
if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) {
|
||||
continue
|
||||
}
|
||||
if (c == nil || c.MinVersion == 0) && v < VersionTLS12 {
|
||||
@ -1157,47 +1215,36 @@ 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 needFIPS() {
|
||||
return slices.DeleteFunc(curvePreferences, func(c CurveID) bool {
|
||||
return !slices.Contains(defaultCurvePreferencesFIPS, c)
|
||||
curvePreferences := defaultCurvePreferences()
|
||||
if fips140tls.Required() {
|
||||
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
|
||||
return !slices.Contains(allowedCurvePreferencesFIPS, x)
|
||||
})
|
||||
}
|
||||
} else if needFIPS() {
|
||||
curvePreferences = slices.Clone(defaultCurvePreferencesFIPS)
|
||||
} else {
|
||||
curvePreferences = defaultCurvePreferences()
|
||||
if c != nil && len(c.CurvePreferences) != 0 {
|
||||
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
|
||||
return !slices.Contains(c.CurvePreferences, x)
|
||||
})
|
||||
}
|
||||
if version < VersionTLS13 {
|
||||
return slices.DeleteFunc(curvePreferences, func(c CurveID) bool {
|
||||
return c == x25519Kyber768Draft00
|
||||
})
|
||||
curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange)
|
||||
}
|
||||
return curvePreferences
|
||||
}
|
||||
|
||||
func (c *Config) supportsCurve(version uint16, curve CurveID) bool {
|
||||
for _, cc := range c.curvePreferences(version) {
|
||||
if cc == curve {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(c.curvePreferences(version), curve)
|
||||
}
|
||||
|
||||
// mutualVersion returns the protocol version to use given the advertised
|
||||
// versions of the peer. Priority is given to the peer preference order.
|
||||
func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) {
|
||||
supportedVersions := c.supportedVersions(isClient)
|
||||
for _, peerVersion := range peerVersions {
|
||||
for _, v := range supportedVersions {
|
||||
if v == peerVersion {
|
||||
for _, v := range peerVersions {
|
||||
if slices.Contains(supportedVersions, v) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@ -1316,7 +1363,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
|
||||
}
|
||||
// Finally, there needs to be a mutual cipher suite that uses the static
|
||||
// RSA key exchange instead of ECDHE.
|
||||
rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
|
||||
rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool {
|
||||
if c.flags&suiteECDHE != 0 {
|
||||
return false
|
||||
}
|
||||
@ -1347,7 +1394,11 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
|
||||
}
|
||||
|
||||
// The only signed key exchange we support is ECDHE.
|
||||
if !supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints) {
|
||||
ecdheSupported, err := supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ecdheSupported {
|
||||
return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange"))
|
||||
}
|
||||
|
||||
@ -1393,7 +1444,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
|
||||
// Make sure that there is a mutually supported cipher suite that works with
|
||||
// this certificate. Cipher suite selection will then apply the logic in
|
||||
// reverse to pick it. See also serverHandshakeState.cipherSuiteOk.
|
||||
cipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
|
||||
cipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool {
|
||||
if c.flags&suiteECDHE == 0 {
|
||||
return false
|
||||
}
|
||||
@ -1637,19 +1688,14 @@ func unexpectedMessageError(wanted, got any) error {
|
||||
|
||||
// supportedSignatureAlgorithms returns the supported signature algorithms.
|
||||
func supportedSignatureAlgorithms() []SignatureScheme {
|
||||
if !needFIPS() {
|
||||
return defaultSupportedSignatureAlgorithms
|
||||
if fips140tls.Required() {
|
||||
return allowedSupportedSignatureAlgorithmsFIPS
|
||||
}
|
||||
return defaultSupportedSignatureAlgorithmsFIPS
|
||||
return defaultSupportedSignatureAlgorithms
|
||||
}
|
||||
|
||||
func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlgorithms []SignatureScheme) bool {
|
||||
for _, s := range supportedSignatureAlgorithms {
|
||||
if s == sigAlg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(supportedSignatureAlgorithms, sigAlg)
|
||||
}
|
||||
|
||||
// CertificateVerificationError is returned when certificate verification fails during the handshake.
|
||||
@ -1666,3 +1712,42 @@ func (e *CertificateVerificationError) Error() string {
|
||||
func (e *CertificateVerificationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// fipsAllowedChains returns chains that are allowed to be used in a TLS connection
|
||||
// based on the current fips140tls enforcement setting.
|
||||
//
|
||||
// If fips140tls is not required, the chains are returned as-is with no processing.
|
||||
// Otherwise, the returned chains are filtered to only those allowed by FIPS 140-3.
|
||||
// If this results in no chains it returns an error.
|
||||
func fipsAllowedChains(chains [][]*x509.Certificate) ([][]*x509.Certificate, error) {
|
||||
if !fips140tls.Required() {
|
||||
return chains, nil
|
||||
}
|
||||
|
||||
permittedChains := make([][]*x509.Certificate, 0, len(chains))
|
||||
for _, chain := range chains {
|
||||
if fipsAllowChain(chain) {
|
||||
permittedChains = append(permittedChains, chain)
|
||||
}
|
||||
}
|
||||
|
||||
if len(permittedChains) == 0 {
|
||||
return nil, errors.New("tls: no FIPS compatible certificate chains found")
|
||||
}
|
||||
|
||||
return permittedChains, nil
|
||||
}
|
||||
|
||||
func fipsAllowChain(chain []*x509.Certificate) bool {
|
||||
if len(chain) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, cert := range chain {
|
||||
if !isCertificateAllowedFIPS(cert) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -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) + ")"
|
||||
|
12
conn.go
12
conn.go
@ -758,6 +758,15 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
|
||||
return c.in.setErrorLocked(io.EOF)
|
||||
}
|
||||
if c.vers == VersionTLS13 {
|
||||
// TLS 1.3 removed warning-level alerts except for alertUserCanceled
|
||||
// (RFC 8446, § 6.1). Since at least one major implementation
|
||||
// (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert,
|
||||
// many TLS stacks now ignore it outright when seen in a TLS 1.3
|
||||
// handshake (e.g. BoringSSL, NSS, Rustls).
|
||||
if alert(data[1]) == alertUserCanceled {
|
||||
// Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry.
|
||||
return c.retryReadRecord(expectChangeCipherSpec)
|
||||
}
|
||||
return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])})
|
||||
}
|
||||
switch data[0] {
|
||||
@ -1685,8 +1694,7 @@ func (c *Conn) connectionStateLocked() ConnectionState {
|
||||
state.NegotiatedProtocol = c.clientProtocol
|
||||
state.DidResume = c.didResume
|
||||
state.testingOnlyDidHRR = c.didHRR
|
||||
// c.curveID is not set on TLS 1.0–1.2 resumptions. Fix that before exposing it.
|
||||
state.testingOnlyCurveID = c.curveID
|
||||
state.CurveID = c.curveID
|
||||
state.NegotiatedProtocolIsMutual = true
|
||||
state.ServerName = c.serverName
|
||||
state.CipherSuite = c.cipherSuite
|
||||
|
59
defaults.go
59
defaults.go
@ -12,14 +12,15 @@ import (
|
||||
// Defaults are collected in this file to allow distributions to more easily patch
|
||||
// them to apply local policies.
|
||||
|
||||
//var tlskyber = godebug.New("tlskyber")
|
||||
//var tlsmlkem = godebug.New("tlsmlkem")
|
||||
|
||||
// defaultCurvePreferences is the default set of supported key exchanges, as
|
||||
// well as the preference order.
|
||||
func defaultCurvePreferences() []CurveID {
|
||||
if false {
|
||||
return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
|
||||
}
|
||||
// For now, x25519Kyber768Draft00 must always be followed by X25519.
|
||||
return []CurveID{x25519Kyber768Draft00, X25519, CurveP256, CurveP384, CurveP521}
|
||||
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
|
||||
}
|
||||
|
||||
// defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that
|
||||
@ -44,9 +45,17 @@ var defaultSupportedSignatureAlgorithms = []SignatureScheme{
|
||||
//var tlsrsakex = godebug.New("tlsrsakex")
|
||||
//var tls3des = godebug.New("tls3des")
|
||||
|
||||
func defaultCipherSuites() []uint16 {
|
||||
suites := slices.Clone(cipherSuitesPreferenceOrder)
|
||||
return slices.DeleteFunc(suites, func(c uint16) bool {
|
||||
func supportedCipherSuites(aesGCMPreferred bool) []uint16 {
|
||||
if aesGCMPreferred {
|
||||
return slices.Clone(cipherSuitesPreferenceOrder)
|
||||
} else {
|
||||
return slices.Clone(cipherSuitesPreferenceOrderNoAES)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCipherSuites(aesGCMPreferred bool) []uint16 {
|
||||
cipherSuites := supportedCipherSuites(aesGCMPreferred)
|
||||
return slices.DeleteFunc(cipherSuites, func(c uint16) bool {
|
||||
return disabledCipherSuites[c] ||
|
||||
rsaKexCiphers[c] ||
|
||||
tdesCiphers[c]
|
||||
@ -88,41 +97,3 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
// The FIPS-only policies below match BoringSSL's ssl_policy_fips_202205.
|
||||
|
||||
var defaultSupportedVersionsFIPS = []uint16{
|
||||
VersionTLS12,
|
||||
VersionTLS13,
|
||||
}
|
||||
|
||||
// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
|
||||
// in preference order (most preferable first).
|
||||
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384}
|
||||
|
||||
// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
|
||||
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
|
||||
var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
|
||||
PSSWithSHA256,
|
||||
PSSWithSHA384,
|
||||
PSSWithSHA512,
|
||||
PKCS1WithSHA256,
|
||||
ECDSAWithP256AndSHA256,
|
||||
PKCS1WithSHA384,
|
||||
ECDSAWithP384AndSHA384,
|
||||
PKCS1WithSHA512,
|
||||
}
|
||||
|
||||
// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.
|
||||
var defaultCipherSuitesFIPS = []uint16{
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
// defaultCipherSuitesTLS13FIPS are the FIPS-allowed cipher suites for TLS 1.3.
|
||||
var defaultCipherSuitesTLS13FIPS = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
69
defaults_boring.go
Normal file
69
defaults_boring.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build boringcrypto
|
||||
|
||||
package reality
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
// These Go+BoringCrypto policies mostly match BoringSSL's
|
||||
// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2.
|
||||
// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
|
||||
//
|
||||
// P-521 is allowed per https://go.dev/issue/71757.
|
||||
//
|
||||
// They are applied when crypto/tls/fipsonly is imported with GOEXPERIMENT=boringcrypto.
|
||||
|
||||
var (
|
||||
allowedSupportedVersionsFIPS = []uint16{
|
||||
VersionTLS12,
|
||||
VersionTLS13,
|
||||
}
|
||||
allowedCurvePreferencesFIPS = []CurveID{
|
||||
CurveP256,
|
||||
CurveP384,
|
||||
CurveP521,
|
||||
}
|
||||
allowedSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
|
||||
PSSWithSHA256,
|
||||
PSSWithSHA384,
|
||||
PSSWithSHA512,
|
||||
PKCS1WithSHA256,
|
||||
ECDSAWithP256AndSHA256,
|
||||
PKCS1WithSHA384,
|
||||
ECDSAWithP384AndSHA384,
|
||||
PKCS1WithSHA512,
|
||||
ECDSAWithP521AndSHA512,
|
||||
}
|
||||
allowedCipherSuitesFIPS = []uint16{
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
allowedCipherSuitesTLS13FIPS = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
)
|
||||
|
||||
func isCertificateAllowedFIPS(c *x509.Certificate) bool {
|
||||
// The key must be RSA 2048, RSA 3072, RSA 4096,
|
||||
// or ECDSA P-256, P-384, P-521.
|
||||
switch k := c.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
size := k.N.BitLen()
|
||||
return size == 2048 || size == 3072 || size == 4096
|
||||
case *ecdsa.PublicKey:
|
||||
return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
76
defaults_fips140.go
Normal file
76
defaults_fips140.go
Normal file
@ -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
|
||||
}
|
||||
}
|
515
ech.go
515
ech.go
@ -5,7 +5,10 @@
|
||||
package reality
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
@ -13,6 +16,18 @@ import (
|
||||
"github.com/xtls/reality/hpke"
|
||||
)
|
||||
|
||||
// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
|
||||
// We need this so that when we insert them into ECHConfigs the ordering
|
||||
// is stable.
|
||||
var sortedSupportedAEADs []uint16
|
||||
|
||||
func init() {
|
||||
for aeadID := range hpke.SupportedAEADs {
|
||||
sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID)
|
||||
}
|
||||
slices.Sort(sortedSupportedAEADs)
|
||||
}
|
||||
|
||||
type echCipher struct {
|
||||
KDFID uint16
|
||||
AEADID uint16
|
||||
@ -39,87 +54,112 @@ type echConfig struct {
|
||||
Extensions []echExtension
|
||||
}
|
||||
|
||||
var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")
|
||||
var errMalformedECHConfigList = errors.New("tls: malformed ECHConfigList")
|
||||
|
||||
type echConfigErr struct {
|
||||
field string
|
||||
}
|
||||
|
||||
func (e *echConfigErr) Error() string {
|
||||
if e.field == "" {
|
||||
return "tls: malformed ECHConfig"
|
||||
}
|
||||
return fmt.Sprintf("tls: malformed ECHConfig, invalid %s field", e.field)
|
||||
}
|
||||
|
||||
func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) {
|
||||
s := cryptobyte.String(enc)
|
||||
ec.raw = []byte(enc)
|
||||
if !s.ReadUint16(&ec.Version) {
|
||||
return false, echConfig{}, &echConfigErr{"version"}
|
||||
}
|
||||
if !s.ReadUint16(&ec.Length) {
|
||||
return false, echConfig{}, &echConfigErr{"length"}
|
||||
}
|
||||
if len(ec.raw) < int(ec.Length)+4 {
|
||||
return false, echConfig{}, &echConfigErr{"length"}
|
||||
}
|
||||
ec.raw = ec.raw[:ec.Length+4]
|
||||
if ec.Version != extensionEncryptedClientHello {
|
||||
s.Skip(int(ec.Length))
|
||||
return true, echConfig{}, nil
|
||||
}
|
||||
if !s.ReadUint8(&ec.ConfigID) {
|
||||
return false, echConfig{}, &echConfigErr{"config_id"}
|
||||
}
|
||||
if !s.ReadUint16(&ec.KemID) {
|
||||
return false, echConfig{}, &echConfigErr{"kem_id"}
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &ec.PublicKey) {
|
||||
return false, echConfig{}, &echConfigErr{"public_key"}
|
||||
}
|
||||
var cipherSuites cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&cipherSuites) {
|
||||
return false, echConfig{}, &echConfigErr{"cipher_suites"}
|
||||
}
|
||||
for !cipherSuites.Empty() {
|
||||
var c echCipher
|
||||
if !cipherSuites.ReadUint16(&c.KDFID) {
|
||||
return false, echConfig{}, &echConfigErr{"cipher_suites kdf_id"}
|
||||
}
|
||||
if !cipherSuites.ReadUint16(&c.AEADID) {
|
||||
return false, echConfig{}, &echConfigErr{"cipher_suites aead_id"}
|
||||
}
|
||||
ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
|
||||
}
|
||||
if !s.ReadUint8(&ec.MaxNameLength) {
|
||||
return false, echConfig{}, &echConfigErr{"maximum_name_length"}
|
||||
}
|
||||
var publicName cryptobyte.String
|
||||
if !s.ReadUint8LengthPrefixed(&publicName) {
|
||||
return false, echConfig{}, &echConfigErr{"public_name"}
|
||||
}
|
||||
ec.PublicName = publicName
|
||||
var extensions cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&extensions) {
|
||||
return false, echConfig{}, &echConfigErr{"extensions"}
|
||||
}
|
||||
for !extensions.Empty() {
|
||||
var e echExtension
|
||||
if !extensions.ReadUint16(&e.Type) {
|
||||
return false, echConfig{}, &echConfigErr{"extensions type"}
|
||||
}
|
||||
if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
|
||||
return false, echConfig{}, &echConfigErr{"extensions data"}
|
||||
}
|
||||
ec.Extensions = append(ec.Extensions, e)
|
||||
}
|
||||
|
||||
return false, ec, nil
|
||||
}
|
||||
|
||||
// parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
|
||||
// slice of parsed ECHConfigs, in the same order they were parsed, or an error
|
||||
// if the list is malformed.
|
||||
func parseECHConfigList(data []byte) ([]echConfig, error) {
|
||||
s := cryptobyte.String(data)
|
||||
// Skip the length prefix
|
||||
var length uint16
|
||||
if !s.ReadUint16(&length) {
|
||||
return nil, errMalformedECHConfig
|
||||
return nil, errMalformedECHConfigList
|
||||
}
|
||||
if length != uint16(len(data)-2) {
|
||||
return nil, errMalformedECHConfig
|
||||
return nil, errMalformedECHConfigList
|
||||
}
|
||||
var configs []echConfig
|
||||
for len(s) > 0 {
|
||||
var ec echConfig
|
||||
ec.raw = []byte(s)
|
||||
if !s.ReadUint16(&ec.Version) {
|
||||
return nil, errMalformedECHConfig
|
||||
if len(s) < 4 {
|
||||
return nil, errors.New("tls: malformed ECHConfig")
|
||||
}
|
||||
if !s.ReadUint16(&ec.Length) {
|
||||
return nil, errMalformedECHConfig
|
||||
configLen := uint16(s[2])<<8 | uint16(s[3])
|
||||
skip, ec, err := parseECHConfig(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ec.raw) < int(ec.Length)+4 {
|
||||
return nil, errMalformedECHConfig
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
s = s[configLen+4:]
|
||||
if !skip {
|
||||
configs = append(configs, ec)
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
@ -196,6 +236,195 @@ func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, e
|
||||
return append(h, make([]byte, paddingLen)...), nil
|
||||
}
|
||||
|
||||
func skipUint8LengthPrefixed(s *cryptobyte.String) bool {
|
||||
var skip uint8
|
||||
if !s.ReadUint8(&skip) {
|
||||
return false
|
||||
}
|
||||
return s.Skip(int(skip))
|
||||
}
|
||||
|
||||
func skipUint16LengthPrefixed(s *cryptobyte.String) bool {
|
||||
var skip uint16
|
||||
if !s.ReadUint16(&skip) {
|
||||
return false
|
||||
}
|
||||
return s.Skip(int(skip))
|
||||
}
|
||||
|
||||
type rawExtension struct {
|
||||
extType uint16
|
||||
data []byte
|
||||
}
|
||||
|
||||
func extractRawExtensions(hello *clientHelloMsg) ([]rawExtension, error) {
|
||||
s := cryptobyte.String(hello.original)
|
||||
if !s.Skip(4+2+32) || // header, version, random
|
||||
!skipUint8LengthPrefixed(&s) || // session ID
|
||||
!skipUint16LengthPrefixed(&s) || // cipher suites
|
||||
!skipUint8LengthPrefixed(&s) { // compression methods
|
||||
return nil, errors.New("tls: malformed outer client hello")
|
||||
}
|
||||
var rawExtensions []rawExtension
|
||||
var extensions cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&extensions) {
|
||||
return nil, errors.New("tls: malformed outer client hello")
|
||||
}
|
||||
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) ||
|
||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
rawExtensions = append(rawExtensions, rawExtension{extension, extData})
|
||||
}
|
||||
return rawExtensions, nil
|
||||
}
|
||||
|
||||
func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHelloMsg, error) {
|
||||
// Reconstructing the inner client hello from its encoded form is somewhat
|
||||
// complicated. It is missing its header (message type and length), session
|
||||
// ID, and the extensions may be compressed. Since we need to put the
|
||||
// extensions back in the same order as they were in the raw outer hello,
|
||||
// and since we don't store the raw extensions, or the order we parsed them
|
||||
// in, we need to reparse the raw extensions from the outer hello in order
|
||||
// to properly insert them into the inner hello. This _should_ result in raw
|
||||
// bytes which match the hello as it was generated by the client.
|
||||
innerReader := cryptobyte.String(encoded)
|
||||
var versionAndRandom, sessionID, cipherSuites, compressionMethods []byte
|
||||
var extensions cryptobyte.String
|
||||
if !innerReader.ReadBytes(&versionAndRandom, 2+32) ||
|
||||
!readUint8LengthPrefixed(&innerReader, &sessionID) ||
|
||||
len(sessionID) != 0 ||
|
||||
!readUint16LengthPrefixed(&innerReader, &cipherSuites) ||
|
||||
!readUint8LengthPrefixed(&innerReader, &compressionMethods) ||
|
||||
!innerReader.ReadUint16LengthPrefixed(&extensions) {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
|
||||
// The specification says we must verify that the trailing padding is all
|
||||
// zeros. This is kind of weird for TLS messages, where we generally just
|
||||
// throw away any trailing garbage.
|
||||
for _, p := range innerReader {
|
||||
if p != 0 {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
}
|
||||
|
||||
rawOuterExts, err := extractRawExtensions(outer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recon := cryptobyte.NewBuilder(nil)
|
||||
recon.AddUint8(typeClientHello)
|
||||
recon.AddUint24LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(versionAndRandom)
|
||||
recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(outer.sessionId)
|
||||
})
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(cipherSuites)
|
||||
})
|
||||
recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(compressionMethods)
|
||||
})
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) ||
|
||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
if extension == extensionECHOuterExtensions {
|
||||
if !extData.ReadUint8LengthPrefixed(&extData) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
var i int
|
||||
for !extData.Empty() {
|
||||
var extType uint16
|
||||
if !extData.ReadUint16(&extType) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
if extType == extensionEncryptedClientHello {
|
||||
recon.SetError(errors.New("tls: invalid outer extensions"))
|
||||
return
|
||||
}
|
||||
for ; i <= len(rawOuterExts); i++ {
|
||||
if i == len(rawOuterExts) {
|
||||
recon.SetError(errors.New("tls: invalid outer extensions"))
|
||||
return
|
||||
}
|
||||
if rawOuterExts[i].extType == extType {
|
||||
break
|
||||
}
|
||||
}
|
||||
recon.AddUint16(rawOuterExts[i].extType)
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(rawOuterExts[i].data)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
recon.AddUint16(extension)
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(extData)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
reconBytes, err := recon.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inner := &clientHelloMsg{}
|
||||
if !inner.unmarshal(reconBytes) {
|
||||
return nil, errors.New("tls: invalid reconstructed inner client hello")
|
||||
}
|
||||
|
||||
if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) {
|
||||
return nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
hasTLS13 := false
|
||||
for _, v := range inner.supportedVersions {
|
||||
// Skip GREASE values (values of the form 0x?A0A).
|
||||
// GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism used by
|
||||
// browsers like Chrome to ensure TLS implementations correctly ignore unknown values.
|
||||
// GREASE values follow a specific pattern: 0x?A0A, where ? can be any hex digit.
|
||||
// These values should be ignored when processing supported TLS versions.
|
||||
if v&0x0F0F == 0x0A0A && v&0xff == v>>8 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure at least TLS 1.3 is offered.
|
||||
if v == VersionTLS13 {
|
||||
hasTLS13 = true
|
||||
} else if v < VersionTLS13 {
|
||||
// Reject if any non-GREASE value is below TLS 1.3, as ECH requires TLS 1.3+.
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension with unsupported versions")
|
||||
}
|
||||
}
|
||||
|
||||
if !hasTLS13 {
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension but did not offer TLS 1.3")
|
||||
}
|
||||
|
||||
return inner, nil
|
||||
}
|
||||
|
||||
func decryptECHPayload(context *hpke.Recipient, hello, payload []byte) ([]byte, error) {
|
||||
outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1)
|
||||
return context.Open(outerAAD, payload)
|
||||
}
|
||||
|
||||
func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint8(0) // outer
|
||||
@ -207,7 +436,7 @@ func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payl
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echContext, useKey bool) error {
|
||||
func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error {
|
||||
var encapKey []byte
|
||||
if useKey {
|
||||
encapKey = ech.encapsulatedKey
|
||||
@ -282,3 +511,159 @@ type ECHRejectionError struct {
|
||||
func (e *ECHRejectionError) Error() string {
|
||||
return "tls: server rejected ECH"
|
||||
}
|
||||
|
||||
var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension")
|
||||
var errInvalidECHExt = errors.New("tls: client sent invalid encrypted_client_hello extension")
|
||||
|
||||
type echExtType uint8
|
||||
|
||||
const (
|
||||
innerECHExt echExtType = 1
|
||||
outerECHExt echExtType = 0
|
||||
)
|
||||
|
||||
func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) {
|
||||
data := make([]byte, len(ext))
|
||||
copy(data, ext)
|
||||
s := cryptobyte.String(data)
|
||||
var echInt uint8
|
||||
if !s.ReadUint8(&echInt) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
echType = echExtType(echInt)
|
||||
if echType == innerECHExt {
|
||||
if !s.Empty() {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
return echType, cs, 0, nil, nil, nil
|
||||
}
|
||||
if echType != outerECHExt {
|
||||
err = errInvalidECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint16(&cs.KDFID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint16(&cs.AEADID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint8(&configID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &encap) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &payload) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: clone encap and payload so that mutating them does not mutate the
|
||||
// raw extension bytes.
|
||||
return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil
|
||||
}
|
||||
|
||||
func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) {
|
||||
builder := cryptobyte.NewBuilder(nil)
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
for _, c := range configs {
|
||||
builder.AddBytes(c.Config)
|
||||
}
|
||||
})
|
||||
return builder.Bytes()
|
||||
}
|
||||
|
||||
func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) {
|
||||
echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello)
|
||||
if err != nil {
|
||||
if errors.Is(err, errInvalidECHExt) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
} else {
|
||||
c.sendAlert(alertDecodeError)
|
||||
}
|
||||
|
||||
return nil, nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
if echType == innerECHExt {
|
||||
return outer, &echServerContext{inner: true}, nil
|
||||
}
|
||||
|
||||
if len(c.config.EncryptedClientHelloKeys) == 0 {
|
||||
return outer, nil, nil
|
||||
}
|
||||
|
||||
for _, echKey := range c.config.EncryptedClientHelloKeys {
|
||||
skip, config, err := parseECHConfig(echKey.Config)
|
||||
if err != nil || skip {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err)
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err)
|
||||
}
|
||||
info := append([]byte("tls ech\x00"), echKey.Config...)
|
||||
hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
|
||||
if err != nil {
|
||||
// attempt next trial decryption
|
||||
continue
|
||||
}
|
||||
|
||||
encodedInner, err := decryptECHPayload(hpkeContext, outer.original, payload)
|
||||
if err != nil {
|
||||
// attempt next trial decryption
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: we do not enforce that the sent server_name matches the ECH
|
||||
// configs PublicName, since this is not particularly important, and
|
||||
// the client already had to know what it was in order to properly
|
||||
// encrypt the payload. This is only a MAY in the spec, so we're not
|
||||
// doing anything revolutionary.
|
||||
|
||||
echInner, err := decodeInnerClientHello(outer, encodedInner)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
c.echAccepted = true
|
||||
|
||||
return echInner, &echServerContext{
|
||||
hpkeContext: hpkeContext,
|
||||
configID: configID,
|
||||
ciphersuite: echCiphersuite,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return outer, nil, nil
|
||||
}
|
||||
|
||||
func buildRetryConfigList(keys []EncryptedClientHelloKey) ([]byte, error) {
|
||||
var atLeastOneRetryConfig bool
|
||||
var retryBuilder cryptobyte.Builder
|
||||
retryBuilder.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, c := range keys {
|
||||
if !c.SendAsRetry {
|
||||
continue
|
||||
}
|
||||
atLeastOneRetryConfig = true
|
||||
b.AddBytes(c.Config)
|
||||
}
|
||||
})
|
||||
if !atLeastOneRetryConfig {
|
||||
return nil, nil
|
||||
}
|
||||
return retryBuilder.Bytes()
|
||||
}
|
37
fips140tls/fipstls.go
Normal file
37
fips140tls/fipstls.go
Normal file
@ -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)
|
||||
}
|
143
gcm/gcm.go
Normal file
143
gcm/gcm.go
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/xtls/reality/aes"
|
||||
"github.com/xtls/reality/alias"
|
||||
)
|
||||
|
||||
// GCM represents a Galois Counter Mode with a specific key.
|
||||
type GCM struct {
|
||||
cipher aes.Block
|
||||
nonceSize int
|
||||
tagSize int
|
||||
gcmPlatformData
|
||||
}
|
||||
|
||||
func New(cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) {
|
||||
// This function is outlined to let the allocation happen on the parent stack.
|
||||
return newGCM(&GCM{}, cipher, nonceSize, tagSize)
|
||||
}
|
||||
|
||||
// newGCM is marked go:noinline to avoid it inlining into New, and making New
|
||||
// too complex to inline itself.
|
||||
//
|
||||
//go:noinline
|
||||
func newGCM(g *GCM, cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) {
|
||||
if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize {
|
||||
return nil, errors.New("cipher: incorrect tag size given to GCM")
|
||||
}
|
||||
if nonceSize <= 0 {
|
||||
return nil, errors.New("cipher: the nonce can't have zero length")
|
||||
}
|
||||
if cipher.BlockSize() != gcmBlockSize {
|
||||
return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
|
||||
}
|
||||
g.cipher = *cipher
|
||||
g.nonceSize = nonceSize
|
||||
g.tagSize = tagSize
|
||||
initGCM(g)
|
||||
return g, nil
|
||||
}
|
||||
|
||||
const (
|
||||
gcmBlockSize = 16
|
||||
gcmTagSize = 16
|
||||
gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
|
||||
gcmStandardNonceSize = 12
|
||||
)
|
||||
|
||||
func (g *GCM) NonceSize() int {
|
||||
return g.nonceSize
|
||||
}
|
||||
|
||||
func (g *GCM) Overhead() int {
|
||||
return g.tagSize
|
||||
}
|
||||
|
||||
func (g *GCM) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
// fips140.RecordNonApproved()
|
||||
return g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||
}
|
||||
|
||||
func (g *GCM) sealAfterIndicator(dst, nonce, plaintext, data []byte) []byte {
|
||||
if len(nonce) != g.nonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
if g.nonceSize == 0 {
|
||||
panic("crypto/cipher: incorrect GCM nonce size")
|
||||
}
|
||||
if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize {
|
||||
panic("crypto/cipher: message too large for GCM")
|
||||
}
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
panic("crypto/cipher: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, data) {
|
||||
panic("crypto/cipher: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
|
||||
seal(out, g, nonce, plaintext, data)
|
||||
return ret
|
||||
}
|
||||
|
||||
var errOpen = errors.New("cipher: message authentication failed")
|
||||
|
||||
func (g *GCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
if len(nonce) != g.nonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
// Sanity check to prevent the authentication from always succeeding if an
|
||||
// implementation leaves tagSize uninitialized, for example.
|
||||
if g.tagSize < gcmMinimumTagSize {
|
||||
panic("crypto/cipher: incorrect GCM tag size")
|
||||
}
|
||||
|
||||
if len(ciphertext) < g.tagSize {
|
||||
return nil, errOpen
|
||||
}
|
||||
if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) {
|
||||
return nil, errOpen
|
||||
}
|
||||
|
||||
ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize)
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
panic("crypto/cipher: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, data) {
|
||||
panic("crypto/cipher: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
|
||||
// fips140.RecordApproved()
|
||||
if err := open(out, g, nonce, ciphertext, data); err != nil {
|
||||
// We sometimes decrypt and authenticate concurrently, so we overwrite
|
||||
// dst in the event of a tag mismatch. To be consistent across platforms
|
||||
// and to avoid releasing unauthenticated plaintext, we clear the buffer
|
||||
// in the event of an error.
|
||||
clear(out)
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// sliceForAppend takes a slice and a requested number of bytes. It returns a
|
||||
// slice with the contents of the given slice followed by that many bytes and a
|
||||
// second slice that aliases into it and contains only the extra bytes. If the
|
||||
// original slice has sufficient capacity then no allocation is performed.
|
||||
func sliceForAppend(in []byte, n int) (head, tail []byte) {
|
||||
if total := len(in) + n; cap(in) >= total {
|
||||
head = in[:total]
|
||||
} else {
|
||||
head = make([]byte, total)
|
||||
copy(head, in)
|
||||
}
|
||||
tail = head[len(in):]
|
||||
return
|
||||
}
|
106
gcm/gcm_generic.go
Normal file
106
gcm/gcm_generic.go
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcm
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/xtls/reality/aes"
|
||||
)
|
||||
|
||||
func sealGeneric(out []byte, g *GCM, nonce, plaintext, additionalData []byte) {
|
||||
var H, counter, tagMask [gcmBlockSize]byte
|
||||
aes.EncryptBlockInternal(&g.cipher, H[:], H[:])
|
||||
deriveCounterGeneric(&H, &counter, nonce)
|
||||
gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter)
|
||||
|
||||
gcmCounterCryptGeneric(&g.cipher, out, plaintext, &counter)
|
||||
|
||||
var tag [gcmTagSize]byte
|
||||
gcmAuthGeneric(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData)
|
||||
copy(out[len(plaintext):], tag[:])
|
||||
}
|
||||
|
||||
func openGeneric(out []byte, g *GCM, nonce, ciphertext, additionalData []byte) error {
|
||||
var H, counter, tagMask [gcmBlockSize]byte
|
||||
aes.EncryptBlockInternal(&g.cipher, H[:], H[:])
|
||||
deriveCounterGeneric(&H, &counter, nonce)
|
||||
gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter)
|
||||
|
||||
tag := ciphertext[len(ciphertext)-g.tagSize:]
|
||||
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
|
||||
|
||||
var expectedTag [gcmTagSize]byte
|
||||
gcmAuthGeneric(expectedTag[:], &H, &tagMask, ciphertext, additionalData)
|
||||
if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
|
||||
return errOpen
|
||||
}
|
||||
|
||||
gcmCounterCryptGeneric(&g.cipher, out, ciphertext, &counter)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deriveCounterGeneric computes the initial GCM counter state from the given nonce.
|
||||
// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with
|
||||
// zeros on entry.
|
||||
func deriveCounterGeneric(H, counter *[gcmBlockSize]byte, nonce []byte) {
|
||||
// GCM has two modes of operation with respect to the initial counter
|
||||
// state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path"
|
||||
// for nonces of other lengths. For a 96-bit nonce, the nonce, along
|
||||
// with a four-byte big-endian counter starting at one, is used
|
||||
// directly as the starting counter. For other nonce sizes, the counter
|
||||
// is computed by passing it through the GHASH function.
|
||||
if len(nonce) == gcmStandardNonceSize {
|
||||
copy(counter[:], nonce)
|
||||
counter[gcmBlockSize-1] = 1
|
||||
} else {
|
||||
lenBlock := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(lenBlock[8:], uint64(len(nonce))*8)
|
||||
ghash(counter, H, nonce, lenBlock)
|
||||
}
|
||||
}
|
||||
|
||||
// gcmCounterCryptGeneric encrypts src using AES in counter mode with 32-bit
|
||||
// wrapping (which is different from AES-CTR) and places the result into out.
|
||||
// counter is the initial value and will be updated with the next value.
|
||||
func gcmCounterCryptGeneric(b *aes.Block, out, src []byte, counter *[gcmBlockSize]byte) {
|
||||
var mask [gcmBlockSize]byte
|
||||
|
||||
for len(src) >= gcmBlockSize {
|
||||
aes.EncryptBlockInternal(b, mask[:], counter[:])
|
||||
gcmInc32(counter)
|
||||
|
||||
subtle.XORBytes(out, src, mask[:])
|
||||
out = out[gcmBlockSize:]
|
||||
src = src[gcmBlockSize:]
|
||||
}
|
||||
|
||||
if len(src) > 0 {
|
||||
aes.EncryptBlockInternal(b, mask[:], counter[:])
|
||||
gcmInc32(counter)
|
||||
subtle.XORBytes(out, src, mask[:])
|
||||
}
|
||||
}
|
||||
|
||||
// gcmInc32 treats the final four bytes of counterBlock as a big-endian value
|
||||
// and increments it.
|
||||
func gcmInc32(counterBlock *[gcmBlockSize]byte) {
|
||||
ctr := counterBlock[len(counterBlock)-4:]
|
||||
binary.BigEndian.PutUint32(ctr, binary.BigEndian.Uint32(ctr)+1)
|
||||
}
|
||||
|
||||
// gcmAuthGeneric calculates GHASH(additionalData, ciphertext), masks the result
|
||||
// with tagMask and writes the result to out.
|
||||
func gcmAuthGeneric(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) {
|
||||
checkGenericIsExpected()
|
||||
lenBlock := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(lenBlock[:8], uint64(len(additionalData))*8)
|
||||
binary.BigEndian.PutUint64(lenBlock[8:], uint64(len(ciphertext))*8)
|
||||
var S [gcmBlockSize]byte
|
||||
ghash(&S, H, additionalData, ciphertext, lenBlock)
|
||||
subtle.XORBytes(out, S[:], tagMask[:])
|
||||
}
|
19
gcm/gcm_noasm.go
Normal file
19
gcm/gcm_noasm.go
Normal file
@ -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)
|
||||
}
|
255
gcm/gcm_nonces.go
Normal file
255
gcm/gcm_nonces.go
Normal file
@ -0,0 +1,255 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/xtls/reality/aes"
|
||||
)
|
||||
|
||||
// SealWithRandomNonce encrypts plaintext to out, and writes a random nonce to
|
||||
// nonce. nonce must be 12 bytes, and out must be 16 bytes longer than plaintext.
|
||||
// out and plaintext may overlap exactly or not at all. additionalData and out
|
||||
// must not overlap.
|
||||
//
|
||||
// This complies with FIPS 140-3 IG C.H Scenario 2.
|
||||
//
|
||||
// Note that this is NOT a [cipher.AEAD].Seal method.
|
||||
// func SealWithRandomNonce(g *GCM, nonce, out, plaintext, additionalData []byte) {
|
||||
// if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize {
|
||||
// panic("crypto/cipher: message too large for GCM")
|
||||
// }
|
||||
// if len(nonce) != gcmStandardNonceSize {
|
||||
// panic("crypto/cipher: incorrect nonce length given to GCMWithRandomNonce")
|
||||
// }
|
||||
// if len(out) != len(plaintext)+gcmTagSize {
|
||||
// panic("crypto/cipher: incorrect output length given to GCMWithRandomNonce")
|
||||
// }
|
||||
// if alias.InexactOverlap(out, plaintext) {
|
||||
// panic("crypto/cipher: invalid buffer overlap of output and input")
|
||||
// }
|
||||
// if alias.AnyOverlap(out, additionalData) {
|
||||
// panic("crypto/cipher: invalid buffer overlap of output and additional data")
|
||||
// }
|
||||
// // fips140.RecordApproved()
|
||||
// drbg.Read(nonce)
|
||||
// seal(out, g, nonce, plaintext, additionalData)
|
||||
// }
|
||||
|
||||
// NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces
|
||||
// the construction of deterministic nonces. The nonce must be 96 bits, the
|
||||
// first 32 bits must be an encoding of the module name, and the last 64 bits
|
||||
// must be a counter.
|
||||
//
|
||||
// This complies with FIPS 140-3 IG C.H Scenario 3.
|
||||
func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) {
|
||||
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GCMWithCounterNonce{g: *g}, nil
|
||||
}
|
||||
|
||||
type GCMWithCounterNonce struct {
|
||||
g GCM
|
||||
ready bool
|
||||
fixedName uint32
|
||||
start uint64
|
||||
next uint64
|
||||
}
|
||||
|
||||
func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize }
|
||||
|
||||
func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize }
|
||||
|
||||
func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
if len(nonce) != gcmStandardNonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
|
||||
counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:])
|
||||
if !g.ready {
|
||||
// The first invocation sets the fixed name encoding and start counter.
|
||||
g.ready = true
|
||||
g.start = counter
|
||||
g.fixedName = binary.BigEndian.Uint32(nonce[:4])
|
||||
}
|
||||
if g.fixedName != binary.BigEndian.Uint32(nonce[:4]) {
|
||||
panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce")
|
||||
}
|
||||
counter -= g.start
|
||||
|
||||
// Ensure the counter is monotonically increasing.
|
||||
if counter == math.MaxUint64 {
|
||||
panic("crypto/cipher: counter wrapped")
|
||||
}
|
||||
if counter < g.next {
|
||||
panic("crypto/cipher: counter decreased")
|
||||
}
|
||||
g.next = counter + 1
|
||||
|
||||
// fips140.RecordApproved()
|
||||
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||
}
|
||||
|
||||
func (g *GCMWithCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
// fips140.RecordApproved()
|
||||
return g.g.Open(dst, nonce, ciphertext, data)
|
||||
}
|
||||
|
||||
// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the
|
||||
// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325,
|
||||
// Section 7.2.1.
|
||||
//
|
||||
// This complies with FIPS 140-3 IG C.H Scenario 1.a.
|
||||
func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) {
|
||||
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GCMForTLS12{g: *g}, nil
|
||||
}
|
||||
|
||||
type GCMForTLS12 struct {
|
||||
g GCM
|
||||
next uint64
|
||||
}
|
||||
|
||||
func (g *GCMForTLS12) NonceSize() int { return gcmStandardNonceSize }
|
||||
|
||||
func (g *GCMForTLS12) Overhead() int { return gcmTagSize }
|
||||
|
||||
func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
if len(nonce) != gcmStandardNonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
|
||||
counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:])
|
||||
|
||||
// Ensure the counter is monotonically increasing.
|
||||
if counter == math.MaxUint64 {
|
||||
panic("crypto/cipher: counter wrapped")
|
||||
}
|
||||
if counter < g.next {
|
||||
panic("crypto/cipher: counter decreased")
|
||||
}
|
||||
g.next = counter + 1
|
||||
|
||||
// fips140.RecordApproved()
|
||||
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||
}
|
||||
|
||||
func (g *GCMForTLS12) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
// fips140.RecordApproved()
|
||||
return g.g.Open(dst, nonce, ciphertext, data)
|
||||
}
|
||||
|
||||
// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the
|
||||
// construction of nonces as specified in RFC 8446, Section 5.3.
|
||||
func NewGCMForTLS13(cipher *aes.Block) (*GCMForTLS13, error) {
|
||||
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GCMForTLS13{g: *g}, nil
|
||||
}
|
||||
|
||||
type GCMForTLS13 struct {
|
||||
g GCM
|
||||
ready bool
|
||||
mask uint64
|
||||
next uint64
|
||||
}
|
||||
|
||||
func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize }
|
||||
|
||||
func (g *GCMForTLS13) Overhead() int { return gcmTagSize }
|
||||
|
||||
func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
if len(nonce) != gcmStandardNonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
|
||||
counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:])
|
||||
if !g.ready {
|
||||
// In the first call, the counter is zero, so we learn the XOR mask.
|
||||
g.ready = true
|
||||
g.mask = counter
|
||||
}
|
||||
counter ^= g.mask
|
||||
|
||||
// Ensure the counter is monotonically increasing.
|
||||
if counter == math.MaxUint64 {
|
||||
panic("crypto/cipher: counter wrapped")
|
||||
}
|
||||
if counter < g.next {
|
||||
panic("crypto/cipher: counter decreased")
|
||||
}
|
||||
g.next = counter + 1
|
||||
|
||||
// fips140.RecordApproved()
|
||||
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||
}
|
||||
|
||||
func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
// fips140.RecordApproved()
|
||||
return g.g.Open(dst, nonce, ciphertext, data)
|
||||
}
|
||||
|
||||
// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the
|
||||
// construction of nonces as specified in RFC 5647.
|
||||
//
|
||||
// This complies with FIPS 140-3 IG C.H Scenario 1.d.
|
||||
func NewGCMForSSH(cipher *aes.Block) (*GCMForSSH, error) {
|
||||
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GCMForSSH{g: *g}, nil
|
||||
}
|
||||
|
||||
type GCMForSSH struct {
|
||||
g GCM
|
||||
ready bool
|
||||
start uint64
|
||||
next uint64
|
||||
}
|
||||
|
||||
func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize }
|
||||
|
||||
func (g *GCMForSSH) Overhead() int { return gcmTagSize }
|
||||
|
||||
func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
if len(nonce) != gcmStandardNonceSize {
|
||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||
}
|
||||
|
||||
counter := binary.BigEndian.Uint64(nonce[len(nonce)-8:])
|
||||
if !g.ready {
|
||||
// In the first call we learn the start value.
|
||||
g.ready = true
|
||||
g.start = counter
|
||||
}
|
||||
counter -= g.start
|
||||
|
||||
// Ensure the counter is monotonically increasing.
|
||||
if counter == math.MaxUint64 {
|
||||
panic("crypto/cipher: counter wrapped")
|
||||
}
|
||||
if counter < g.next {
|
||||
panic("crypto/cipher: counter decreased")
|
||||
}
|
||||
g.next = counter + 1
|
||||
|
||||
// fips140.RecordApproved()
|
||||
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||
}
|
||||
|
||||
func (g *GCMForSSH) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
// fips140.RecordApproved()
|
||||
return g.g.Open(dst, nonce, ciphertext, data)
|
||||
}
|
162
gcm/ghash.go
Normal file
162
gcm/ghash.go
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM
|
||||
// standard and make binary.BigEndian suitable for marshaling these values, the
|
||||
// bits are stored in big endian order. For example:
|
||||
//
|
||||
// the coefficient of x⁰ can be obtained by v.low >> 63.
|
||||
// the coefficient of x⁶³ can be obtained by v.low & 1.
|
||||
// the coefficient of x⁶⁴ can be obtained by v.high >> 63.
|
||||
// the coefficient of x¹²⁷ can be obtained by v.high & 1.
|
||||
type gcmFieldElement struct {
|
||||
low, high uint64
|
||||
}
|
||||
|
||||
// GHASH is exposed to allow crypto/cipher to implement non-AES GCM modes.
|
||||
// It is not allowed as a stand-alone operation in FIPS mode because it
|
||||
// is not ACVP tested.
|
||||
func GHASH(key *[16]byte, inputs ...[]byte) []byte {
|
||||
// fips140.RecordNonApproved()
|
||||
var out [gcmBlockSize]byte
|
||||
ghash(&out, key, inputs...)
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// ghash is a variable-time generic implementation of GHASH, which shouldn't
|
||||
// be used on any architecture with hardware support for AES-GCM.
|
||||
//
|
||||
// Each input is zero-padded to 128-bit before being absorbed.
|
||||
func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) {
|
||||
// productTable contains the first sixteen powers of the key, H.
|
||||
// However, they are in bit reversed order.
|
||||
var productTable [16]gcmFieldElement
|
||||
|
||||
// We precompute 16 multiples of H. However, when we do lookups
|
||||
// into this table we'll be using bits from a field element and
|
||||
// therefore the bits will be in the reverse order. So normally one
|
||||
// would expect, say, 4*H to be in index 4 of the table but due to
|
||||
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
||||
x := gcmFieldElement{
|
||||
binary.BigEndian.Uint64(H[:8]),
|
||||
binary.BigEndian.Uint64(H[8:]),
|
||||
}
|
||||
productTable[reverseBits(1)] = x
|
||||
|
||||
for i := 2; i < 16; i += 2 {
|
||||
productTable[reverseBits(i)] = ghashDouble(&productTable[reverseBits(i/2)])
|
||||
productTable[reverseBits(i+1)] = ghashAdd(&productTable[reverseBits(i)], &x)
|
||||
}
|
||||
|
||||
var y gcmFieldElement
|
||||
for _, input := range inputs {
|
||||
ghashUpdate(&productTable, &y, input)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(out[:], y.low)
|
||||
binary.BigEndian.PutUint64(out[8:], y.high)
|
||||
}
|
||||
|
||||
// reverseBits reverses the order of the bits of 4-bit number in i.
|
||||
func reverseBits(i int) int {
|
||||
i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
|
||||
i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
|
||||
return i
|
||||
}
|
||||
|
||||
// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum.
|
||||
func ghashAdd(x, y *gcmFieldElement) gcmFieldElement {
|
||||
// Addition in a characteristic 2 field is just XOR.
|
||||
return gcmFieldElement{x.low ^ y.low, x.high ^ y.high}
|
||||
}
|
||||
|
||||
// ghashDouble returns the result of doubling an element of GF(2¹²⁸).
|
||||
func ghashDouble(x *gcmFieldElement) (double gcmFieldElement) {
|
||||
msbSet := x.high&1 == 1
|
||||
|
||||
// Because of the bit-ordering, doubling is actually a right shift.
|
||||
double.high = x.high >> 1
|
||||
double.high |= x.low << 63
|
||||
double.low = x.low >> 1
|
||||
|
||||
// If the most-significant bit was set before shifting then it,
|
||||
// conceptually, becomes a term of x^128. This is greater than the
|
||||
// irreducible polynomial so the result has to be reduced. The
|
||||
// irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to
|
||||
// eliminate the term at x^128 which also means subtracting the other
|
||||
// four terms. In characteristic 2 fields, subtraction == addition ==
|
||||
// XOR.
|
||||
if msbSet {
|
||||
double.low ^= 0xe100000000000000
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var ghashReductionTable = []uint16{
|
||||
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
|
||||
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
|
||||
}
|
||||
|
||||
// ghashMul sets y to y*H, where H is the GCM key, fixed during New.
|
||||
func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) {
|
||||
var z gcmFieldElement
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
word := y.high
|
||||
if i == 1 {
|
||||
word = y.low
|
||||
}
|
||||
|
||||
// Multiplication works by multiplying z by 16 and adding in
|
||||
// one of the precomputed multiples of H.
|
||||
for j := 0; j < 64; j += 4 {
|
||||
msw := z.high & 0xf
|
||||
z.high >>= 4
|
||||
z.high |= z.low << 60
|
||||
z.low >>= 4
|
||||
z.low ^= uint64(ghashReductionTable[msw]) << 48
|
||||
|
||||
// the values in |table| are ordered for little-endian bit
|
||||
// positions. See the comment in New.
|
||||
t := productTable[word&0xf]
|
||||
|
||||
z.low ^= t.low
|
||||
z.high ^= t.high
|
||||
word >>= 4
|
||||
}
|
||||
}
|
||||
|
||||
*y = z
|
||||
}
|
||||
|
||||
// updateBlocks extends y with more polynomial terms from blocks, based on
|
||||
// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks.
|
||||
func updateBlocks(productTable *[16]gcmFieldElement, y *gcmFieldElement, blocks []byte) {
|
||||
for len(blocks) > 0 {
|
||||
y.low ^= binary.BigEndian.Uint64(blocks)
|
||||
y.high ^= binary.BigEndian.Uint64(blocks[8:])
|
||||
ghashMul(productTable, y)
|
||||
blocks = blocks[gcmBlockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
// ghashUpdate extends y with more polynomial terms from data. If data is not a
|
||||
// multiple of gcmBlockSize bytes long then the remainder is zero padded.
|
||||
func ghashUpdate(productTable *[16]gcmFieldElement, y *gcmFieldElement, data []byte) {
|
||||
fullBlocks := (len(data) >> 4) << 4
|
||||
updateBlocks(productTable, y, data[:fullBlocks])
|
||||
|
||||
if len(data) != fullBlocks {
|
||||
var partialBlock [gcmBlockSize]byte
|
||||
copy(partialBlock[:], data[fullBlocks:])
|
||||
updateBlocks(productTable, y, partialBlock[:])
|
||||
}
|
||||
}
|
8
go.mod
8
go.mod
@ -1,9 +1,9 @@
|
||||
module github.com/xtls/reality
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/sys v0.24.0
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/sys v0.33.0
|
||||
)
|
||||
|
10
go.sum
10
go.sum
@ -1,6 +1,16 @@
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/mlkem"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
@ -19,11 +20,13 @@ import (
|
||||
"hash"
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/reality/fips140tls"
|
||||
"github.com/xtls/reality/hpke"
|
||||
"github.com/xtls/reality/mlkem768"
|
||||
"github.com/xtls/reality/tls13"
|
||||
)
|
||||
|
||||
type clientHandshakeState struct {
|
||||
@ -40,7 +43,7 @@ type clientHandshakeState struct {
|
||||
|
||||
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
|
||||
|
||||
func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echContext, error) {
|
||||
func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) {
|
||||
config := c.config
|
||||
if len(config.ServerName) == 0 && !config.InsecureSkipVerify {
|
||||
return nil, nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
|
||||
@ -91,24 +94,12 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
hello.secureRenegotiation = c.clientFinished[:]
|
||||
}
|
||||
|
||||
preferenceOrder := cipherSuitesPreferenceOrder
|
||||
if !hasAESGCMHardwareSupport {
|
||||
preferenceOrder = cipherSuitesPreferenceOrderNoAES
|
||||
}
|
||||
configCipherSuites := config.cipherSuites()
|
||||
hello.cipherSuites = make([]uint16, 0, len(configCipherSuites))
|
||||
|
||||
for _, suiteId := range preferenceOrder {
|
||||
suite := mutualCipherSuite(configCipherSuites, suiteId)
|
||||
if suite == nil {
|
||||
continue
|
||||
}
|
||||
// Don't advertise TLS 1.2-only cipher suites unless
|
||||
// we're attempting TLS 1.2.
|
||||
if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
|
||||
continue
|
||||
}
|
||||
hello.cipherSuites = append(hello.cipherSuites, suiteId)
|
||||
hello.cipherSuites = config.cipherSuites(hasAESGCMHardwareSupport)
|
||||
// Don't advertise TLS 1.2-only cipher suites unless we're attempting TLS 1.2.
|
||||
if maxVersion < VersionTLS12 {
|
||||
hello.cipherSuites = slices.DeleteFunc(hello.cipherSuites, func(id uint16) bool {
|
||||
return cipherSuiteByID(id).flags&suiteTLS12 != 0
|
||||
})
|
||||
}
|
||||
|
||||
_, err := io.ReadFull(config.rand(), hello.random)
|
||||
@ -141,8 +132,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
if len(hello.supportedVersions) == 1 {
|
||||
hello.cipherSuites = nil
|
||||
}
|
||||
if needFIPS() {
|
||||
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
|
||||
if fips140tls.Required() {
|
||||
hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...)
|
||||
} else if hasAESGCMHardwareSupport {
|
||||
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
|
||||
} else {
|
||||
@ -154,27 +145,31 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
}
|
||||
curveID := hello.supportedCurves[0]
|
||||
keyShareKeys = &keySharePrivateKeys{curveID: curveID}
|
||||
if curveID == x25519Kyber768Draft00 {
|
||||
// Note that if X25519MLKEM768 is supported, it will be first because
|
||||
// the preference order is fixed.
|
||||
if curveID == X25519MLKEM768 {
|
||||
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
seed := make([]byte, mlkem768.SeedSize)
|
||||
seed := make([]byte, mlkem.SeedSize)
|
||||
if _, err := io.ReadFull(config.rand(), seed); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keyShareKeys.kyber, err = mlkem768.NewKeyFromSeed(seed)
|
||||
keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// For draft-tls-westerbaan-xyber768d00-03, we send both a hybrid
|
||||
// and a standard X25519 key share, since most servers will only
|
||||
// support the latter. We reuse the same X25519 ephemeral key for
|
||||
// both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
|
||||
mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes()
|
||||
x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes()
|
||||
hello.keyShares = []keyShare{
|
||||
{group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(),
|
||||
keyShareKeys.kyber.EncapsulationKey()...)},
|
||||
{group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()},
|
||||
{group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)},
|
||||
}
|
||||
// If both X25519MLKEM768 and X25519 are supported, we send both key
|
||||
// shares (as a fallback) and we reuse the same X25519 ephemeral
|
||||
// key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
|
||||
if slices.Contains(hello.supportedCurves, X25519) {
|
||||
hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey})
|
||||
}
|
||||
} else {
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
@ -199,7 +194,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
hello.quicTransportParameters = p
|
||||
}
|
||||
|
||||
var ech *echContext
|
||||
var ech *echClientContext
|
||||
if c.config.EncryptedClientHelloConfigList != nil {
|
||||
if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
|
||||
return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
|
||||
@ -215,7 +210,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
if echConfig == nil {
|
||||
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
|
||||
}
|
||||
ech = &echContext{config: echConfig}
|
||||
ech = &echClientContext{config: echConfig}
|
||||
hello.encryptedClientHello = []byte{1} // indicate inner hello
|
||||
// We need to explicitly set these 1.2 fields to nil, as we do not
|
||||
// marshal them when encoding the inner hello, otherwise transcripts
|
||||
@ -244,7 +239,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
|
||||
return hello, keyShareKeys, ech, nil
|
||||
}
|
||||
|
||||
type echContext struct {
|
||||
type echClientContext struct {
|
||||
config *echConfig
|
||||
hpkeContext *hpke.Sender
|
||||
encapsulatedKey []byte
|
||||
@ -253,6 +248,7 @@ type echContext struct {
|
||||
kdfID uint16
|
||||
aeadID uint16
|
||||
echRejected bool
|
||||
retryConfigs []byte
|
||||
}
|
||||
|
||||
func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
||||
@ -263,6 +259,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
||||
// This may be a renegotiation handshake, in which case some fields
|
||||
// need to be reset.
|
||||
c.didResume = false
|
||||
c.curveID = 0
|
||||
|
||||
hello, keyShareKeys, ech, err := c.makeClientHello()
|
||||
if err != nil {
|
||||
@ -325,7 +322,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
||||
if err := transcriptMsg(hello, transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
|
||||
earlyTrafficSecret := earlySecret.ClientEarlyTrafficSecret(transcript)
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
|
||||
}
|
||||
|
||||
@ -384,7 +381,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
func (c *Conn) loadSession(hello *clientHelloMsg) (
|
||||
session *SessionState, earlySecret, binderKey []byte, err error) {
|
||||
session *SessionState, earlySecret *tls13.EarlySecret, binderKey []byte, err error) {
|
||||
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
@ -456,6 +453,11 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
// FIPS 140-3 requires the use of Extended Master Secret.
|
||||
if !session.extMasterSecret && fips140tls.Required() {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
hello.sessionTicket = session.ticket
|
||||
return
|
||||
}
|
||||
@ -511,8 +513,8 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
|
||||
hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())}
|
||||
|
||||
// Compute the PSK binders. See RFC 8446, Section 4.2.11.2.
|
||||
earlySecret = cipherSuite.extract(session.secret, nil)
|
||||
binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil)
|
||||
earlySecret = tls13.NewEarlySecret(cipherSuite.hash.New, session.secret)
|
||||
binderKey = earlySecret.ResumptionBinderKey()
|
||||
transcript := cipherSuite.hash.New()
|
||||
if err := computeAndUpdatePSK(hello, binderKey, transcript, cipherSuite.finishedHash); err != nil {
|
||||
return nil, nil, nil, err
|
||||
@ -546,6 +548,19 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error {
|
||||
func (hs *clientHandshakeState) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
// If we did not load a session (hs.session == nil), but we did set a
|
||||
// session ID in the transmitted client hello (hs.hello.sessionId != nil),
|
||||
// it means we tried to negotiate TLS 1.3 and sent a random session ID as a
|
||||
// compatibility measure (see RFC 8446, Section 4.1.2).
|
||||
//
|
||||
// Since we're now handshaking for TLS 1.2, if the server echoed the
|
||||
// transmitted ID back to us, we know mischief is afoot: the session ID
|
||||
// was random and can't possibly be recognized by the server.
|
||||
if hs.session == nil && hs.hello.sessionId != nil && bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server echoed TLS 1.3 compatibility session ID in TLS 1.2")
|
||||
}
|
||||
|
||||
isResume, err := hs.processServerHello()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -702,7 +717,7 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
||||
if ok {
|
||||
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
|
||||
if err != nil {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return err
|
||||
}
|
||||
if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ {
|
||||
@ -766,6 +781,10 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
||||
hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret,
|
||||
hs.finishedHash.Sum())
|
||||
} else {
|
||||
if fips140tls.Required() {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: FIPS 140-3 requires the use of Extended Master Secret")
|
||||
}
|
||||
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret,
|
||||
hs.hello.random, hs.serverHello.random)
|
||||
}
|
||||
@ -863,10 +882,23 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
|
||||
}
|
||||
|
||||
if hs.serverHello.compressionMethod != compressionNone {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return false, errors.New("tls: server selected unsupported compression format")
|
||||
}
|
||||
|
||||
supportsPointFormat := false
|
||||
offeredNonCompressedFormat := false
|
||||
for _, format := range hs.serverHello.supportedPoints {
|
||||
if format == pointFormatUncompressed {
|
||||
supportsPointFormat = true
|
||||
} else {
|
||||
offeredNonCompressedFormat = true
|
||||
}
|
||||
}
|
||||
if !supportsPointFormat && offeredNonCompressedFormat {
|
||||
return false, errors.New("tls: server offered only incompatible point formats")
|
||||
}
|
||||
|
||||
if c.handshakes == 0 && hs.serverHello.secureRenegotiationSupported {
|
||||
c.secureRenegotiation = true
|
||||
if len(hs.serverHello.secureRenegotiation) != 0 {
|
||||
@ -921,16 +953,17 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
|
||||
c.verifiedChains = hs.session.verifiedChains
|
||||
c.ocspResponse = hs.session.ocspResponse
|
||||
// Let the ServerHello SCTs override the session SCTs from the original
|
||||
// connection, if any are provided
|
||||
// connection, if any are provided.
|
||||
if len(c.scts) == 0 && len(hs.session.scts) != 0 {
|
||||
c.scts = hs.session.scts
|
||||
}
|
||||
c.curveID = hs.session.curveID
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// checkALPN ensure that the server's choice of ALPN protocol is compatible with
|
||||
// the protocols that we advertised in the Client Hello.
|
||||
// the protocols that we advertised in the ClientHello.
|
||||
func checkALPN(clientProtos []string, serverProto string, quic bool) error {
|
||||
if serverProto == "" {
|
||||
if quic && len(clientProtos) > 0 {
|
||||
@ -1072,7 +1105,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
|
||||
for i, asn1Data := range certificates {
|
||||
cert, err := globalCertCache.newCert(asn1Data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: failed to parse certificate from server: " + err.Error())
|
||||
}
|
||||
if cert.cert.PublicKeyAlgorithm == x509.RSA {
|
||||
@ -1104,8 +1137,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
var err error
|
||||
c.verifiedChains, err = certs[0].Verify(opts)
|
||||
chains, err := certs[0].Verify(opts)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
}
|
||||
|
||||
c.verifiedChains, err = fipsAllowedChains(chains)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
@ -1122,8 +1160,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
var err error
|
||||
c.verifiedChains, err = certs[0].Verify(opts)
|
||||
chains, err := certs[0].Verify(opts)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
}
|
||||
|
||||
c.verifiedChains, err = fipsAllowedChains(chains)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
|
@ -8,7 +8,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"crypto/mlkem"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
@ -16,7 +18,7 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/reality/mlkem768"
|
||||
"github.com/xtls/reality/tls13"
|
||||
)
|
||||
|
||||
type clientHandshakeStateTLS13 struct {
|
||||
@ -27,7 +29,7 @@ type clientHandshakeStateTLS13 struct {
|
||||
keyShareKeys *keySharePrivateKeys
|
||||
|
||||
session *SessionState
|
||||
earlySecret []byte
|
||||
earlySecret *tls13.EarlySecret
|
||||
binderKey []byte
|
||||
|
||||
certReq *certificateRequestMsgTLS13
|
||||
@ -35,9 +37,9 @@ type clientHandshakeStateTLS13 struct {
|
||||
sentDummyCCS bool
|
||||
suite *cipherSuiteTLS13
|
||||
transcript hash.Hash
|
||||
masterSecret []byte
|
||||
masterSecret *tls13.MasterSecret
|
||||
trafficSecret []byte // client_application_traffic_secret_0
|
||||
echContext *echContext
|
||||
echContext *echClientContext
|
||||
}
|
||||
|
||||
// handshake requires hs.c, hs.hello, hs.serverHello, hs.keyShareKeys, and,
|
||||
@ -83,18 +85,18 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
}
|
||||
}
|
||||
|
||||
var echRetryConfigList []byte
|
||||
if hs.echContext != nil {
|
||||
confTranscript := cloneHash(hs.echContext.innerTranscript, hs.suite.hash)
|
||||
confTranscript.Write(hs.serverHello.original[:30])
|
||||
confTranscript.Write(make([]byte, 8))
|
||||
confTranscript.Write(hs.serverHello.original[38:])
|
||||
acceptConfirmation := hs.suite.expandLabel(
|
||||
hs.suite.extract(hs.echContext.innerHello.random, nil),
|
||||
"ech accept confirmation",
|
||||
confTranscript.Sum(nil),
|
||||
8,
|
||||
)
|
||||
h := hs.suite.hash.New
|
||||
prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
acceptConfirmation := tls13.ExpandLabel(h, prk, "ech accept confirmation", confTranscript.Sum(nil), 8)
|
||||
if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.random[len(hs.serverHello.random)-8:]) == 1 {
|
||||
hs.hello = hs.echContext.innerHello
|
||||
c.serverName = c.config.ServerName
|
||||
@ -103,7 +105,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
|
||||
if hs.serverHello.encryptedClientHello != nil {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: unexpected encrypted_client_hello extension in server hello despite ECH being accepted")
|
||||
return errors.New("tls: unexpected encrypted client hello extension in server hello despite ECH being accepted")
|
||||
}
|
||||
|
||||
if hs.hello.serverName == "" && hs.serverHello.serverNameAck {
|
||||
@ -112,9 +114,6 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
}
|
||||
} else {
|
||||
hs.echContext.echRejected = true
|
||||
// If the server sent us retry configs, we'll return these to
|
||||
// the user so they can update their Config.
|
||||
echRetryConfigList = hs.serverHello.encryptedClientHello
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +152,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
|
||||
if hs.echContext != nil && hs.echContext.echRejected {
|
||||
c.sendAlert(alertECHRequired)
|
||||
return &ECHRejectionError{echRetryConfigList}
|
||||
return &ECHRejectionError{hs.echContext.retryConfigs}
|
||||
}
|
||||
|
||||
c.isHandshakeComplete.Store(true)
|
||||
@ -198,8 +197,8 @@ func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error {
|
||||
}
|
||||
|
||||
if hs.serverHello.compressionMethod != compressionNone {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported compression format")
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: server sent non-zero legacy TLS compression method")
|
||||
}
|
||||
|
||||
selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite)
|
||||
@ -266,12 +265,13 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
copy(hrrHello, hs.serverHello.original)
|
||||
hrrHello = bytes.Replace(hrrHello, hs.serverHello.encryptedClientHello, make([]byte, 8), 1)
|
||||
confTranscript.Write(hrrHello)
|
||||
acceptConfirmation := hs.suite.expandLabel(
|
||||
hs.suite.extract(hs.echContext.innerHello.random, nil),
|
||||
"hrr ech accept confirmation",
|
||||
confTranscript.Sum(nil),
|
||||
8,
|
||||
)
|
||||
h := hs.suite.hash.New
|
||||
prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
acceptConfirmation := tls13.ExpandLabel(h, prk, "hrr ech accept confirmation", confTranscript.Sum(nil), 8)
|
||||
if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.encryptedClientHello) == 1 {
|
||||
hello = hs.echContext.innerHello
|
||||
c.serverName = c.config.ServerName
|
||||
@ -286,7 +286,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
} else if hs.serverHello.encryptedClientHello != nil {
|
||||
// Unsolicited ECH extension should be rejected
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: unexpected ECH extension in serverHello")
|
||||
return errors.New("tls: unexpected encrypted client hello extension in serverHello")
|
||||
}
|
||||
|
||||
// The only HelloRetryRequest extensions we support are key_share and
|
||||
@ -320,12 +320,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||
}
|
||||
// Note: we don't support selecting X25519Kyber768Draft00 in a HRR,
|
||||
// because we currently only support it at all when CurvePreferences is
|
||||
// empty, which will cause us to also send a key share for it.
|
||||
// Note: we don't support selecting X25519MLKEM768 in a HRR, because it
|
||||
// is currently first in preference order, so if it's enabled we'll
|
||||
// always send a key share for it.
|
||||
//
|
||||
// This will have to change once we support selecting hybrid KEMs
|
||||
// without sending key shares for them.
|
||||
// This will have to change once we support multiple hybrid KEMs.
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
@ -478,12 +477,12 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
c := hs.c
|
||||
|
||||
ecdhePeerData := hs.serverHello.serverShare.data
|
||||
if hs.serverHello.serverShare.group == x25519Kyber768Draft00 {
|
||||
if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize {
|
||||
if hs.serverHello.serverShare.group == X25519MLKEM768 {
|
||||
if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
return errors.New("tls: invalid server X25519MLKEM768 key share")
|
||||
}
|
||||
ecdhePeerData = hs.serverHello.serverShare.data[:x25519PublicKeySize]
|
||||
ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:]
|
||||
}
|
||||
peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData)
|
||||
if err != nil {
|
||||
@ -495,33 +494,30 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
if hs.serverHello.serverShare.group == x25519Kyber768Draft00 {
|
||||
if hs.keyShareKeys.kyber == nil {
|
||||
if hs.serverHello.serverShare.group == X25519MLKEM768 {
|
||||
if hs.keyShareKeys.mlkem == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
ciphertext := hs.serverHello.serverShare.data[x25519PublicKeySize:]
|
||||
kyberShared, err := kyberDecapsulate(hs.keyShareKeys.kyber, ciphertext)
|
||||
ciphertext := hs.serverHello.serverShare.data[:mlkem.CiphertextSize768]
|
||||
mlkemShared, err := hs.keyShareKeys.mlkem.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid Kyber server key share")
|
||||
return errors.New("tls: invalid X25519MLKEM768 server key share")
|
||||
}
|
||||
sharedKey = append(sharedKey, kyberShared...)
|
||||
sharedKey = append(mlkemShared, sharedKey...)
|
||||
}
|
||||
c.curveID = hs.serverHello.serverShare.group
|
||||
|
||||
earlySecret := hs.earlySecret
|
||||
if !hs.usingPSK {
|
||||
earlySecret = hs.suite.extract(nil, nil)
|
||||
earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil)
|
||||
}
|
||||
|
||||
handshakeSecret := hs.suite.extract(sharedKey,
|
||||
hs.suite.deriveSecret(earlySecret, "derived", nil))
|
||||
handshakeSecret := earlySecret.HandshakeSecret(sharedKey)
|
||||
|
||||
clientSecret := hs.suite.deriveSecret(handshakeSecret,
|
||||
clientHandshakeTrafficLabel, hs.transcript)
|
||||
clientSecret := handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
|
||||
serverSecret := hs.suite.deriveSecret(handshakeSecret,
|
||||
serverHandshakeTrafficLabel, hs.transcript)
|
||||
serverSecret := handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
@ -543,8 +539,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.masterSecret = hs.suite.extract(nil,
|
||||
hs.suite.deriveSecret(handshakeSecret, "derived", nil))
|
||||
hs.masterSecret = handshakeSecret.MasterSecret()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -604,9 +599,13 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
|
||||
return errors.New("tls: server accepted 0-RTT with the wrong ALPN")
|
||||
}
|
||||
}
|
||||
if hs.echContext != nil && !hs.echContext.echRejected && encryptedExtensions.echRetryConfigs != nil {
|
||||
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 ECH retry configs after accepting ECH")
|
||||
return errors.New("tls: server sent encrypted client hello retry configs after accepting encrypted client hello")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -732,10 +731,8 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error {
|
||||
|
||||
// Derive secrets that take context through the server Finished.
|
||||
|
||||
hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
clientApplicationTrafficLabel, hs.transcript)
|
||||
serverSecret := hs.suite.deriveSecret(hs.masterSecret,
|
||||
serverApplicationTrafficLabel, hs.transcript)
|
||||
hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript)
|
||||
serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
|
||||
|
||||
err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)
|
||||
@ -842,8 +839,7 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
|
||||
|
||||
if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
|
||||
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
resumptionLabel, hs.transcript)
|
||||
c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript)
|
||||
}
|
||||
|
||||
if c.quic != nil {
|
||||
@ -876,6 +872,11 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
||||
return errors.New("tls: received a session ticket with invalid lifetime")
|
||||
}
|
||||
|
||||
if len(msg.label) == 0 {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: received a session ticket with empty opaque ticket label")
|
||||
}
|
||||
|
||||
// RFC 9001, Section 4.6.1
|
||||
if c.quic != nil && msg.maxEarlyData != 0 && msg.maxEarlyData != 0xffffffff {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
@ -887,7 +888,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
psk := cipherSuite.expandLabel(c.resumptionSecret, "resumption",
|
||||
psk := tls13.ExpandLabel(cipherSuite.hash.New, c.resumptionSecret, "resumption",
|
||||
msg.nonce, cipherSuite.hash.Size())
|
||||
|
||||
session := c.sessionState()
|
||||
|
@ -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
|
||||
|
@ -18,6 +18,8 @@ import (
|
||||
"hash"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/reality/fips140tls"
|
||||
)
|
||||
|
||||
// serverHandshakeState contains details of a server handshake in progress.
|
||||
@ -40,7 +42,7 @@ type serverHandshakeState struct {
|
||||
|
||||
// serverHandshake performs a TLS handshake as a server.
|
||||
func (c *Conn) serverHandshake(ctx context.Context) error {
|
||||
clientHello, err := c.readClientHello(ctx)
|
||||
clientHello, ech, err := c.readClientHello(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -50,6 +52,7 @@ func (c *Conn) serverHandshake(ctx context.Context) error {
|
||||
c: c,
|
||||
ctx: ctx,
|
||||
clientHello: clientHello,
|
||||
echContext: ech,
|
||||
}
|
||||
return hs.handshake()
|
||||
}
|
||||
@ -130,17 +133,27 @@ func (hs *serverHandshakeState) handshake() error {
|
||||
}
|
||||
|
||||
// readClientHello reads a ClientHello message and selects the protocol version.
|
||||
func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
|
||||
func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServerContext, error) {
|
||||
// clientHelloMsg is included in the transcript, but we haven't initialized
|
||||
// it yet. The respective handshake functions will record it themselves.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
clientHello, ok := msg.(*clientHelloMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return nil, unexpectedMessageError(clientHello, msg)
|
||||
return nil, nil, unexpectedMessageError(clientHello, msg)
|
||||
}
|
||||
|
||||
// ECH processing has to be done before we do any other negotiation based on
|
||||
// the contents of the client hello, since we may swap it out completely.
|
||||
var ech *echServerContext
|
||||
if len(clientHello.encryptedClientHello) != 0 {
|
||||
clientHello, ech, err = c.processECHClientHello(clientHello)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var configForClient *Config
|
||||
@ -149,7 +162,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
|
||||
chi := clientHelloInfo(ctx, c, clientHello)
|
||||
if configForClient, err = c.config.GetConfigForClient(chi); err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
} else if configForClient != nil {
|
||||
c.config = configForClient
|
||||
}
|
||||
@ -157,19 +170,39 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
|
||||
c.ticketKeys = originalConfig.ticketKeys(configForClient)
|
||||
|
||||
clientVersions := clientHello.supportedVersions
|
||||
if len(clientHello.supportedVersions) == 0 {
|
||||
if clientHello.vers >= VersionTLS13 && len(clientVersions) == 0 {
|
||||
// RFC 8446 4.2.1 indicates when the supported_versions extension is not sent,
|
||||
// compatible servers MUST negotiate TLS 1.2 or earlier if supported, even
|
||||
// if the client legacy version is TLS 1.3 or later.
|
||||
//
|
||||
// Since we reject empty extensionSupportedVersions in the client hello unmarshal
|
||||
// finding the supportedVersions empty indicates the extension was not present.
|
||||
clientVersions = supportedVersionsFromMax(VersionTLS12)
|
||||
} else if len(clientVersions) == 0 {
|
||||
clientVersions = supportedVersionsFromMax(clientHello.vers)
|
||||
}
|
||||
c.vers, ok = c.config.mutualVersion(roleServer, clientVersions)
|
||||
if !ok {
|
||||
c.sendAlert(alertProtocolVersion)
|
||||
return nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
|
||||
return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
|
||||
}
|
||||
c.haveVers = true
|
||||
c.in.version = c.vers
|
||||
c.out.version = c.vers
|
||||
|
||||
return clientHello, nil
|
||||
// This check reflects some odd specification implied behavior. Client-facing servers
|
||||
// are supposed to reject hellos with outer ECH and inner ECH that offers 1.2, but
|
||||
// backend servers are allowed to accept hellos with inner ECH that offer 1.2, since
|
||||
// they cannot expect client-facing servers to behave properly. Since we act as both
|
||||
// a client-facing and backend server, we only enforce 1.3 being negotiated if we
|
||||
// saw a hello with outer ECH first. The spec probably should've made this an error,
|
||||
// but it didn't, and this matches the boringssl behavior.
|
||||
if c.vers != VersionTLS13 && (ech != nil && !ech.inner) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, nil, errors.New("tls: Encrypted Client Hello cannot be used pre-TLS 1.3")
|
||||
}
|
||||
|
||||
return clientHello, ech, nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) processClientHello() error {
|
||||
@ -243,7 +276,11 @@ func (hs *serverHandshakeState) processClientHello() error {
|
||||
hs.hello.scts = hs.cert.SignedCertificateTimestamps
|
||||
}
|
||||
|
||||
hs.ecdheOk = supportsECDHE(c.config, c.vers, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints)
|
||||
hs.ecdheOk, err = supportsECDHE(c.config, c.vers, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints)
|
||||
if err != nil {
|
||||
c.sendAlert(alertMissingExtension)
|
||||
return err
|
||||
}
|
||||
|
||||
if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 {
|
||||
// Although omitting the ec_point_formats extension is permitted, some
|
||||
@ -314,7 +351,7 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro
|
||||
|
||||
// supportsECDHE returns whether ECDHE key exchanges can be used with this
|
||||
// pre-TLS 1.3 client.
|
||||
func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, supportedPoints []uint8) bool {
|
||||
func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, supportedPoints []uint8) (bool, error) {
|
||||
supportsCurve := false
|
||||
for _, curve := range supportedCurves {
|
||||
if c.supportsCurve(version, curve) {
|
||||
@ -324,10 +361,12 @@ func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, support
|
||||
}
|
||||
|
||||
supportsPointFormat := false
|
||||
offeredNonCompressedFormat := false
|
||||
for _, pointFormat := range supportedPoints {
|
||||
if pointFormat == pointFormatUncompressed {
|
||||
supportsPointFormat = true
|
||||
break
|
||||
} else {
|
||||
offeredNonCompressedFormat = true
|
||||
}
|
||||
}
|
||||
// Per RFC 8422, Section 5.1.2, if the Supported Point Formats extension is
|
||||
@ -336,34 +375,23 @@ func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, support
|
||||
// the parser. See https://go.dev/issue/49126.
|
||||
if len(supportedPoints) == 0 {
|
||||
supportsPointFormat = true
|
||||
} else if offeredNonCompressedFormat && !supportsPointFormat {
|
||||
return false, errors.New("tls: client offered only incompatible point formats")
|
||||
}
|
||||
|
||||
return supportsCurve && supportsPointFormat
|
||||
return supportsCurve && supportsPointFormat, nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) pickCipherSuite() error {
|
||||
c := hs.c
|
||||
|
||||
preferenceOrder := cipherSuitesPreferenceOrder
|
||||
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
|
||||
preferenceOrder = cipherSuitesPreferenceOrderNoAES
|
||||
}
|
||||
|
||||
configCipherSuites := c.config.cipherSuites()
|
||||
preferenceList := make([]uint16, 0, len(configCipherSuites))
|
||||
for _, suiteID := range preferenceOrder {
|
||||
for _, id := range configCipherSuites {
|
||||
if id == suiteID {
|
||||
preferenceList = append(preferenceList, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
preferenceList := c.config.cipherSuites(isAESGCMPreferred(hs.clientHello.cipherSuites))
|
||||
|
||||
hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk)
|
||||
if hs.suite == nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no cipher suite supported by both client and server")
|
||||
return fmt.Errorf("tls: no cipher suite supported by both client and server; client offered: %x",
|
||||
hs.clientHello.cipherSuites)
|
||||
}
|
||||
c.cipherSuite = hs.suite.id
|
||||
|
||||
@ -459,7 +487,7 @@ func (hs *serverHandshakeState) checkForResumption() error {
|
||||
|
||||
// Check that we also support the ciphersuite from the session.
|
||||
suite := selectCipherSuite([]uint16{sessionState.cipherSuite},
|
||||
c.config.cipherSuites(), hs.cipherSuiteOk)
|
||||
c.config.supportedCipherSuites(), hs.cipherSuiteOk)
|
||||
if suite == nil {
|
||||
return nil
|
||||
}
|
||||
@ -489,6 +517,10 @@ func (hs *serverHandshakeState) checkForResumption() error {
|
||||
// weird downgrade in client capabilities.
|
||||
return errors.New("tls: session supported extended_master_secret but client does not")
|
||||
}
|
||||
if !sessionState.extMasterSecret && fips140tls.Required() {
|
||||
// FIPS 140-3 requires the use of Extended Master Secret.
|
||||
return nil
|
||||
}
|
||||
|
||||
c.peerCertificates = sessionState.peerCertificates
|
||||
c.ocspResponse = sessionState.ocspResponse
|
||||
@ -497,6 +529,7 @@ func (hs *serverHandshakeState) checkForResumption() error {
|
||||
c.extMasterSecret = sessionState.extMasterSecret
|
||||
hs.sessionState = sessionState
|
||||
hs.suite = suite
|
||||
c.curveID = sessionState.curveID
|
||||
c.didResume = true
|
||||
return nil
|
||||
}
|
||||
@ -667,7 +700,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
|
||||
|
||||
preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers)
|
||||
if err != nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return err
|
||||
}
|
||||
if hs.hello.extendedMasterSecret {
|
||||
@ -675,6 +708,10 @@ func (hs *serverHandshakeState) doFullHandshake() error {
|
||||
hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret,
|
||||
hs.finishedHash.Sum())
|
||||
} else {
|
||||
if fips140tls.Required() {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: FIPS 140-3 requires the use of Extended Master Secret")
|
||||
}
|
||||
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret,
|
||||
hs.clientHello.random, hs.hello.random)
|
||||
}
|
||||
@ -856,14 +893,14 @@ func (hs *serverHandshakeState) sendFinished(out []byte) error {
|
||||
}
|
||||
|
||||
// processCertsFromClient takes a chain of client certificates either from a
|
||||
// Certificates message and verifies them.
|
||||
// certificateMsg message or a certificateMsgTLS13 message and verifies them.
|
||||
func (c *Conn) processCertsFromClient(certificate Certificate) error {
|
||||
certificates := certificate.Certificate
|
||||
certs := make([]*x509.Certificate, len(certificates))
|
||||
var err error
|
||||
for i, asn1Data := range certificates {
|
||||
if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: failed to parse client certificate: " + err.Error())
|
||||
}
|
||||
if certs[i].PublicKeyAlgorithm == x509.RSA {
|
||||
@ -879,7 +916,7 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error {
|
||||
if c.vers == VersionTLS13 {
|
||||
c.sendAlert(alertCertificateRequired)
|
||||
} else {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
}
|
||||
return errors.New("tls: client didn't provide a certificate")
|
||||
}
|
||||
@ -909,7 +946,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error {
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
}
|
||||
|
||||
c.verifiedChains = chains
|
||||
c.verifiedChains, err = fipsAllowedChains(chains)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
c.peerCertificates = certs
|
||||
|
@ -9,20 +9,26 @@ import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/reality/mlkem768"
|
||||
"github.com/xtls/reality/fips140tls"
|
||||
"github.com/xtls/reality/hpke"
|
||||
"github.com/xtls/reality/tls13"
|
||||
)
|
||||
|
||||
// maxClientPSKIdentities is the number of client PSK identities the server will
|
||||
@ -30,6 +36,18 @@ import (
|
||||
// messages cause too much work in session ticket decryption attempts.
|
||||
const maxClientPSKIdentities = 5
|
||||
|
||||
type echServerContext struct {
|
||||
hpkeContext *hpke.Recipient
|
||||
configID uint8
|
||||
ciphersuite echCipher
|
||||
transcript hash.Hash
|
||||
// inner indicates that the initial client_hello we recieved contained an
|
||||
// encrypted_client_hello extension that indicated it was an "inner" hello.
|
||||
// We don't do any additional processing of the hello in this case, so all
|
||||
// fields above are unset.
|
||||
inner bool
|
||||
}
|
||||
|
||||
type serverHandshakeStateTLS13 struct {
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
@ -41,13 +59,14 @@ type serverHandshakeStateTLS13 struct {
|
||||
suite *cipherSuiteTLS13
|
||||
cert *Certificate
|
||||
sigAlg SignatureScheme
|
||||
earlySecret []byte
|
||||
earlySecret *tls13.EarlySecret
|
||||
sharedKey []byte
|
||||
handshakeSecret []byte
|
||||
masterSecret []byte
|
||||
handshakeSecret *tls13.HandshakeSecret
|
||||
masterSecret *tls13.MasterSecret
|
||||
trafficSecret []byte // client_application_traffic_secret_0
|
||||
transcript hash.Hash
|
||||
clientFinished []byte
|
||||
echContext *echServerContext
|
||||
}
|
||||
|
||||
var (
|
||||
@ -211,11 +230,11 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
||||
hs.hello.compressionMethod = compressionNone
|
||||
|
||||
preferenceList := defaultCipherSuitesTLS13
|
||||
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
|
||||
if !hasAESGCMHardwareSupport || !isAESGCMPreferred(hs.clientHello.cipherSuites) {
|
||||
preferenceList = defaultCipherSuitesTLS13NoAES
|
||||
}
|
||||
if needFIPS() {
|
||||
preferenceList = defaultCipherSuitesTLS13FIPS
|
||||
if fips140tls.Required() {
|
||||
preferenceList = allowedCipherSuitesTLS13FIPS
|
||||
}
|
||||
for _, suiteID := range preferenceList {
|
||||
hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID)
|
||||
@ -225,42 +244,51 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
||||
}
|
||||
if hs.suite == nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no cipher suite supported by both client and server")
|
||||
return fmt.Errorf("tls: no cipher suite supported by both client and server; client offered: %x",
|
||||
hs.clientHello.cipherSuites)
|
||||
}
|
||||
c.cipherSuite = hs.suite.id
|
||||
hs.hello.cipherSuite = hs.suite.id
|
||||
hs.transcript = hs.suite.hash.New()
|
||||
|
||||
// Pick the key exchange method in server preference order, but give
|
||||
// priority to key shares, to avoid a HelloRetryRequest round-trip.
|
||||
var selectedGroup CurveID
|
||||
var clientKeyShare *keyShare
|
||||
// First, if a post-quantum key exchange is available, use one. See
|
||||
// draft-ietf-tls-key-share-prediction-01, Section 4 for why this must be
|
||||
// first.
|
||||
//
|
||||
// Second, if the client sent a key share for a group we support, use that,
|
||||
// to avoid a HelloRetryRequest round-trip.
|
||||
//
|
||||
// Finally, pick in our fixed preference order.
|
||||
preferredGroups := c.config.curvePreferences(c.vers)
|
||||
for _, preferredGroup := range preferredGroups {
|
||||
ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool {
|
||||
return ks.group == preferredGroup
|
||||
preferredGroups = slices.DeleteFunc(preferredGroups, func(group CurveID) bool {
|
||||
return !slices.Contains(hs.clientHello.supportedCurves, group)
|
||||
})
|
||||
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")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if selectedGroup == 0 {
|
||||
for _, preferredGroup := range preferredGroups {
|
||||
if slices.Contains(hs.clientHello.supportedCurves, preferredGroup) {
|
||||
selectedGroup = preferredGroup
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if selectedGroup == 0 {
|
||||
if len(preferredGroups) == 0 {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no ECDHE curve supported by both client and server")
|
||||
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 clientKeyShare == nil {
|
||||
ks, err := hs.doHelloRetryRequest(selectedGroup)
|
||||
@ -273,13 +301,13 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
||||
|
||||
ecdhGroup := selectedGroup
|
||||
ecdhData := clientKeyShare.data
|
||||
if selectedGroup == x25519Kyber768Draft00 {
|
||||
if selectedGroup == X25519MLKEM768 {
|
||||
ecdhGroup = X25519
|
||||
if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize {
|
||||
if len(ecdhData) != mlkem.EncapsulationKeySize768+x25519PublicKeySize {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid Kyber client key share")
|
||||
return errors.New("tls: invalid X25519MLKEM768 client key share")
|
||||
}
|
||||
ecdhData = ecdhData[:x25519PublicKeySize]
|
||||
ecdhData = ecdhData[mlkem.EncapsulationKeySize768:]
|
||||
}
|
||||
if _, ok := curveForCurveID(ecdhGroup); !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
@ -301,14 +329,24 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid client key share")
|
||||
}
|
||||
if selectedGroup == x25519Kyber768Draft00 {
|
||||
ciphertext, kyberShared, err := kyberEncapsulate(clientKeyShare.data[x25519PublicKeySize:])
|
||||
if selectedGroup == X25519MLKEM768 {
|
||||
k, err := mlkem.NewEncapsulationKey768(clientKeyShare.data[:mlkem.EncapsulationKeySize768])
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid Kyber client key share")
|
||||
return errors.New("tls: invalid X25519MLKEM768 client key share")
|
||||
}
|
||||
hs.sharedKey = append(hs.sharedKey, kyberShared...)
|
||||
hs.hello.serverShare.data = append(hs.hello.serverShare.data, ciphertext...)
|
||||
mlkemSharedSecret, ciphertext := k.Encapsulate()
|
||||
// draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.3: "For
|
||||
// X25519MLKEM768, the shared secret is the concatenation of the ML-KEM
|
||||
// shared secret and the X25519 shared secret. The shared secret is 64
|
||||
// bytes (32 bytes for each part)."
|
||||
hs.sharedKey = append(mlkemSharedSecret, hs.sharedKey...)
|
||||
// draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.2: "When the
|
||||
// X25519MLKEM768 group is negotiated, the server's key exchange value
|
||||
// is the concatenation of an ML-KEM ciphertext returned from
|
||||
// encapsulation to the client's encapsulation key, and the server's
|
||||
// ephemeral X25519 share."
|
||||
hs.hello.serverShare.data = append(ciphertext, hs.hello.serverShare.data...)
|
||||
}
|
||||
|
||||
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
||||
@ -435,8 +473,8 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
||||
}
|
||||
}
|
||||
|
||||
hs.earlySecret = hs.suite.extract(sessionState.secret, nil)
|
||||
binderKey := hs.suite.deriveSecret(hs.earlySecret, resumptionBinderLabel, nil)
|
||||
hs.earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, sessionState.secret)
|
||||
binderKey := hs.earlySecret.ResumptionBinderKey()
|
||||
// Clone the transcript in case a HelloRetryRequest was recorded.
|
||||
transcript := cloneHash(hs.transcript, hs.suite.hash)
|
||||
if transcript == nil {
|
||||
@ -464,7 +502,7 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
||||
if err := transcriptMsg(hs.clientHello, transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript)
|
||||
earlyTrafficSecret := hs.earlySecret.ClientEarlyTrafficSecret(transcript)
|
||||
c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret)
|
||||
}
|
||||
|
||||
@ -582,6 +620,23 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID)
|
||||
selectedGroup: selectedGroup,
|
||||
}
|
||||
|
||||
if hs.echContext != nil {
|
||||
// Compute the acceptance message.
|
||||
helloRetryRequest.encryptedClientHello = make([]byte, 8)
|
||||
confTranscript := cloneHash(hs.transcript, hs.suite.hash)
|
||||
if err := transcriptMsg(helloRetryRequest, confTranscript); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := hs.suite.hash.New
|
||||
prf, err := hkdf.Extract(h, hs.clientHello.random, nil)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, err
|
||||
}
|
||||
acceptConfirmation := tls13.ExpandLabel(h, prf, "hrr ech accept confirmation", confTranscript.Sum(nil), 8)
|
||||
helloRetryRequest.encryptedClientHello = acceptConfirmation
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -602,6 +657,45 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID)
|
||||
return nil, unexpectedMessageError(clientHello, msg)
|
||||
}
|
||||
|
||||
if hs.echContext != nil {
|
||||
if len(clientHello.encryptedClientHello) == 0 {
|
||||
c.sendAlert(alertMissingExtension)
|
||||
return nil, errors.New("tls: second client hello missing encrypted client hello extension")
|
||||
}
|
||||
|
||||
echType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello)
|
||||
if err != nil {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return nil, errors.New("tls: client sent invalid encrypted client hello extension")
|
||||
}
|
||||
|
||||
if echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return nil, errors.New("tls: unexpected switch in encrypted client hello extension type")
|
||||
}
|
||||
|
||||
if echType == outerECHExt {
|
||||
if echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, errors.New("tls: second client hello encrypted client hello extension does not match")
|
||||
}
|
||||
|
||||
encodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload)
|
||||
if err != nil {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return nil, errors.New("tls: failed to decrypt second client hello encrypted client hello extension payload")
|
||||
}
|
||||
|
||||
echInner, err := decodeInnerClientHello(clientHello, encodedInner)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, errors.New("tls: client sent invalid encrypted client hello extension")
|
||||
}
|
||||
|
||||
clientHello = echInner
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientHello.keyShares) != 1 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, errors.New("tls: client didn't send one key share in second ClientHello")
|
||||
@ -689,9 +783,28 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool {
|
||||
func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
||||
c := hs.c
|
||||
|
||||
if hs.echContext != nil {
|
||||
copy(hs.hello.random[32-8:], make([]byte, 8))
|
||||
echTranscript := cloneHash(hs.transcript, hs.suite.hash)
|
||||
echTranscript.Write(hs.clientHello.original)
|
||||
if err := transcriptMsg(hs.hello, echTranscript); err != nil {
|
||||
return err
|
||||
}
|
||||
// compute the acceptance message
|
||||
h := hs.suite.hash.New
|
||||
prk, err := hkdf.Extract(h, hs.clientHello.random, nil)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
acceptConfirmation := tls13.ExpandLabel(h, prk, "ech accept confirmation", echTranscript.Sum(nil), 8)
|
||||
copy(hs.hello.random[32-8:], acceptConfirmation)
|
||||
}
|
||||
|
||||
if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -702,16 +815,13 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
||||
|
||||
earlySecret := hs.earlySecret
|
||||
if earlySecret == nil {
|
||||
earlySecret = hs.suite.extract(nil, nil)
|
||||
earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil)
|
||||
}
|
||||
hs.handshakeSecret = hs.suite.extract(hs.sharedKey,
|
||||
hs.suite.deriveSecret(earlySecret, "derived", nil))
|
||||
hs.handshakeSecret = earlySecret.HandshakeSecret(hs.sharedKey)
|
||||
|
||||
clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,
|
||||
clientHandshakeTrafficLabel, hs.transcript)
|
||||
clientSecret := hs.handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
|
||||
serverSecret := hs.suite.deriveSecret(hs.handshakeSecret,
|
||||
serverHandshakeTrafficLabel, hs.transcript)
|
||||
serverSecret := hs.handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
@ -745,6 +855,16 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
||||
encryptedExtensions.earlyData = hs.earlyData
|
||||
}
|
||||
|
||||
// If client sent ECH extension, but we didn't accept it,
|
||||
// send retry configs, if available.
|
||||
if len(hs.c.config.EncryptedClientHelloKeys) > 0 && len(hs.clientHello.encryptedClientHello) > 0 && hs.echContext == nil {
|
||||
encryptedExtensions.echRetryConfigs, err = buildRetryConfigList(hs.c.config.EncryptedClientHelloKeys)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -836,13 +956,10 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
|
||||
|
||||
// Derive secrets that take context through the server Finished.
|
||||
|
||||
hs.masterSecret = hs.suite.extract(nil,
|
||||
hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil))
|
||||
hs.masterSecret = hs.handshakeSecret.MasterSecret()
|
||||
|
||||
hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
clientApplicationTrafficLabel, hs.transcript)
|
||||
serverSecret := hs.suite.deriveSecret(hs.masterSecret,
|
||||
serverApplicationTrafficLabel, hs.transcript)
|
||||
hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript)
|
||||
serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
@ -889,12 +1006,7 @@ func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool {
|
||||
}
|
||||
|
||||
// Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9.
|
||||
for _, pskMode := range hs.clientHello.pskModes {
|
||||
if pskMode == pskModeDHE {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(hs.clientHello.pskModes, pskModeDHE)
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
||||
@ -908,8 +1020,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
resumptionLabel, hs.transcript)
|
||||
c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript)
|
||||
|
||||
if !hs.shouldSendSessionTickets() {
|
||||
return nil
|
||||
@ -924,7 +1035,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error {
|
||||
}
|
||||
// ticket_nonce, which must be unique per connection, is always left at
|
||||
// zero because we only ever send one ticket per connection.
|
||||
psk := suite.expandLabel(c.resumptionSecret, "resumption",
|
||||
psk := tls13.ExpandLabel(suite.hash.New, c.resumptionSecret, "resumption",
|
||||
nil, suite.hash.Size())
|
||||
|
||||
m := new(newSessionTicketMsgTLS13)
|
||||
|
218
hpke/hpye.go
218
hpke/hpye.go
@ -9,13 +9,13 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/hkdf"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// testingOnlyGenerateKey is only used during testing, to provide
|
||||
@ -26,28 +26,23 @@ type hkdfKDF struct {
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
func (kdf *hkdfKDF) LabeledExtract(suiteID []byte, salt []byte, label string, inputKey []byte) []byte {
|
||||
labeledIKM := make([]byte, 0, 7+len(suiteID)+len(label)+len(inputKey))
|
||||
func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputKey []byte) ([]byte, error) {
|
||||
labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey))
|
||||
labeledIKM = append(labeledIKM, []byte("HPKE-v1")...)
|
||||
labeledIKM = append(labeledIKM, suiteID...)
|
||||
labeledIKM = append(labeledIKM, sid...)
|
||||
labeledIKM = append(labeledIKM, label...)
|
||||
labeledIKM = append(labeledIKM, inputKey...)
|
||||
return hkdf.Extract(kdf.hash.New, labeledIKM, salt)
|
||||
}
|
||||
|
||||
func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) []byte {
|
||||
func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) ([]byte, error) {
|
||||
labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info))
|
||||
labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length)
|
||||
labeledInfo = append(labeledInfo, []byte("HPKE-v1")...)
|
||||
labeledInfo = append(labeledInfo, suiteID...)
|
||||
labeledInfo = append(labeledInfo, label...)
|
||||
labeledInfo = append(labeledInfo, info...)
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(kdf.hash.New, randomKey, labeledInfo).Read(out)
|
||||
if err != nil || n != int(length) {
|
||||
panic("hpke: LabeledExpand failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length))
|
||||
}
|
||||
|
||||
// dhKEM implements the KEM specified in RFC 9180, Section 4.1.
|
||||
@ -59,13 +54,17 @@ type dhKEM struct {
|
||||
nSecret uint16
|
||||
}
|
||||
|
||||
type KemID uint16
|
||||
|
||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
||||
|
||||
var SupportedKEMs = map[uint16]struct {
|
||||
curve ecdh.Curve
|
||||
hash crypto.Hash
|
||||
nSecret uint16
|
||||
}{
|
||||
// RFC 9180 Section 7.1
|
||||
0x0020: {ecdh.X25519(), crypto.SHA256, 32},
|
||||
DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32},
|
||||
}
|
||||
|
||||
func newDHKem(kemID uint16) (*dhKEM, error) {
|
||||
@ -81,8 +80,11 @@ func newDHKem(kemID uint16) (*dhKEM, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) []byte {
|
||||
eaePRK := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey)
|
||||
func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) ([]byte, error) {
|
||||
eaePRK, err := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dh.kdf.LabeledExpand(dh.suiteID[:], eaePRK, "shared_secret", kemContext, dh.nSecret)
|
||||
}
|
||||
|
||||
@ -104,13 +106,28 @@ func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encap
|
||||
|
||||
encPubRecip := pubRecipient.Bytes()
|
||||
kemContext := append(encPubEph, encPubRecip...)
|
||||
|
||||
return dh.ExtractAndExpand(dhVal, kemContext), encPubEph, nil
|
||||
sharedSecret, err = dh.ExtractAndExpand(dhVal, kemContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sharedSecret, encPubEph, nil
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
func (dh *dhKEM) Decap(encPubEph []byte, secRecipient *ecdh.PrivateKey) ([]byte, error) {
|
||||
pubEph, err := dh.dh.NewPublicKey(encPubEph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dhVal, err := secRecipient.ECDH(pubEph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kemContext := append(encPubEph, secRecipient.PublicKey().Bytes()...)
|
||||
return dh.ExtractAndExpand(dhVal, kemContext)
|
||||
}
|
||||
|
||||
type context struct {
|
||||
aead cipher.AEAD
|
||||
kem *dhKEM
|
||||
|
||||
sharedSecret []byte
|
||||
|
||||
@ -123,6 +140,14 @@ type Sender struct {
|
||||
seqNum uint128
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
*context
|
||||
}
|
||||
|
||||
type Recipient struct {
|
||||
*context
|
||||
}
|
||||
|
||||
var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
@ -131,97 +156,160 @@ var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
|
||||
return cipher.NewGCM(block)
|
||||
}
|
||||
|
||||
type AEADID uint16
|
||||
|
||||
const (
|
||||
AEAD_AES_128_GCM = 0x0001
|
||||
AEAD_AES_256_GCM = 0x0002
|
||||
AEAD_ChaCha20Poly1305 = 0x0003
|
||||
)
|
||||
|
||||
var SupportedAEADs = map[uint16]struct {
|
||||
keySize int
|
||||
nonceSize int
|
||||
aead func([]byte) (cipher.AEAD, error)
|
||||
}{
|
||||
// RFC 9180, Section 7.3
|
||||
0x0001: {keySize: 16, nonceSize: 12, aead: aesGCMNew},
|
||||
0x0002: {keySize: 32, nonceSize: 12, aead: aesGCMNew},
|
||||
0x0003: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New},
|
||||
AEAD_AES_128_GCM: {keySize: 16, nonceSize: 12, aead: aesGCMNew},
|
||||
AEAD_AES_256_GCM: {keySize: 32, nonceSize: 12, aead: aesGCMNew},
|
||||
AEAD_ChaCha20Poly1305: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New},
|
||||
}
|
||||
|
||||
type KDFID uint16
|
||||
|
||||
const KDF_HKDF_SHA256 = 0x0001
|
||||
|
||||
var SupportedKDFs = map[uint16]func() *hkdfKDF{
|
||||
// RFC 9180, Section 7.2
|
||||
0x0001: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} },
|
||||
KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} },
|
||||
}
|
||||
|
||||
func SetupSender(kemID, kdfID, aeadID uint16, pub crypto.PublicKey, info []byte) ([]byte, *Sender, error) {
|
||||
suiteID := SuiteID(kemID, kdfID, aeadID)
|
||||
|
||||
kem, err := newDHKem(kemID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pubRecipient, ok := pub.(*ecdh.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("incorrect public key type")
|
||||
}
|
||||
sharedSecret, encapsulatedKey, err := kem.Encap(pubRecipient)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
func newContext(sharedSecret []byte, kemID, kdfID, aeadID uint16, info []byte) (*context, error) {
|
||||
sid := suiteID(kemID, kdfID, aeadID)
|
||||
|
||||
kdfInit, ok := SupportedKDFs[kdfID]
|
||||
if !ok {
|
||||
return nil, nil, errors.New("unsupported KDF id")
|
||||
return nil, errors.New("unsupported KDF id")
|
||||
}
|
||||
kdf := kdfInit()
|
||||
|
||||
aeadInfo, ok := SupportedAEADs[aeadID]
|
||||
if !ok {
|
||||
return nil, nil, errors.New("unsupported AEAD id")
|
||||
return nil, errors.New("unsupported AEAD id")
|
||||
}
|
||||
|
||||
pskIDHash := kdf.LabeledExtract(suiteID, nil, "psk_id_hash", nil)
|
||||
infoHash := kdf.LabeledExtract(suiteID, nil, "info_hash", info)
|
||||
pskIDHash, err := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoHash, err := kdf.LabeledExtract(sid, nil, "info_hash", info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ksContext := append([]byte{0}, pskIDHash...)
|
||||
ksContext = append(ksContext, infoHash...)
|
||||
|
||||
secret := kdf.LabeledExtract(suiteID, sharedSecret, "secret", nil)
|
||||
|
||||
key := kdf.LabeledExpand(suiteID, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
|
||||
baseNonce := kdf.LabeledExpand(suiteID, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */)
|
||||
exporterSecret := kdf.LabeledExpand(suiteID, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/)
|
||||
secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseNonce, err := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exporterSecret, err := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, err := aeadInfo.aead(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encapsulatedKey, &Sender{
|
||||
kem: kem,
|
||||
return &context{
|
||||
aead: aead,
|
||||
sharedSecret: sharedSecret,
|
||||
suiteID: suiteID,
|
||||
suiteID: sid,
|
||||
key: key,
|
||||
baseNonce: baseNonce,
|
||||
exporterSecret: exporterSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Sender) nextNonce() []byte {
|
||||
nonce := s.seqNum.bytes()[16-s.aead.NonceSize():]
|
||||
for i := range s.baseNonce {
|
||||
nonce[i] ^= s.baseNonce[i]
|
||||
func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) {
|
||||
kem, err := newDHKem(kemID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Message limit is, according to the RFC, 2^95+1, which
|
||||
// is somewhat confusing, but we do as we're told.
|
||||
if s.seqNum.bitLen() >= (s.aead.NonceSize()*8)-1 {
|
||||
panic("message limit reached")
|
||||
sharedSecret, encapsulatedKey, err := kem.Encap(pub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return encapsulatedKey, &Sender{context}, nil
|
||||
}
|
||||
|
||||
func SetupRecipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Recipient, error) {
|
||||
kem, err := newDHKem(kemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecret, err := kem.Decap(encPubEph, priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Recipient{context}, nil
|
||||
}
|
||||
|
||||
func (ctx *context) nextNonce() []byte {
|
||||
nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():]
|
||||
for i := range ctx.baseNonce {
|
||||
nonce[i] ^= ctx.baseNonce[i]
|
||||
}
|
||||
s.seqNum = s.seqNum.addOne()
|
||||
return nonce
|
||||
}
|
||||
|
||||
func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
|
||||
func (ctx *context) incrementNonce() {
|
||||
// Message limit is, according to the RFC, 2^95+1, which
|
||||
// is somewhat confusing, but we do as we're told.
|
||||
if ctx.seqNum.bitLen() >= (ctx.aead.NonceSize()*8)-1 {
|
||||
panic("message limit reached")
|
||||
}
|
||||
ctx.seqNum = ctx.seqNum.addOne()
|
||||
}
|
||||
|
||||
func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
|
||||
ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
|
||||
s.incrementNonce()
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func SuiteID(kemID, kdfID, aeadID uint16) []byte {
|
||||
func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) {
|
||||
plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.incrementNonce()
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func suiteID(kemID, kdfID, aeadID uint16) []byte {
|
||||
suiteID := make([]byte, 0, 4+2+2+2)
|
||||
suiteID = append(suiteID, []byte("HPKE")...)
|
||||
suiteID = binary.BigEndian.AppendUint16(suiteID, kemID)
|
||||
@ -238,6 +326,14 @@ func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) {
|
||||
return kemInfo.curve.NewPublicKey(bytes)
|
||||
}
|
||||
|
||||
func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) {
|
||||
kemInfo, ok := SupportedKEMs[kemID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported KEM id")
|
||||
}
|
||||
return kemInfo.curve.NewPrivateKey(bytes)
|
||||
}
|
||||
|
||||
type uint128 struct {
|
||||
hi, lo uint64
|
||||
}
|
||||
|
121
key_schedule.go
121
key_schedule.go
@ -7,94 +7,27 @@ package reality
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hmac"
|
||||
"crypto/mlkem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/xtls/reality/mlkem768"
|
||||
"github.com/xtls/reality/tls13"
|
||||
)
|
||||
|
||||
// This file contains the functions necessary to compute the TLS 1.3 key
|
||||
// schedule. See RFC 8446, Section 7.
|
||||
|
||||
const (
|
||||
resumptionBinderLabel = "res binder"
|
||||
clientEarlyTrafficLabel = "c e traffic"
|
||||
clientHandshakeTrafficLabel = "c hs traffic"
|
||||
serverHandshakeTrafficLabel = "s hs traffic"
|
||||
clientApplicationTrafficLabel = "c ap traffic"
|
||||
serverApplicationTrafficLabel = "s ap traffic"
|
||||
exporterLabel = "exp master"
|
||||
resumptionLabel = "res master"
|
||||
trafficUpdateLabel = "traffic upd"
|
||||
)
|
||||
|
||||
// expandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
|
||||
func (c *cipherSuiteTLS13) expandLabel(secret []byte, label string, context []byte, length int) []byte {
|
||||
var hkdfLabel cryptobyte.Builder
|
||||
hkdfLabel.AddUint16(uint16(length))
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes([]byte("tls13 "))
|
||||
b.AddBytes([]byte(label))
|
||||
})
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(context)
|
||||
})
|
||||
hkdfLabelBytes, err := hkdfLabel.Bytes()
|
||||
if err != nil {
|
||||
// Rather than calling BytesOrPanic, we explicitly handle this error, in
|
||||
// order to provide a reasonable error message. It should be basically
|
||||
// impossible for this to panic, and routing errors back through the
|
||||
// tree rooted in this function is quite painful. The labels are fixed
|
||||
// size, and the context is either a fixed-length computed hash, or
|
||||
// parsed from a field which has the same length limitation. As such, an
|
||||
// error here is likely to only be caused during development.
|
||||
//
|
||||
// NOTE: another reasonable approach here might be to return a
|
||||
// randomized slice if we encounter an error, which would break the
|
||||
// connection, but avoid panicking. This would perhaps be safer but
|
||||
// significantly more confusing to users.
|
||||
panic(fmt.Errorf("failed to construct HKDF label: %s", err))
|
||||
}
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(c.hash.New, secret, hkdfLabelBytes).Read(out)
|
||||
if err != nil || n != length {
|
||||
panic("tls: HKDF-Expand-Label invocation failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// deriveSecret implements Derive-Secret from RFC 8446, Section 7.1.
|
||||
func (c *cipherSuiteTLS13) deriveSecret(secret []byte, label string, transcript hash.Hash) []byte {
|
||||
if transcript == nil {
|
||||
transcript = c.hash.New()
|
||||
}
|
||||
return c.expandLabel(secret, label, transcript.Sum(nil), c.hash.Size())
|
||||
}
|
||||
|
||||
// extract implements HKDF-Extract with the cipher suite hash.
|
||||
func (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte {
|
||||
if newSecret == nil {
|
||||
newSecret = make([]byte, c.hash.Size())
|
||||
}
|
||||
return hkdf.Extract(c.hash.New, newSecret, currentSecret)
|
||||
}
|
||||
|
||||
// nextTrafficSecret generates the next traffic secret, given the current one,
|
||||
// according to RFC 8446, Section 7.2.
|
||||
func (c *cipherSuiteTLS13) nextTrafficSecret(trafficSecret []byte) []byte {
|
||||
return c.expandLabel(trafficSecret, trafficUpdateLabel, nil, c.hash.Size())
|
||||
return tls13.ExpandLabel(c.hash.New, trafficSecret, "traffic upd", nil, c.hash.Size())
|
||||
}
|
||||
|
||||
// trafficKey generates traffic keys according to RFC 8446, Section 7.3.
|
||||
func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
|
||||
key = c.expandLabel(trafficSecret, "key", nil, c.keyLen)
|
||||
iv = c.expandLabel(trafficSecret, "iv", nil, aeadNonceLength)
|
||||
key = tls13.ExpandLabel(c.hash.New, trafficSecret, "key", nil, c.keyLen)
|
||||
iv = tls13.ExpandLabel(c.hash.New, trafficSecret, "iv", nil, aeadNonceLength)
|
||||
return
|
||||
}
|
||||
|
||||
@ -102,7 +35,7 @@ func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
|
||||
// to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey
|
||||
// selection.
|
||||
func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {
|
||||
finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size())
|
||||
finishedKey := tls13.ExpandLabel(c.hash.New, baseKey, "finished", nil, c.hash.Size())
|
||||
verifyData := hmac.New(c.hash.New, finishedKey)
|
||||
verifyData.Write(transcript.Sum(nil))
|
||||
return verifyData.Sum(nil)
|
||||
@ -110,51 +43,17 @@ func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []
|
||||
|
||||
// exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to
|
||||
// RFC 8446, Section 7.5.
|
||||
func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
|
||||
expMasterSecret := c.deriveSecret(masterSecret, exporterLabel, transcript)
|
||||
func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
|
||||
expMasterSecret := s.ExporterMasterSecret(transcript)
|
||||
return func(label string, context []byte, length int) ([]byte, error) {
|
||||
secret := c.deriveSecret(expMasterSecret, label, nil)
|
||||
h := c.hash.New()
|
||||
h.Write(context)
|
||||
return c.expandLabel(secret, "exporter", h.Sum(nil), length), nil
|
||||
return expMasterSecret.Exporter(label, context, length), nil
|
||||
}
|
||||
}
|
||||
|
||||
type keySharePrivateKeys struct {
|
||||
curveID CurveID
|
||||
ecdhe *ecdh.PrivateKey
|
||||
kyber *mlkem768.DecapsulationKey
|
||||
}
|
||||
|
||||
// kyberDecapsulate implements decapsulation according to Kyber Round 3.
|
||||
func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) {
|
||||
K, err := mlkem768.Decapsulate(dk, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kyberSharedSecret(K, c), nil
|
||||
}
|
||||
|
||||
// kyberEncapsulate implements encapsulation according to Kyber Round 3.
|
||||
func kyberEncapsulate(ek []byte) (c, ss []byte, err error) {
|
||||
c, ss, err = mlkem768.Encapsulate(ek)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return c, kyberSharedSecret(ss, c), nil
|
||||
}
|
||||
|
||||
func kyberSharedSecret(K, c []byte) []byte {
|
||||
// Package mlkem768 implements ML-KEM, which compared to Kyber removed a
|
||||
// final hashing step. Compute SHAKE-256(K || SHA3-256(c), 32) to match Kyber.
|
||||
// See https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3.
|
||||
h := sha3.NewShake256()
|
||||
h.Write(K)
|
||||
ch := sha3.Sum256(c)
|
||||
h.Write(ch[:])
|
||||
out := make([]byte, 32)
|
||||
h.Read(out)
|
||||
return out
|
||||
mlkem *mlkem.DecapsulationKey768
|
||||
}
|
||||
|
||||
const x25519PublicKeySize = 32
|
||||
|
@ -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
|
||||
}
|
@ -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 }
|
64
prf.go
64
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
|
||||
}
|
||||
}
|
||||
|
49
ticket.go
49
ticket.go
@ -44,20 +44,21 @@ type SessionState struct {
|
||||
// case 0: Empty;
|
||||
// case 1: opaque alpn<1..2^8-1>;
|
||||
// };
|
||||
// select (SessionState.type) {
|
||||
// select (SessionState.version) {
|
||||
// case VersionTLS10..VersionTLS12: uint16 curve_id;
|
||||
// case VersionTLS13: 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;
|
||||
// };
|
||||
// };
|
||||
// };
|
||||
// };
|
||||
// } 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.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")
|
||||
}
|
||||
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() {
|
||||
} else {
|
||||
if !s.ReadUint16((*uint16)(&ss.curveID)) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
26
tls.go
26
tls.go
@ -2,8 +2,23 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE-Go file.
|
||||
|
||||
// Server side implementation of REALITY protocol, a fork of package tls in Go 1.20.
|
||||
// Server side implementation of REALITY protocol, a fork of package tls in latest Go.
|
||||
// For client side, please follow https://github.com/XTLS/Xray-core/blob/main/transport/internet/reality/reality.go.
|
||||
|
||||
// Package tls partially implements TLS 1.2, as specified in RFC 5246,
|
||||
// and TLS 1.3, as specified in RFC 8446.
|
||||
//
|
||||
// # FIPS 140-3 mode
|
||||
//
|
||||
// When the program is in [FIPS 140-3 mode], this package behaves as if only
|
||||
// SP 800-140C and SP 800-140D approved protocol versions, cipher suites,
|
||||
// signature algorithms, certificate public key types and sizes, and key
|
||||
// exchange and derivation algorithms were implemented. Others are silently
|
||||
// ignored and not negotiated, or rejected. This set may depend on the
|
||||
// algorithms supported by the FIPS 140-3 Go Cryptographic Module selected with
|
||||
// GOFIPS140, and may change across Go versions.
|
||||
//
|
||||
// [FIPS 140-3 mode]: https://go.dev/doc/security/fips140
|
||||
package reality
|
||||
|
||||
// BUG(agl): The crypto/tls package only implements some countermeasures
|
||||
@ -38,6 +53,9 @@ import (
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"github.com/xtls/reality/gcm"
|
||||
fipsaes "github.com/xtls/reality/aes"
|
||||
)
|
||||
|
||||
type CloseWriteConn interface {
|
||||
@ -158,7 +176,7 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
||||
go func() {
|
||||
for {
|
||||
mutex.Lock()
|
||||
hs.clientHello, err = hs.c.readClientHello(context.Background()) // TODO: Change some rules in this function.
|
||||
hs.clientHello, _, err = hs.c.readClientHello(context.Background()) // TODO: Change some rules in this function.
|
||||
if copying || err != nil || hs.c.vers != VersionTLS13 || !config.ServerNames[hs.clientHello.serverName] {
|
||||
break
|
||||
}
|
||||
@ -173,9 +191,9 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
||||
break
|
||||
}
|
||||
var aead cipher.AEAD
|
||||
if aesgcmPreferred(hs.clientHello.cipherSuites) {
|
||||
if isAESGCMPreferred(hs.clientHello.cipherSuites) {
|
||||
block, _ := aes.NewCipher(hs.c.AuthKey)
|
||||
aead, _ = cipher.NewGCM(block)
|
||||
aead, _ = gcm.NewGCMForTLS13(block.(*fipsaes.Block))
|
||||
} else {
|
||||
aead, _ = chacha20poly1305.New(hs.c.AuthKey)
|
||||
}
|
||||
|
68
tls12/tls12.go
Normal file
68
tls12/tls12.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls12
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// PRF implements the TLS 1.2 pseudo-random function, as defined in RFC 5246,
|
||||
// Section 5 and allowed by SP 800-135, Revision 1, Section 4.2.2.
|
||||
func PRF[H hash.Hash](hash func() H, secret []byte, label string, seed []byte, keyLen int) []byte {
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
|
||||
result := make([]byte, keyLen)
|
||||
pHash(hash, result, secret, labelAndSeed)
|
||||
return result
|
||||
}
|
||||
|
||||
// pHash implements the P_hash function, as defined in RFC 5246, Section 5.
|
||||
func pHash[H hash.Hash](hash1 func() H, result, secret, seed []byte) {
|
||||
h := hmac.New(any(hash1).(func() hash.Hash), secret)
|
||||
h.Write(seed)
|
||||
a := h.Sum(nil)
|
||||
|
||||
for len(result) > 0 {
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
h.Write(seed)
|
||||
b := h.Sum(nil)
|
||||
n := copy(result, b)
|
||||
result = result[n:]
|
||||
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
a = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
const masterSecretLength = 48
|
||||
const extendedMasterSecretLabel = "extended master secret"
|
||||
|
||||
// MasterSecret implements the TLS 1.2 extended master secret derivation, as
|
||||
// defined in RFC 7627 and allowed by SP 800-135, Revision 1, Section 4.2.2.
|
||||
func MasterSecret[H hash.Hash](hash func() H, preMasterSecret, transcript []byte) []byte {
|
||||
// "The TLS 1.2 KDF is an approved KDF when the following conditions are
|
||||
// satisfied: [...] (3) P_HASH uses either SHA-256, SHA-384 or SHA-512."
|
||||
//h := hash()
|
||||
hash()
|
||||
// switch any(h).(type) {
|
||||
// case *sha256.Digest:
|
||||
// if h.Size() != 32 {
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
// case *sha512.Digest:
|
||||
// if h.Size() != 46 && h.Size() != 64 {
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
// default:
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
|
||||
return PRF(hash, preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength)
|
||||
}
|
180
tls13/tls13.go
Normal file
180
tls13/tls13.go
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tls13 implements the TLS 1.3 Key Schedule as specified in RFC 8446,
|
||||
// Section 7.1 and allowed by FIPS 140-3 IG 2.4.B Resolution 7.
|
||||
package tls13
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// We don't set the service indicator in this package but we delegate that to
|
||||
// the underlying functions because the TLS 1.3 KDF does not have a standard of
|
||||
// its own.
|
||||
|
||||
// ExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
|
||||
func ExpandLabel[H hash.Hash](hash func() H, secret []byte, label string, context []byte, length int) []byte {
|
||||
if len("tls13 ")+len(label) > 255 || len(context) > 255 {
|
||||
// It should be impossible for this to panic: labels are fixed strings,
|
||||
// and context is either a fixed-length computed hash, or parsed from a
|
||||
// field which has the same length limitation.
|
||||
//
|
||||
// Another reasonable approach might be to return a randomized slice if
|
||||
// we encounter an error, which would break the connection, but avoid
|
||||
// panicking. This would perhaps be safer but significantly more
|
||||
// confusing to users.
|
||||
panic("tls13: label or context too long")
|
||||
}
|
||||
hkdfLabel := make([]byte, 0, 2+1+len("tls13 ")+len(label)+1+len(context))
|
||||
hkdfLabel = binary.BigEndian.AppendUint16(hkdfLabel, uint16(length))
|
||||
hkdfLabel = append(hkdfLabel, byte(len("tls13 ")+len(label)))
|
||||
hkdfLabel = append(hkdfLabel, "tls13 "...)
|
||||
hkdfLabel = append(hkdfLabel, label...)
|
||||
hkdfLabel = append(hkdfLabel, byte(len(context)))
|
||||
hkdfLabel = append(hkdfLabel, context...)
|
||||
b, _ := hkdf.Expand(hash, secret, string(hkdfLabel), length)
|
||||
return b
|
||||
}
|
||||
|
||||
func extract[H hash.Hash](hash func() H, newSecret, currentSecret []byte) []byte {
|
||||
if newSecret == nil {
|
||||
newSecret = make([]byte, hash().Size())
|
||||
}
|
||||
b, _ := hkdf.Extract(hash, newSecret, currentSecret)
|
||||
return b
|
||||
}
|
||||
|
||||
func deriveSecret[H hash.Hash](hash func() H, secret []byte, label string, transcript hash.Hash) []byte {
|
||||
if transcript == nil {
|
||||
transcript = hash()
|
||||
}
|
||||
return ExpandLabel(hash, secret, label, transcript.Sum(nil), transcript.Size())
|
||||
}
|
||||
|
||||
const (
|
||||
resumptionBinderLabel = "res binder"
|
||||
clientEarlyTrafficLabel = "c e traffic"
|
||||
clientHandshakeTrafficLabel = "c hs traffic"
|
||||
serverHandshakeTrafficLabel = "s hs traffic"
|
||||
clientApplicationTrafficLabel = "c ap traffic"
|
||||
serverApplicationTrafficLabel = "s ap traffic"
|
||||
earlyExporterLabel = "e exp master"
|
||||
exporterLabel = "exp master"
|
||||
resumptionLabel = "res master"
|
||||
)
|
||||
|
||||
type EarlySecret struct {
|
||||
secret []byte
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
func NewEarlySecret[H hash.Hash](h func() H, psk []byte) *EarlySecret {
|
||||
return &EarlySecret{
|
||||
secret: extract(h, psk, nil),
|
||||
hash: func() hash.Hash { return h() },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EarlySecret) ResumptionBinderKey() []byte {
|
||||
return deriveSecret(s.hash, s.secret, resumptionBinderLabel, nil)
|
||||
}
|
||||
|
||||
// ClientEarlyTrafficSecret derives the client_early_traffic_secret from the
|
||||
// early secret and the transcript up to the ClientHello.
|
||||
func (s *EarlySecret) ClientEarlyTrafficSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
type HandshakeSecret struct {
|
||||
secret []byte
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret {
|
||||
derived := deriveSecret(s.hash, s.secret, "derived", nil)
|
||||
return &HandshakeSecret{
|
||||
secret: extract(s.hash, sharedSecret, derived),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientHandshakeTrafficSecret derives the client_handshake_traffic_secret from
|
||||
// the handshake secret and the transcript up to the ServerHello.
|
||||
func (s *HandshakeSecret) ClientHandshakeTrafficSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientHandshakeTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ServerHandshakeTrafficSecret derives the server_handshake_traffic_secret from
|
||||
// the handshake secret and the transcript up to the ServerHello.
|
||||
func (s *HandshakeSecret) ServerHandshakeTrafficSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
type MasterSecret struct {
|
||||
secret []byte
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
func (s *HandshakeSecret) MasterSecret() *MasterSecret {
|
||||
derived := deriveSecret(s.hash, s.secret, "derived", nil)
|
||||
return &MasterSecret{
|
||||
secret: extract(s.hash, nil, derived),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientApplicationTrafficSecret derives the client_application_traffic_secret_0
|
||||
// from the master secret and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ClientApplicationTrafficSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientApplicationTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ServerApplicationTrafficSecret derives the server_application_traffic_secret_0
|
||||
// from the master secret and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ServerApplicationTrafficSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, serverApplicationTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ResumptionMasterSecret derives the resumption_master_secret from the master secret
|
||||
// and the transcript up to the client Finished.
|
||||
func (s *MasterSecret) ResumptionMasterSecret(transcript hash.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, resumptionLabel, transcript)
|
||||
}
|
||||
|
||||
type ExporterMasterSecret struct {
|
||||
secret []byte
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
// ExporterMasterSecret derives the exporter_master_secret from the master secret
|
||||
// and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret {
|
||||
return &ExporterMasterSecret{
|
||||
secret: deriveSecret(s.hash, s.secret, exporterLabel, transcript),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// EarlyExporterMasterSecret derives the exporter_master_secret from the early secret
|
||||
// and the transcript up to the ClientHello.
|
||||
func (s *EarlySecret) EarlyExporterMasterSecret(transcript hash.Hash) *ExporterMasterSecret {
|
||||
return &ExporterMasterSecret{
|
||||
secret: deriveSecret(s.hash, s.secret, earlyExporterLabel, transcript),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ExporterMasterSecret) Exporter(label string, context []byte, length int) []byte {
|
||||
secret := deriveSecret(s.hash, s.secret, label, nil)
|
||||
h := s.hash()
|
||||
h.Write(context)
|
||||
return ExpandLabel(s.hash, secret, "exporter", h.Sum(nil), length)
|
||||
}
|
||||
|
||||
func TestingOnlyExporterSecret(s *ExporterMasterSecret) []byte {
|
||||
return s.secret
|
||||
}
|
Loading…
Reference in New Issue
Block a user