mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
crypto/internal/hpke: modularize API and support more ciphersuites
Updates #75300 Change-Id: I6a6a6964de449b36bc6f5594e08c3c47a0a2f17f Reviewed-on: https://go-review.googlesource.com/c/go/+/701435 Reviewed-by: Daniel McCarney <daniel@binaryparadox.net> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Mark Freeman <markfreeman@google.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
parent
e7d47ac33d
commit
7c985a2df4
12 changed files with 1301 additions and 484 deletions
130
src/crypto/internal/hpke/aead.go
Normal file
130
src/crypto/internal/hpke/aead.go
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package hpke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The AEAD is one of the three components of an HPKE ciphersuite, implementing
|
||||||
|
// symmetric encryption.
|
||||||
|
type AEAD interface {
|
||||||
|
ID() uint16
|
||||||
|
keySize() int
|
||||||
|
nonceSize() int
|
||||||
|
aead(key []byte) (cipher.AEAD, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAEAD returns the AEAD implementation for the given AEAD ID.
|
||||||
|
//
|
||||||
|
// Applications are encouraged to use specific implementations like [AES128GCM]
|
||||||
|
// or [ChaCha20Poly1305] instead, unless runtime agility is required.
|
||||||
|
func NewAEAD(id uint16) (AEAD, error) {
|
||||||
|
switch id {
|
||||||
|
case 0x0001: // AES-128-GCM
|
||||||
|
return AES128GCM(), nil
|
||||||
|
case 0x0002: // AES-256-GCM
|
||||||
|
return AES256GCM(), nil
|
||||||
|
case 0x0003: // ChaCha20Poly1305
|
||||||
|
return ChaCha20Poly1305(), nil
|
||||||
|
case 0xFFFF: // Export-only
|
||||||
|
return ExportOnly(), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported AEAD %04x", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AES128GCM returns an AES-128-GCM AEAD implementation.
|
||||||
|
func AES128GCM() AEAD { return aes128GCM }
|
||||||
|
|
||||||
|
// AES256GCM returns an AES-256-GCM AEAD implementation.
|
||||||
|
func AES256GCM() AEAD { return aes256GCM }
|
||||||
|
|
||||||
|
// ChaCha20Poly1305 returns a ChaCha20Poly1305 AEAD implementation.
|
||||||
|
func ChaCha20Poly1305() AEAD { return chacha20poly1305AEAD }
|
||||||
|
|
||||||
|
// ExportOnly returns a placeholder AEAD implementation that cannot encrypt or
|
||||||
|
// decrypt, but only export secrets with [Sender.Export] or [Recipient.Export].
|
||||||
|
//
|
||||||
|
// When this is used, [Sender.Seal] and [Recipient.Open] return errors.
|
||||||
|
func ExportOnly() AEAD { return exportOnlyAEAD{} }
|
||||||
|
|
||||||
|
type aead struct {
|
||||||
|
nK int
|
||||||
|
nN int
|
||||||
|
new func([]byte) (cipher.AEAD, error)
|
||||||
|
id uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var aes128GCM = &aead{
|
||||||
|
nK: 128 / 8,
|
||||||
|
nN: 96 / 8,
|
||||||
|
new: newAESGCM,
|
||||||
|
id: 0x0001,
|
||||||
|
}
|
||||||
|
|
||||||
|
var aes256GCM = &aead{
|
||||||
|
nK: 256 / 8,
|
||||||
|
nN: 96 / 8,
|
||||||
|
new: newAESGCM,
|
||||||
|
id: 0x0002,
|
||||||
|
}
|
||||||
|
|
||||||
|
var chacha20poly1305AEAD = &aead{
|
||||||
|
nK: chacha20poly1305.KeySize,
|
||||||
|
nN: chacha20poly1305.NonceSize,
|
||||||
|
new: chacha20poly1305.New,
|
||||||
|
id: 0x0003,
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAESGCM(key []byte) (cipher.AEAD, error) {
|
||||||
|
b, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cipher.NewGCM(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aead) ID() uint16 {
|
||||||
|
return a.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aead) aead(key []byte) (cipher.AEAD, error) {
|
||||||
|
if len(key) != a.nK {
|
||||||
|
return nil, errors.New("invalid key size")
|
||||||
|
}
|
||||||
|
return a.new(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aead) keySize() int {
|
||||||
|
return a.nK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aead) nonceSize() int {
|
||||||
|
return a.nN
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportOnlyAEAD struct{}
|
||||||
|
|
||||||
|
func (exportOnlyAEAD) ID() uint16 {
|
||||||
|
return 0xFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (exportOnlyAEAD) aead(key []byte) (cipher.AEAD, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (exportOnlyAEAD) keySize() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (exportOnlyAEAD) nonceSize() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -2,354 +2,217 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package hpke implements Hybrid Public Key Encryption (HPKE) as defined in
|
||||||
|
// [RFC 9180].
|
||||||
|
//
|
||||||
|
// [RFC 9180]: https://www.rfc-editor.org/rfc/rfc9180.html
|
||||||
package hpke
|
package hpke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/ecdh"
|
"encoding/binary"
|
||||||
"crypto/hkdf"
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
"errors"
|
||||||
"internal/byteorder"
|
|
||||||
"math/bits"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// testingOnlyGenerateKey is only used during testing, to provide
|
|
||||||
// a fixed test key to use when checking the RFC 9180 vectors.
|
|
||||||
var testingOnlyGenerateKey func() (*ecdh.PrivateKey, error)
|
|
||||||
|
|
||||||
type hkdfKDF struct {
|
|
||||||
hash crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 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, error) {
|
|
||||||
labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info))
|
|
||||||
labeledInfo = byteorder.BEAppendUint16(labeledInfo, length)
|
|
||||||
labeledInfo = append(labeledInfo, []byte("HPKE-v1")...)
|
|
||||||
labeledInfo = append(labeledInfo, suiteID...)
|
|
||||||
labeledInfo = append(labeledInfo, label...)
|
|
||||||
labeledInfo = append(labeledInfo, info...)
|
|
||||||
return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
// dhKEM implements the KEM specified in RFC 9180, Section 4.1.
|
|
||||||
type dhKEM struct {
|
|
||||||
dh ecdh.Curve
|
|
||||||
kdf hkdfKDF
|
|
||||||
|
|
||||||
suiteID []byte
|
|
||||||
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
|
|
||||||
DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32},
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDHKem(kemID uint16) (*dhKEM, error) {
|
|
||||||
suite, ok := SupportedKEMs[kemID]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unsupported suite ID")
|
|
||||||
}
|
|
||||||
return &dhKEM{
|
|
||||||
dh: suite.curve,
|
|
||||||
kdf: hkdfKDF{suite.hash},
|
|
||||||
suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID),
|
|
||||||
nSecret: suite.nSecret,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encapPub []byte, err error) {
|
|
||||||
var privEph *ecdh.PrivateKey
|
|
||||||
if testingOnlyGenerateKey != nil {
|
|
||||||
privEph, err = testingOnlyGenerateKey()
|
|
||||||
} else {
|
|
||||||
privEph, err = dh.dh.GenerateKey(rand.Reader)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
dhVal, err := privEph.ECDH(pubRecipient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
encPubEph := privEph.PublicKey().Bytes()
|
|
||||||
|
|
||||||
encPubRecip := pubRecipient.Bytes()
|
|
||||||
kemContext := append(encPubEph, encPubRecip...)
|
|
||||||
sharedSecret, err = dh.ExtractAndExpand(dhVal, kemContext)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return sharedSecret, encPubEph, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
type context struct {
|
||||||
aead cipher.AEAD
|
|
||||||
|
|
||||||
sharedSecret []byte
|
|
||||||
|
|
||||||
suiteID []byte
|
suiteID []byte
|
||||||
|
|
||||||
key []byte
|
export func(string, uint16) ([]byte, error)
|
||||||
baseNonce []byte
|
|
||||||
exporterSecret []byte
|
|
||||||
|
|
||||||
seqNum uint128
|
aead cipher.AEAD
|
||||||
|
baseNonce []byte
|
||||||
|
// seqNum starts at zero and is incremented for each Seal/Open call.
|
||||||
|
// 64 bits are enough not to overflow for 500 years at 1ns per operation.
|
||||||
|
seqNum uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sender is a sending HPKE context. It is instantiated with a specific KEM
|
||||||
|
// encapsulation key (i.e. the public key), and it is stateful, incrementing the
|
||||||
|
// nonce counter for each [Sender.Seal] call.
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
*context
|
*context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recipient is a receiving HPKE context. It is instantiated with a specific KEM
|
||||||
|
// decapsulation key (i.e. the secret key), and it is stateful, incrementing the
|
||||||
|
// nonce counter for each successful [Recipient.Open] call.
|
||||||
type Recipient struct {
|
type Recipient struct {
|
||||||
*context
|
*context
|
||||||
}
|
}
|
||||||
|
|
||||||
var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
|
func newContext(sharedSecret []byte, kemID uint16, kdf KDF, aead AEAD, info []byte) (*context, error) {
|
||||||
block, err := aes.NewCipher(key)
|
sid := suiteID(kemID, kdf.ID(), aead.ID())
|
||||||
|
|
||||||
|
pskIDHash, err := kdf.labeledExtract(sid, nil, "psk_id_hash", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cipher.NewGCM(block)
|
infoHash, err := kdf.labeledExtract(sid, nil, "info_hash", info)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} },
|
|
||||||
}
|
|
||||||
|
|
||||||
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, errors.New("unsupported KDF id")
|
|
||||||
}
|
|
||||||
kdf := kdfInit()
|
|
||||||
|
|
||||||
aeadInfo, ok := SupportedAEADs[aeadID]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unsupported AEAD id")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ksContext := append([]byte{0}, pskIDHash...)
|
ksContext := append([]byte{0}, pskIDHash...)
|
||||||
ksContext = append(ksContext, infoHash...)
|
ksContext = append(ksContext, infoHash...)
|
||||||
|
|
||||||
secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
|
secret, err := kdf.labeledExtract(sid, sharedSecret, "secret", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
|
key, err := kdf.labeledExpand(sid, secret, "key", ksContext, uint16(aead.keySize()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
baseNonce, err := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */)
|
a, err := aead.aead(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
exporterSecret, err := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/)
|
baseNonce, err := kdf.labeledExpand(sid, secret, "base_nonce", ksContext, uint16(aead.nonceSize()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
expSecret, err := kdf.labeledExpand(sid, secret, "exp", ksContext, uint16(len(secret)))
|
||||||
aead, err := aeadInfo.aead(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
export := func(exporterContext string, length uint16) ([]byte, error) {
|
||||||
|
return kdf.labeledExpand(sid, expSecret, "sec", []byte(exporterContext), length)
|
||||||
|
}
|
||||||
|
|
||||||
return &context{
|
return &context{
|
||||||
aead: aead,
|
aead: a,
|
||||||
sharedSecret: sharedSecret,
|
suiteID: sid,
|
||||||
suiteID: sid,
|
export: export,
|
||||||
key: key,
|
baseNonce: baseNonce,
|
||||||
baseNonce: baseNonce,
|
|
||||||
exporterSecret: exporterSecret,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) {
|
// NewSender returns a sending HPKE context for the provided KEM encapsulation
|
||||||
kem, err := newDHKem(kemID)
|
// key (i.e. the public key), and using the ciphersuite defined by the
|
||||||
|
// combination of KEM, KDF, and AEAD.
|
||||||
|
//
|
||||||
|
// The info parameter is additional public information that must match between
|
||||||
|
// sender and recipient.
|
||||||
|
//
|
||||||
|
// The returned enc ciphertext can be used to instantiate a matching receiving
|
||||||
|
// HPKE context with the corresponding KEM decapsulation key.
|
||||||
|
func NewSender(kem KEMSender, kdf KDF, aead AEAD, info []byte) (enc []byte, s *Sender, err error) {
|
||||||
|
sharedSecret, encapsulatedKey, err := kem.encap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
sharedSecret, encapsulatedKey, err := kem.Encap(pub)
|
context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encapsulatedKey, &Sender{context}, nil
|
return encapsulatedKey, &Sender{context}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRecipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Recipient, error) {
|
// NewRecipient returns a receiving HPKE context for the provided KEM
|
||||||
kem, err := newDHKem(kemID)
|
// decapsulation key (i.e. the secret key), and using the ciphersuite defined by
|
||||||
|
// the combination of KEM, KDF, and AEAD.
|
||||||
|
//
|
||||||
|
// The enc parameter must have been produced by a matching sending HPKE context
|
||||||
|
// with the corresponding KEM encapsulation key. The info parameter is
|
||||||
|
// additional public information that must match between sender and recipient.
|
||||||
|
func NewRecipient(enc []byte, kem KEMRecipient, kdf KDF, aead AEAD, info []byte) (*Recipient, error) {
|
||||||
|
sharedSecret, err := kem.decap(enc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sharedSecret, err := kem.Decap(encPubEph, priv)
|
context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Recipient{context}, nil
|
return &Recipient{context}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seal encrypts the provided plaintext, optionally binding to the additional
|
||||||
|
// public data aad.
|
||||||
|
//
|
||||||
|
// Seal uses incrementing counters for each call, and Open on the receiving side
|
||||||
|
// must be called in the same order as Seal.
|
||||||
|
func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
|
||||||
|
if s.aead == nil {
|
||||||
|
return nil, errors.New("export-only instantiation")
|
||||||
|
}
|
||||||
|
ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
|
||||||
|
s.seqNum++
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal instantiates a single-use HPKE sending HPKE context like [NewSender],
|
||||||
|
// and then encrypts the provided plaintext like [Sender.Seal] (with no aad).
|
||||||
|
func Seal(kem KEMSender, kdf KDF, aead AEAD, info, plaintext []byte) (enc, ct []byte, err error) {
|
||||||
|
enc, s, err := NewSender(kem, kdf, aead, info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ct, err = s.Seal(nil, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return enc, ct, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export produces a secret value derived from the shared key between sender and
|
||||||
|
// recipient. length must be at most 65,535.
|
||||||
|
func (s *Sender) Export(exporterContext string, length int) ([]byte, error) {
|
||||||
|
if length < 0 || length > 0xFFFF {
|
||||||
|
return nil, errors.New("invalid length")
|
||||||
|
}
|
||||||
|
return s.export(exporterContext, uint16(length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open decrypts the provided ciphertext, optionally binding to the additional
|
||||||
|
// public data aad, or returns an error if decryption fails.
|
||||||
|
//
|
||||||
|
// Open uses incrementing counters for each successful call, and must be called
|
||||||
|
// in the same order as Seal on the sending side.
|
||||||
|
func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) {
|
||||||
|
if r.aead == nil {
|
||||||
|
return nil, errors.New("export-only instantiation")
|
||||||
|
}
|
||||||
|
plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.seqNum++
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open instantiates a single-use HPKE receiving HPKE context like [NewRecipient],
|
||||||
|
// and then decrypts the provided ciphertext like [Recipient.Open] (with no aad).
|
||||||
|
func Open(enc []byte, kem KEMRecipient, kdf KDF, aead AEAD, info, ciphertext []byte) ([]byte, error) {
|
||||||
|
r, err := NewRecipient(enc, kem, kdf, aead, info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.Open(nil, ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export produces a secret value derived from the shared key between sender and
|
||||||
|
// recipient. length must be at most 65,535.
|
||||||
|
func (r *Recipient) Export(exporterContext string, length int) ([]byte, error) {
|
||||||
|
if length < 0 || length > 0xFFFF {
|
||||||
|
return nil, errors.New("invalid length")
|
||||||
|
}
|
||||||
|
return r.export(exporterContext, uint16(length))
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *context) nextNonce() []byte {
|
func (ctx *context) nextNonce() []byte {
|
||||||
nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():]
|
nonce := make([]byte, ctx.aead.NonceSize())
|
||||||
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], ctx.seqNum)
|
||||||
for i := range ctx.baseNonce {
|
for i := range ctx.baseNonce {
|
||||||
nonce[i] ^= ctx.baseNonce[i]
|
nonce[i] ^= ctx.baseNonce[i]
|
||||||
}
|
}
|
||||||
return nonce
|
return nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (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 {
|
func suiteID(kemID, kdfID, aeadID uint16) []byte {
|
||||||
suiteID := make([]byte, 0, 4+2+2+2)
|
suiteID := make([]byte, 0, 4+2+2+2)
|
||||||
suiteID = append(suiteID, []byte("HPKE")...)
|
suiteID = append(suiteID, []byte("HPKE")...)
|
||||||
suiteID = byteorder.BEAppendUint16(suiteID, kemID)
|
suiteID = binary.BigEndian.AppendUint16(suiteID, kemID)
|
||||||
suiteID = byteorder.BEAppendUint16(suiteID, kdfID)
|
suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID)
|
||||||
suiteID = byteorder.BEAppendUint16(suiteID, aeadID)
|
suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID)
|
||||||
return suiteID
|
return suiteID
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) {
|
|
||||||
kemInfo, ok := SupportedKEMs[kemID]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unsupported KEM id")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u uint128) addOne() uint128 {
|
|
||||||
lo, carry := bits.Add64(u.lo, 1, 0)
|
|
||||||
return uint128{u.hi + carry, lo}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u uint128) bitLen() int {
|
|
||||||
return bits.Len64(u.hi) + bits.Len64(u.lo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u uint128) bytes() []byte {
|
|
||||||
b := make([]byte, 16)
|
|
||||||
byteorder.BEPutUint64(b[0:], u.hi)
|
|
||||||
byteorder.BEPutUint64(b[8:], u.lo)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,14 @@ package hpke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/sha3"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"crypto/ecdh"
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustDecodeHex(t *testing.T, in string) []byte {
|
func mustDecodeHex(t *testing.T, in string) []byte {
|
||||||
|
|
@ -27,169 +25,244 @@ func mustDecodeHex(t *testing.T, in string) []byte {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVectorSetup(vector string) map[string]string {
|
func TestVectors(t *testing.T) {
|
||||||
vals := map[string]string{}
|
vectorsJSON, err := os.ReadFile("testdata/rfc9180.json")
|
||||||
for _, l := range strings.Split(vector, "\n") {
|
|
||||||
fields := strings.Split(l, ": ")
|
|
||||||
vals[fields[0]] = fields[1]
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseVectorEncryptions(vector string) []map[string]string {
|
|
||||||
vals := []map[string]string{}
|
|
||||||
for _, section := range strings.Split(vector, "\n\n") {
|
|
||||||
e := map[string]string{}
|
|
||||||
for _, l := range strings.Split(section, "\n") {
|
|
||||||
fields := strings.Split(l, ": ")
|
|
||||||
e[fields[0]] = fields[1]
|
|
||||||
}
|
|
||||||
vals = append(vals, e)
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRFC9180Vectors(t *testing.T) {
|
|
||||||
vectorsJSON, err := os.ReadFile("testdata/rfc9180-vectors.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var vectors []struct {
|
var vectors []struct {
|
||||||
Name string
|
Mode uint16 `json:"mode"`
|
||||||
Setup string
|
KEM uint16 `json:"kem_id"`
|
||||||
Encryptions string
|
KDF uint16 `json:"kdf_id"`
|
||||||
|
AEAD uint16 `json:"aead_id"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
IkmE string `json:"ikmE"`
|
||||||
|
IkmR string `json:"ikmR"`
|
||||||
|
SkRm string `json:"skRm"`
|
||||||
|
PkRm string `json:"pkRm"`
|
||||||
|
Enc string `json:"enc"`
|
||||||
|
Encryptions string `json:"encryptions"`
|
||||||
|
Exports string `json:"exports"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(vectorsJSON, &vectors); err != nil {
|
if err := json.Unmarshal(vectorsJSON, &vectors); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vector := range vectors {
|
for _, vector := range vectors {
|
||||||
t.Run(vector.Name, func(t *testing.T) {
|
name := fmt.Sprintf("mode %04x kem %04x kdf %04x aead %04x",
|
||||||
setup := parseVectorSetup(vector.Setup)
|
vector.Mode, vector.KEM, vector.KDF, vector.AEAD)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
kemID, err := strconv.Atoi(setup["kem_id"])
|
if vector.Mode != 0 {
|
||||||
if err != nil {
|
t.Skip("only mode 0 (base) is supported")
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
if _, ok := SupportedKEMs[uint16(kemID)]; !ok {
|
if vector.KEM == 0x0021 {
|
||||||
t.Skip("unsupported KEM")
|
t.Skip("KEM 0x0021 (DHKEM(X448)) not supported")
|
||||||
}
|
|
||||||
kdfID, err := strconv.Atoi(setup["kdf_id"])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, ok := SupportedKDFs[uint16(kdfID)]; !ok {
|
|
||||||
t.Skip("unsupported KDF")
|
|
||||||
}
|
|
||||||
aeadID, err := strconv.Atoi(setup["aead_id"])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, ok := SupportedAEADs[uint16(aeadID)]; !ok {
|
|
||||||
t.Skip("unsupported AEAD")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info := mustDecodeHex(t, setup["info"])
|
kdf, err := NewKDF(vector.KDF)
|
||||||
pubKeyBytes := mustDecodeHex(t, setup["pkRm"])
|
if err != nil {
|
||||||
pub, err := ParseHPKEPublicKey(uint16(kemID), pubKeyBytes)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if kdf.ID() != vector.KDF {
|
||||||
|
t.Errorf("unexpected KDF ID: got %04x, want %04x", kdf.ID(), vector.KDF)
|
||||||
|
}
|
||||||
|
|
||||||
|
aead, err := NewAEAD(vector.AEAD)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if aead.ID() != vector.AEAD {
|
||||||
|
t.Errorf("unexpected AEAD ID: got %04x, want %04x", aead.ID(), vector.AEAD)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyBytes := mustDecodeHex(t, vector.PkRm)
|
||||||
|
kemSender, err := NewKEMSender(vector.KEM, pubKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if kemSender.ID() != vector.KEM {
|
||||||
|
t.Errorf("unexpected KEM ID: got %04x, want %04x", kemSender.ID(), vector.KEM)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(kemSender.Bytes(), pubKeyBytes) {
|
||||||
|
t.Errorf("unexpected KEM bytes: got %x, want %x", kemSender.Bytes(), pubKeyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
ikmE := mustDecodeHex(t, vector.IkmE)
|
||||||
|
setupDerandomizedEncap(t, vector.KEM, ikmE)
|
||||||
|
|
||||||
|
info := mustDecodeHex(t, vector.Info)
|
||||||
|
encap, sender, err := NewSender(kemSender, kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ephemeralPrivKey := mustDecodeHex(t, setup["skEm"])
|
expectedEncap := mustDecodeHex(t, vector.Enc)
|
||||||
|
|
||||||
testingOnlyGenerateKey = func() (*ecdh.PrivateKey, error) {
|
|
||||||
return SupportedKEMs[uint16(kemID)].curve.NewPrivateKey(ephemeralPrivKey)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { testingOnlyGenerateKey = nil })
|
|
||||||
|
|
||||||
encap, sender, err := SetupSender(
|
|
||||||
uint16(kemID),
|
|
||||||
uint16(kdfID),
|
|
||||||
uint16(aeadID),
|
|
||||||
pub,
|
|
||||||
info,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedEncap := mustDecodeHex(t, setup["enc"])
|
|
||||||
if !bytes.Equal(encap, expectedEncap) {
|
if !bytes.Equal(encap, expectedEncap) {
|
||||||
t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap)
|
t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap)
|
||||||
}
|
}
|
||||||
|
|
||||||
privKeyBytes := mustDecodeHex(t, setup["skRm"])
|
privKeyBytes := mustDecodeHex(t, vector.SkRm)
|
||||||
priv, err := ParseHPKEPrivateKey(uint16(kemID), privKeyBytes)
|
kemRecipient, err := NewKEMRecipient(vector.KEM, privKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if kemRecipient.ID() != vector.KEM {
|
||||||
|
t.Errorf("unexpected KEM ID: got %04x, want %04x", kemRecipient.ID(), vector.KEM)
|
||||||
|
}
|
||||||
|
kemRecipientBytes, err := kemRecipient.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// X25519 serialized keys must be clamped, so the bytes might not match.
|
||||||
|
if !bytes.Equal(kemRecipientBytes, privKeyBytes) && vector.KEM != dhkemX25519 {
|
||||||
|
t.Errorf("unexpected KEM bytes: got %x, want %x", kemRecipientBytes, privKeyBytes)
|
||||||
|
}
|
||||||
|
if vector.KEM == dhkemX25519 {
|
||||||
|
kem2, err := NewKEMRecipient(vector.KEM, kemRecipientBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
kemRecipientBytes2, err := kem2.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(kemRecipientBytes2, kemRecipientBytes) {
|
||||||
|
t.Errorf("X25519 re-serialized key differs: got %x, want %x", kemRecipientBytes2, kemRecipientBytes)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(kem2.KEMSender().Bytes(), pubKeyBytes) {
|
||||||
|
t.Errorf("X25519 re-derived public key differs: got %x, want %x", kem2.KEMSender().Bytes(), pubKeyBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !bytes.Equal(kemRecipient.KEMSender().Bytes(), pubKeyBytes) {
|
||||||
|
t.Errorf("unexpected KEM sender bytes: got %x, want %x", kemRecipient.KEMSender().Bytes(), pubKeyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
seed := mustDecodeHex(t, vector.IkmR)
|
||||||
|
seedRecipient, err := NewKEMRecipientFromSeed(vector.KEM, seed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
seedRecipientBytes, err := seedRecipient.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(seedRecipientBytes, privKeyBytes) && vector.KEM != 0x0020 {
|
||||||
|
t.Errorf("unexpected KEM bytes from seed: got %x, want %x", seedRecipientBytes, privKeyBytes)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(seedRecipient.KEMSender().Bytes(), pubKeyBytes) {
|
||||||
|
t.Errorf("unexpected KEM sender bytes from seed: got %x, want %x", seedRecipient.KEMSender().Bytes(), pubKeyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipient, err := NewRecipient(encap, kemRecipient, kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient, err := SetupRecipient(
|
if aead != ExportOnly() {
|
||||||
uint16(kemID),
|
source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
|
||||||
uint16(kdfID),
|
for range 1000 {
|
||||||
uint16(aeadID),
|
aad, plaintext := drawRandomInput(t, source), drawRandomInput(t, source)
|
||||||
priv,
|
ciphertext, err := sender.Seal(aad, plaintext)
|
||||||
info,
|
if err != nil {
|
||||||
encap,
|
t.Fatal(err)
|
||||||
)
|
}
|
||||||
if err != nil {
|
sink.Write(ciphertext)
|
||||||
t.Fatal(err)
|
got, err := recipient.Open(aad, ciphertext)
|
||||||
}
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
for _, ctx := range []*context{sender.context, recipient.context} {
|
}
|
||||||
expectedSharedSecret := mustDecodeHex(t, setup["shared_secret"])
|
if !bytes.Equal(got, plaintext) {
|
||||||
if !bytes.Equal(ctx.sharedSecret, expectedSharedSecret) {
|
t.Errorf("unexpected plaintext: got %x want %x", got, plaintext)
|
||||||
t.Errorf("unexpected shared secret, got: %x, want %x", ctx.sharedSecret, expectedSharedSecret)
|
}
|
||||||
}
|
}
|
||||||
expectedKey := mustDecodeHex(t, setup["key"])
|
encryptions := make([]byte, 16)
|
||||||
if !bytes.Equal(ctx.key, expectedKey) {
|
sink.Read(encryptions)
|
||||||
t.Errorf("unexpected key, got: %x, want %x", ctx.key, expectedKey)
|
expectedEncryptions := mustDecodeHex(t, vector.Encryptions)
|
||||||
|
if !bytes.Equal(encryptions, expectedEncryptions) {
|
||||||
|
t.Errorf("unexpected accumulated encryptions, got: %x, want %x", encryptions, expectedEncryptions)
|
||||||
}
|
}
|
||||||
expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"])
|
} else {
|
||||||
if !bytes.Equal(ctx.baseNonce, expectedBaseNonce) {
|
if _, err := sender.Seal(nil, nil); err == nil {
|
||||||
t.Errorf("unexpected base nonce, got: %x, want %x", ctx.baseNonce, expectedBaseNonce)
|
t.Error("expected error from Seal with export-only AEAD")
|
||||||
}
|
}
|
||||||
expectedExporterSecret := mustDecodeHex(t, setup["exporter_secret"])
|
if _, err := recipient.Open(nil, nil); err == nil {
|
||||||
if !bytes.Equal(ctx.exporterSecret, expectedExporterSecret) {
|
t.Error("expected error from Open with export-only AEAD")
|
||||||
t.Errorf("unexpected exporter secret, got: %x, want %x", ctx.exporterSecret, expectedExporterSecret)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, enc := range parseVectorEncryptions(vector.Encryptions) {
|
source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
|
||||||
t.Run("seq num "+enc["sequence number"], func(t *testing.T) {
|
for l := range 1000 {
|
||||||
seqNum, err := strconv.Atoi(enc["sequence number"])
|
context := string(drawRandomInput(t, source))
|
||||||
if err != nil {
|
value, err := sender.Export(context, l)
|
||||||
t.Fatal(err)
|
if err != nil {
|
||||||
}
|
t.Fatal(err)
|
||||||
sender.seqNum = uint128{lo: uint64(seqNum)}
|
}
|
||||||
recipient.seqNum = uint128{lo: uint64(seqNum)}
|
sink.Write(value)
|
||||||
expectedNonce := mustDecodeHex(t, enc["nonce"])
|
got, err := recipient.Export(context, l)
|
||||||
computedNonce := sender.nextNonce()
|
if err != nil {
|
||||||
if !bytes.Equal(computedNonce, expectedNonce) {
|
t.Fatal(err)
|
||||||
t.Errorf("unexpected nonce: got %x, want %x", computedNonce, expectedNonce)
|
}
|
||||||
}
|
if !bytes.Equal(got, value) {
|
||||||
|
t.Errorf("recipient: unexpected exported secret: got %x want %x", got, value)
|
||||||
expectedCiphertext := mustDecodeHex(t, enc["ct"])
|
}
|
||||||
ciphertext, err := sender.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"]))
|
}
|
||||||
if err != nil {
|
exports := make([]byte, 16)
|
||||||
t.Fatal(err)
|
sink.Read(exports)
|
||||||
}
|
expectedExports := mustDecodeHex(t, vector.Exports)
|
||||||
if !bytes.Equal(ciphertext, expectedCiphertext) {
|
if !bytes.Equal(exports, expectedExports) {
|
||||||
t.Errorf("unexpected ciphertext: got %x want %x", ciphertext, expectedCiphertext)
|
t.Errorf("unexpected accumulated exports, got: %x, want %x", exports, expectedExports)
|
||||||
}
|
|
||||||
|
|
||||||
expectedPlaintext := mustDecodeHex(t, enc["pt"])
|
|
||||||
plaintext, err := recipient.Open(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["ct"]))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(plaintext, expectedPlaintext) {
|
|
||||||
t.Errorf("unexpected plaintext: got %x want %x", plaintext, expectedPlaintext)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func drawRandomInput(t *testing.T, r io.Reader) []byte {
|
||||||
|
t.Helper()
|
||||||
|
l := make([]byte, 1)
|
||||||
|
if _, err := r.Read(l); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n := int(l[0])
|
||||||
|
b := make([]byte, n)
|
||||||
|
if _, err := r.Read(b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDerandomizedEncap(t *testing.T, kemID uint16, randBytes []byte) {
|
||||||
|
r, err := NewKEMRecipientFromSeed(kemID, randBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testingOnlyGenerateKey = func() *ecdh.PrivateKey {
|
||||||
|
return r.(*dhKEMRecipient).priv.(*ecdh.PrivateKey)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
testingOnlyGenerateKey = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSingletons(t *testing.T) {
|
||||||
|
if HKDFSHA256() != HKDFSHA256() {
|
||||||
|
t.Error("HKDFSHA256() != HKDFSHA256()")
|
||||||
|
}
|
||||||
|
if HKDFSHA384() != HKDFSHA384() {
|
||||||
|
t.Error("HKDFSHA384() != HKDFSHA384()")
|
||||||
|
}
|
||||||
|
if HKDFSHA512() != HKDFSHA512() {
|
||||||
|
t.Error("HKDFSHA512() != HKDFSHA512()")
|
||||||
|
}
|
||||||
|
if AES128GCM() != AES128GCM() {
|
||||||
|
t.Error("AES128GCM() != AES128GCM()")
|
||||||
|
}
|
||||||
|
if AES256GCM() != AES256GCM() {
|
||||||
|
t.Error("AES256GCM() != AES256GCM()")
|
||||||
|
}
|
||||||
|
if ChaCha20Poly1305() != ChaCha20Poly1305() {
|
||||||
|
t.Error("ChaCha20Poly1305() != ChaCha20Poly1305()")
|
||||||
|
}
|
||||||
|
if ExportOnly() != ExportOnly() {
|
||||||
|
t.Error("ExportOnly() != ExportOnly()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
80
src/crypto/internal/hpke/kdf.go
Normal file
80
src/crypto/internal/hpke/kdf.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package hpke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hkdf"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The KDF is one of the three components of an HPKE ciphersuite, implementing
|
||||||
|
// key derivation.
|
||||||
|
type KDF interface {
|
||||||
|
ID() uint16
|
||||||
|
labeledExtract(sid, salt []byte, label string, inputKey []byte) ([]byte, error)
|
||||||
|
labeledExpand(suiteID, randomKey []byte, label string, info []byte, length uint16) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKDF returns the KDF implementation for the given KDF ID.
|
||||||
|
//
|
||||||
|
// Applications are encouraged to use specific implementations like [HKDFSHA256]
|
||||||
|
// instead, unless runtime agility is required.
|
||||||
|
func NewKDF(id uint16) (KDF, error) {
|
||||||
|
switch id {
|
||||||
|
case 0x0001: // HKDF-SHA256
|
||||||
|
return HKDFSHA256(), nil
|
||||||
|
case 0x0002: // HKDF-SHA384
|
||||||
|
return HKDFSHA384(), nil
|
||||||
|
case 0x0003: // HKDF-SHA512
|
||||||
|
return HKDFSHA512(), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported KDF %04x", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKDFSHA256 returns an HKDF-SHA256 KDF implementation.
|
||||||
|
func HKDFSHA256() KDF { return hkdfSHA256 }
|
||||||
|
|
||||||
|
// HKDFSHA384 returns an HKDF-SHA384 KDF implementation.
|
||||||
|
func HKDFSHA384() KDF { return hkdfSHA384 }
|
||||||
|
|
||||||
|
// HKDFSHA512 returns an HKDF-SHA512 KDF implementation.
|
||||||
|
func HKDFSHA512() KDF { return hkdfSHA512 }
|
||||||
|
|
||||||
|
type hkdfKDF struct {
|
||||||
|
hash func() hash.Hash
|
||||||
|
id uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var hkdfSHA256 = &hkdfKDF{hash: sha256.New, id: 0x0001}
|
||||||
|
var hkdfSHA384 = &hkdfKDF{hash: sha512.New384, id: 0x0002}
|
||||||
|
var hkdfSHA512 = &hkdfKDF{hash: sha512.New, id: 0x0003}
|
||||||
|
|
||||||
|
func (kdf *hkdfKDF) ID() uint16 {
|
||||||
|
return kdf.id
|
||||||
|
}
|
||||||
|
|
||||||
|
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, sid...)
|
||||||
|
labeledIKM = append(labeledIKM, label...)
|
||||||
|
labeledIKM = append(labeledIKM, inputKey...)
|
||||||
|
return hkdf.Extract(kdf.hash, labeledIKM, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
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...)
|
||||||
|
return hkdf.Expand(kdf.hash, randomKey, string(labeledInfo), int(length))
|
||||||
|
}
|
||||||
355
src/crypto/internal/hpke/kem.go
Normal file
355
src/crypto/internal/hpke/kem.go
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package hpke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dhkemP256 = 0x0010 // DHKEM(P-256, HKDF-SHA256)
|
||||||
|
dhkemP384 = 0x0011 // DHKEM(P-384, HKDF-SHA384)
|
||||||
|
dhkemP521 = 0x0012 // DHKEM(P-521, HKDF-SHA512)
|
||||||
|
dhkemX25519 = 0x0020 // DHKEM(X25519, HKDF-SHA256)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A KEMSender is an instantiation of a KEM (one of the three components of an
|
||||||
|
// HPKE ciphersuite) with an encapsulation key (i.e. the public key).
|
||||||
|
type KEMSender interface {
|
||||||
|
// ID returns the HPKE KEM identifier.
|
||||||
|
ID() uint16
|
||||||
|
|
||||||
|
// Bytes returns the public key as the output of SerializePublicKey.
|
||||||
|
Bytes() []byte
|
||||||
|
|
||||||
|
encap() (sharedSecret, enc []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKEMSender implements DeserializePublicKey and returns a KEMSender
|
||||||
|
// for the given KEM ID and public key bytes.
|
||||||
|
//
|
||||||
|
// Applications are encouraged to use [ecdh.Curve.NewPublicKey] with
|
||||||
|
// [NewECDHSender] instead, unless runtime agility is required.
|
||||||
|
func NewKEMSender(id uint16, pub []byte) (KEMSender, error) {
|
||||||
|
switch id {
|
||||||
|
case dhkemP256:
|
||||||
|
k, err := ecdh.P256().NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHSender(k)
|
||||||
|
case dhkemP384:
|
||||||
|
k, err := ecdh.P384().NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHSender(k)
|
||||||
|
case dhkemP521:
|
||||||
|
k, err := ecdh.P521().NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHSender(k)
|
||||||
|
case dhkemX25519:
|
||||||
|
k, err := ecdh.X25519().NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHSender(k)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported KEM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A KEMRecipient is an instantiation of a KEM (one of the three components of
|
||||||
|
// an HPKE ciphersuite) with a decapsulation key (i.e. the secret key).
|
||||||
|
type KEMRecipient interface {
|
||||||
|
// ID returns the HPKE KEM identifier.
|
||||||
|
ID() uint16
|
||||||
|
|
||||||
|
// Bytes returns the private key as the output of SerializePrivateKey, as
|
||||||
|
// defined in RFC 9180.
|
||||||
|
//
|
||||||
|
// Note that for X25519 this might not match the input to NewPrivateKey.
|
||||||
|
// This is a requirement of RFC 9180, Section 7.1.2.
|
||||||
|
Bytes() ([]byte, error)
|
||||||
|
|
||||||
|
// KEMSender returns the corresponding KEMSender for this recipient.
|
||||||
|
KEMSender() KEMSender
|
||||||
|
|
||||||
|
decap(enc []byte) (sharedSecret []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKEMRecipient implements DeserializePrivateKey, as defined in RFC 9180, and
|
||||||
|
// returns a KEMRecipient for the given KEM ID and private key bytes.
|
||||||
|
//
|
||||||
|
// Applications are encouraged to use [ecdh.Curve.NewPrivateKey] with
|
||||||
|
// [NewECDHRecipient] instead, unless runtime agility is required.
|
||||||
|
func NewKEMRecipient(id uint16, priv []byte) (KEMRecipient, error) {
|
||||||
|
switch id {
|
||||||
|
case dhkemP256:
|
||||||
|
k, err := ecdh.P256().NewPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHRecipient(k)
|
||||||
|
case dhkemP384:
|
||||||
|
k, err := ecdh.P384().NewPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHRecipient(k)
|
||||||
|
case dhkemP521:
|
||||||
|
k, err := ecdh.P521().NewPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHRecipient(k)
|
||||||
|
case dhkemX25519:
|
||||||
|
k, err := ecdh.X25519().NewPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDHRecipient(k)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported KEM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKEMRecipientFromSeed implements DeriveKeyPair, as defined in RFC 9180, and
|
||||||
|
// returns a KEMRecipient for the given KEM ID and private key seed.
|
||||||
|
//
|
||||||
|
// Currently, it only supports the KEMs based on ECDH (DHKEM).
|
||||||
|
func NewKEMRecipientFromSeed(id uint16, seed []byte) (KEMRecipient, error) {
|
||||||
|
// DeriveKeyPair from RFC 9180 Section 7.1.3.
|
||||||
|
var curve ecdh.Curve
|
||||||
|
var dh dhKEM
|
||||||
|
var Nsk uint16
|
||||||
|
switch id {
|
||||||
|
case dhkemP256:
|
||||||
|
curve = ecdh.P256()
|
||||||
|
dh, _ = dhKEMForCurve(curve)
|
||||||
|
Nsk = 32
|
||||||
|
case dhkemP384:
|
||||||
|
curve = ecdh.P384()
|
||||||
|
dh, _ = dhKEMForCurve(curve)
|
||||||
|
Nsk = 48
|
||||||
|
case dhkemP521:
|
||||||
|
curve = ecdh.P521()
|
||||||
|
dh, _ = dhKEMForCurve(curve)
|
||||||
|
Nsk = 66
|
||||||
|
case dhkemX25519:
|
||||||
|
curve = ecdh.X25519()
|
||||||
|
dh, _ = dhKEMForCurve(curve)
|
||||||
|
Nsk = 32
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported KEM")
|
||||||
|
}
|
||||||
|
suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), dh.id)
|
||||||
|
prk, err := dh.kdf.labeledExtract(suiteID, nil, "dkp_prk", seed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if id == dhkemX25519 {
|
||||||
|
s, err := dh.kdf.labeledExpand(suiteID, prk, "sk", nil, Nsk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewKEMRecipient(id, s)
|
||||||
|
}
|
||||||
|
var counter uint8
|
||||||
|
for counter < 4 {
|
||||||
|
s, err := dh.kdf.labeledExpand(suiteID, prk, "candidate", []byte{counter}, Nsk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if id == dhkemP521 {
|
||||||
|
s[0] &= 0x01
|
||||||
|
}
|
||||||
|
r, err := NewKEMRecipient(id, s)
|
||||||
|
if err != nil {
|
||||||
|
counter++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
panic("chance of four rejections is < 2^-128")
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhKEM struct {
|
||||||
|
kdf KDF
|
||||||
|
id uint16
|
||||||
|
nSecret uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEM) extractAndExpand(dhKey, kemContext []byte) ([]byte, error) {
|
||||||
|
suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), dh.id)
|
||||||
|
eaePRK, err := dh.kdf.labeledExtract(suiteID, nil, "eae_prk", dhKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dh.kdf.labeledExpand(suiteID, eaePRK, "shared_secret", kemContext, dh.nSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEM) ID() uint16 {
|
||||||
|
return dh.id
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhKEMSender struct {
|
||||||
|
dhKEM
|
||||||
|
pub *ecdh.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDHSender returns a KEMSender implementing one of
|
||||||
|
//
|
||||||
|
// - DHKEM(P-256, HKDF-SHA256)
|
||||||
|
// - DHKEM(P-384, HKDF-SHA384)
|
||||||
|
// - DHKEM(P-521, HKDF-SHA512)
|
||||||
|
// - DHKEM(X25519, HKDF-SHA256)
|
||||||
|
//
|
||||||
|
// depending on the underlying curve of the provided public key.
|
||||||
|
func NewECDHSender(pub *ecdh.PublicKey) (KEMSender, error) {
|
||||||
|
dhKEM, err := dhKEMForCurve(pub.Curve())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dhKEMSender{
|
||||||
|
pub: pub,
|
||||||
|
dhKEM: dhKEM,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dhKEMForCurve(curve ecdh.Curve) (dhKEM, error) {
|
||||||
|
switch curve {
|
||||||
|
case ecdh.P256():
|
||||||
|
return dhKEM{
|
||||||
|
kdf: HKDFSHA256(),
|
||||||
|
id: dhkemP256,
|
||||||
|
nSecret: 32,
|
||||||
|
}, nil
|
||||||
|
case ecdh.P384():
|
||||||
|
return dhKEM{
|
||||||
|
kdf: HKDFSHA384(),
|
||||||
|
id: dhkemP384,
|
||||||
|
nSecret: 48,
|
||||||
|
}, nil
|
||||||
|
case ecdh.P521():
|
||||||
|
return dhKEM{
|
||||||
|
kdf: HKDFSHA512(),
|
||||||
|
id: dhkemP521,
|
||||||
|
nSecret: 64,
|
||||||
|
}, nil
|
||||||
|
case ecdh.X25519():
|
||||||
|
return dhKEM{
|
||||||
|
kdf: HKDFSHA256(),
|
||||||
|
id: dhkemX25519,
|
||||||
|
nSecret: 32,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return dhKEM{}, errors.New("unsupported curve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEMSender) Bytes() []byte {
|
||||||
|
return dh.pub.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// testingOnlyGenerateKey is only used during testing, to provide
|
||||||
|
// a fixed test key to use when checking the RFC 9180 vectors.
|
||||||
|
var testingOnlyGenerateKey func() *ecdh.PrivateKey
|
||||||
|
|
||||||
|
func (dh *dhKEMSender) encap() (sharedSecret []byte, encapPub []byte, err error) {
|
||||||
|
privEph, err := dh.pub.Curve().GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if testingOnlyGenerateKey != nil {
|
||||||
|
privEph = testingOnlyGenerateKey()
|
||||||
|
}
|
||||||
|
dhVal, err := privEph.ECDH(dh.pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
encPubEph := privEph.PublicKey().Bytes()
|
||||||
|
|
||||||
|
encPubRecip := dh.pub.Bytes()
|
||||||
|
kemContext := append(encPubEph, encPubRecip...)
|
||||||
|
sharedSecret, err = dh.extractAndExpand(dhVal, kemContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sharedSecret, encPubEph, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhKEMRecipient struct {
|
||||||
|
dhKEM
|
||||||
|
priv ecdh.KeyExchanger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDHRecipient returns a KEMRecipient implementing one of
|
||||||
|
//
|
||||||
|
// - DHKEM(P-256, HKDF-SHA256)
|
||||||
|
// - DHKEM(P-384, HKDF-SHA384)
|
||||||
|
// - DHKEM(P-521, HKDF-SHA512)
|
||||||
|
// - DHKEM(X25519, HKDF-SHA256)
|
||||||
|
//
|
||||||
|
// depending on the underlying curve of the provided private key.
|
||||||
|
func NewECDHRecipient(priv ecdh.KeyExchanger) (KEMRecipient, error) {
|
||||||
|
dhKEM, err := dhKEMForCurve(priv.Curve())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dhKEMRecipient{
|
||||||
|
priv: priv,
|
||||||
|
dhKEM: dhKEM,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEMRecipient) Bytes() ([]byte, error) {
|
||||||
|
// Bizarrely, RFC 9180, Section 7.1.2 says SerializePrivateKey MUST clamp
|
||||||
|
// the output, which I thought we all agreed to instead do as part of the DH
|
||||||
|
// function, letting private keys be random bytes.
|
||||||
|
//
|
||||||
|
// At the same time, it says DeserializePrivateKey MUST also clamp, implying
|
||||||
|
// that the input doesn't have to be clamped, so Bytes by spec doesn't
|
||||||
|
// necessarily match the NewPrivateKey input.
|
||||||
|
//
|
||||||
|
// I'm sure this will not lead to any unexpected behavior or interop issue.
|
||||||
|
priv, ok := dh.priv.(*ecdh.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("ecdh: private key does not support Bytes")
|
||||||
|
}
|
||||||
|
if dh.id == dhkemX25519 {
|
||||||
|
b := priv.Bytes()
|
||||||
|
b[0] &= 248
|
||||||
|
b[31] &= 127
|
||||||
|
b[31] |= 64
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
return priv.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEMRecipient) KEMSender() KEMSender {
|
||||||
|
return &dhKEMSender{
|
||||||
|
pub: dh.priv.PublicKey(),
|
||||||
|
dhKEM: dh.dhKEM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *dhKEMRecipient) decap(encPubEph []byte) ([]byte, error) {
|
||||||
|
pubEph, err := dh.priv.Curve().NewPublicKey(encPubEph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dhVal, err := dh.priv.ECDH(pubEph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kemContext := append(encPubEph, dh.priv.PublicKey().Bytes()...)
|
||||||
|
return dh.extractAndExpand(dhVal, kemContext)
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
332
src/crypto/internal/hpke/testdata/rfc9180.json
vendored
Normal file
332
src/crypto/internal/hpke/testdata/rfc9180.json
vendored
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234",
|
||||||
|
"ikmR": "6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037",
|
||||||
|
"skRm": "4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8",
|
||||||
|
"pkRm": "3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d",
|
||||||
|
"enc": "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431",
|
||||||
|
"encryptions": "dcabb32ad8e8acea785275323395abd0",
|
||||||
|
"exports": "45db490fc51c86ba46cca1217f66a75e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "2cd7c601cefb3d42a62b04b7a9041494c06c7843818e0ce28a8f704ae7ab20f9",
|
||||||
|
"ikmR": "dac33b0e9db1b59dbbea58d59a14e7b5896e9bdf98fad6891e99d1686492b9ee",
|
||||||
|
"skRm": "497b4502664cfea5d5af0b39934dac72242a74f8480451e1aee7d6a53320333d",
|
||||||
|
"pkRm": "430f4b9859665145a6b1ba274024487bd66f03a2dd577d7753c68d7d7d00c00c",
|
||||||
|
"enc": "6c93e09869df3402d7bf231bf540fadd35cd56be14f97178f0954db94b7fc256",
|
||||||
|
"encryptions": "1702e73e1e71705faa8241022af1deea",
|
||||||
|
"exports": "5cb678bf1c52afbd9afb58b8f7c1ced3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b",
|
||||||
|
"ikmR": "1ac01f181fdf9f352797655161c58b75c656a6cc2716dcb66372da835542e1df",
|
||||||
|
"skRm": "8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb",
|
||||||
|
"pkRm": "4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a",
|
||||||
|
"enc": "1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a",
|
||||||
|
"encryptions": "225fb3d35da3bb25e4371bcee4273502",
|
||||||
|
"exports": "54e2189c04100b583c84452f94eb9a4a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "55bc245ee4efda25d38f2d54d5bb6665291b99f8108a8c4b686c2b14893ea5d9",
|
||||||
|
"ikmR": "683ae0da1d22181e74ed2e503ebf82840deb1d5e872cade20f4b458d99783e31",
|
||||||
|
"skRm": "33d196c830a12f9ac65d6e565a590d80f04ee9b19c83c87f2c170d972a812848",
|
||||||
|
"pkRm": "194141ca6c3c3beb4792cd97ba0ea1faff09d98435012345766ee33aae2d7664",
|
||||||
|
"enc": "e5e8f9bfff6c2f29791fc351d2c25ce1299aa5eaca78a757c0b4fb4bcd830918",
|
||||||
|
"exports": "3fe376e3f9c349bc5eae67bbce867a16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "895221ae20f39cbf46871d6ea162d44b84dd7ba9cc7a3c80f16d6ea4242cd6d4",
|
||||||
|
"ikmR": "59a9b44375a297d452fc18e5bba1a64dec709f23109486fce2d3a5428ed2000a",
|
||||||
|
"skRm": "ddfbb71d7ea8ebd98fa9cc211aa7b535d258fe9ab4a08bc9896af270e35aad35",
|
||||||
|
"pkRm": "adf16c696b87995879b27d470d37212f38a58bfe7f84e6d50db638b8f2c22340",
|
||||||
|
"enc": "8998da4c3d6ade83c53e861a022c046db909f1c31107196ab4c2f4dd37e1a949",
|
||||||
|
"encryptions": "19a0d0fb001f83e7606948507842f913",
|
||||||
|
"exports": "e5d853af841b92602804e7a40c1f2487"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "e72b39232ee9ef9f6537a72afe28f551dbe632006aa1b300a00518883a3f2dc1",
|
||||||
|
"ikmR": "a0484936abc95d587acf7034156229f9970e9dfa76773754e40fb30e53c9de16",
|
||||||
|
"skRm": "bdd8943c1e60191f3ea4e69fc4f322aa1086db9650f1f952fdce88395a4bd1af",
|
||||||
|
"pkRm": "aa7bddcf5ca0b2c0cf760b5dffc62740a8e761ec572032a809bebc87aaf7575e",
|
||||||
|
"enc": "c12ba9fb91d7ebb03057d8bea4398688dcc1d1d1ff3b97f09b96b9bf89bd1e4a",
|
||||||
|
"encryptions": "20402e520fdbfee76b2b0af73d810deb",
|
||||||
|
"exports": "80b7f603f0966ca059dd5e8a7cede735"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "636d1237a5ae674c24caa0c32a980d3218d84f916ba31e16699892d27103a2a9",
|
||||||
|
"ikmR": "969bb169aa9c24a501ee9d962e96c310226d427fb6eb3fc579d9882dbc708315",
|
||||||
|
"skRm": "fad15f488c09c167bd18d8f48f282e30d944d624c5676742ad820119de44ea91",
|
||||||
|
"pkRm": "06aa193a5612d89a1935c33f1fda3109fcdf4b867da4c4507879f184340b0e0e",
|
||||||
|
"enc": "1d38fc578d4209ea0ef3ee5f1128ac4876a9549d74dc2d2f46e75942a6188244",
|
||||||
|
"encryptions": "c03e64ef58b22065f04be776d77e160c",
|
||||||
|
"exports": "fa84b4458d580b5069a1be60b4785eac"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 32,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "3cfbc97dece2c497126df8909efbdd3d56b3bbe97ddf6555c99a04ff4402474c",
|
||||||
|
"ikmR": "dff9a966e02b161472f167c0d4252d400069449e62384beb78111cb596220921",
|
||||||
|
"skRm": "7596739457c72bbd6758c7021cfcb4d2fcd677d1232896b8f00da223c5519c36",
|
||||||
|
"pkRm": "9a83674c1bc12909fd59635ba1445592b82a7c01d4dad3ffc8f3975e76c43732",
|
||||||
|
"enc": "444fbbf83d64fef654dfb2a17997d82ca37cd8aeb8094371da33afb95e0c5b0e",
|
||||||
|
"exports": "7557bdf93eadf06e3682fce3d765277f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e",
|
||||||
|
"ikmR": "668b37171f1072f3cf12ea8a236a45df23fc13b82af3609ad1e354f6ef817550",
|
||||||
|
"skRm": "f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2",
|
||||||
|
"pkRm": "04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0",
|
||||||
|
"enc": "04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4",
|
||||||
|
"encryptions": "fcb852ae6a1e19e874fbd18a199df3e4",
|
||||||
|
"exports": "655be1f8b189a6b103528ac6d28d3109"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "a90d3417c3da9cb6c6ae19b4b5dd6cc9529a4cc24efb7ae0ace1f31887a8cd6c",
|
||||||
|
"ikmR": "a0ce15d49e28bd47a18a97e147582d814b08cbe00109fed5ec27d1b4e9f6f5e3",
|
||||||
|
"skRm": "317f915db7bc629c48fe765587897e01e282d3e8445f79f27f65d031a88082b2",
|
||||||
|
"pkRm": "04abc7e49a4c6b3566d77d0304addc6ed0e98512ffccf505e6a8e3eb25c685136f853148544876de76c0f2ef99cdc3a05ccf5ded7860c7c021238f9e2073d2356c",
|
||||||
|
"enc": "04c06b4f6bebc7bb495cb797ab753f911aff80aefb86fd8b6fcc35525f3ab5f03e0b21bd31a86c6048af3cb2d98e0d3bf01da5cc4c39ff5370d331a4f1f7d5a4e0",
|
||||||
|
"encryptions": "8d3263541fc1695b6e88ff3a1208577c",
|
||||||
|
"exports": "038af0baa5ce3c4c5f371c3823b15217"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "f1f1a3bc95416871539ecb51c3a8f0cf608afb40fbbe305c0a72819d35c33f1f",
|
||||||
|
"ikmR": "61092f3f56994dd424405899154a9918353e3e008171517ad576b900ddb275e7",
|
||||||
|
"skRm": "a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b",
|
||||||
|
"pkRm": "04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a006",
|
||||||
|
"enc": "04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291",
|
||||||
|
"encryptions": "702cdecae9ba5c571c8b00ad1f313dbf",
|
||||||
|
"exports": "2e0951156f1e7718a81be3004d606800"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "3800bb050bb4882791fc6b2361d7adc2543e4e0abbac367cf00a0c4251844350",
|
||||||
|
"ikmR": "c6638d8079a235ea4054885355a7caefee67151c6ff2a04f4ba26d099c3a8b02",
|
||||||
|
"skRm": "62c3868357a464f8461d03aa0182c7cebcde841036aea7230ddc7339f1088346",
|
||||||
|
"pkRm": "046c6bb9e1976402c692fef72552f4aaeedd83a5e5079de3d7ae732da0f397b15921fb9c52c9866affc8e29c0271a35937023a9245982ec18bab1eb157cf16fc33",
|
||||||
|
"enc": "04d804370b7e24b94749eb1dc8df6d4d4a5d75f9effad01739ebcad5c54a40d57aaa8b4190fc124dbde2e4f1e1d1b012a3bc4038157dc29b55533a932306d8d38d",
|
||||||
|
"exports": "a6d39296bc2704db6194b7d6180ede8a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "4ab11a9dd78c39668f7038f921ffc0993b368171d3ddde8031501ee1e08c4c9a",
|
||||||
|
"ikmR": "ea9ff7cc5b2705b188841c7ace169290ff312a9cb31467784ca92d7a2e6e1be8",
|
||||||
|
"skRm": "3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38",
|
||||||
|
"pkRm": "04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd",
|
||||||
|
"enc": "0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580",
|
||||||
|
"encryptions": "3d670fc7760ce5b208454bb678fbc1dd",
|
||||||
|
"exports": "0a3e30b572dafc58b998cd51959924be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "0c4b7c8090d9995e298d6fd61c7a0a66bb765a12219af1aacfaac99b4deaf8ad",
|
||||||
|
"ikmR": "a2f6e7c4d9e108e03be268a64fe73e11a320963c85375a30bfc9ec4a214c6a55",
|
||||||
|
"skRm": "9648e8711e9b6cb12dc19abf9da350cf61c3669c017b1db17bb36913b54a051d",
|
||||||
|
"pkRm": "0400f209b1bf3b35b405d750ef577d0b2dc81784005d1c67ff4f6d2860d7640ca379e22ac7fa105d94bc195758f4dfc0b82252098a8350c1bfeda8275ce4dd4262",
|
||||||
|
"enc": "0404dc39344526dbfa728afba96986d575811b5af199c11f821a0e603a4d191b25544a402f25364964b2c129cb417b3c1dab4dfc0854f3084e843f731654392726",
|
||||||
|
"encryptions": "9da1683aade69d882aa094aa57201481",
|
||||||
|
"exports": "80ab8f941a71d59f566e5032c6e2c675"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "02bd2bdbb430c0300cea89b37ada706206a9a74e488162671d1ff68b24deeb5f",
|
||||||
|
"ikmR": "8d283ea65b27585a331687855ab0836a01191d92ab689374f3f8d655e702d82f",
|
||||||
|
"skRm": "ebedc3ca088ad03dfbbfcd43f438c4bb5486376b8ccaea0dc25fc64b2f7fc0da",
|
||||||
|
"pkRm": "048fed808e948d46d95f778bd45236ce0c464567a1dc6f148ba71dc5aeff2ad52a43c71851b99a2cdbf1dad68d00baad45007e0af443ff80ad1b55322c658b7372",
|
||||||
|
"enc": "044415d6537c2e9dd4c8b73f2868b5b9e7e8e3d836990dc2fd5b466d1324c88f2df8436bac7aa2e6ebbfd13bd09eaaa7c57c7495643bacba2121dca2f2040e1c5f",
|
||||||
|
"encryptions": "f025dca38d668cee68e7c434e1b98f9f",
|
||||||
|
"exports": "2efbb7ade3f87133810f507fdd73f874"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 16,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "497efeca99592461588394f7e9496129ed89e62b58204e076d1b7141e999abda",
|
||||||
|
"ikmR": "49b7cbfc1756e8ae010dc80330108f5be91268b3636f3e547dbc714d6bcd3d16",
|
||||||
|
"skRm": "9d34abe85f6da91b286fbbcfbd12c64402de3d7f63819e6c613037746b4eae6b",
|
||||||
|
"pkRm": "0453a4d1a4333b291e32d50a77ac9157bbc946059941cf9ed5784c15adbc7ad8fe6bf34a504ed81fd9bc1b6bb066a037da30fccd6c0b42d72bf37b9fef43c8e498",
|
||||||
|
"enc": "04f910248e120076be2a4c93428ac0c8a6b89621cfef19f0f9e113d835cf39d5feabbf6d26444ebbb49c991ec22338ade3a5edff35a929be67c4e5f33dcff96706",
|
||||||
|
"exports": "6df17307eeb20a9180cff75ea183dd60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "5040af7a10269b11f78bb884812ad20041866db8bbd749a6a69e3f33e54da7164598f005bce09a9fe190e29c2f42df9e9e3aad040fccc625ddbd7aa99063fc594f40",
|
||||||
|
"ikmR": "39a28dc317c3e48b908948f99d608059f882d3d09c0541824bc25f94e6dee7aa0df1c644296b06fbb76e84aef5008f8a908e08fbabadf70658538d74753a85f8856a",
|
||||||
|
"skRm": "009227b4b91cf1eb6eecb6c0c0bae93a272d24e11c63bd4c34a581c49f9c3ca01c16bbd32a0a1fac22784f2ae985c85f183baad103b2d02aee787179dfc1a94fea11",
|
||||||
|
"pkRm": "0400b81073b1612cf7fdb6db07b35cf4bc17bda5854f3d270ecd9ea99f6c07b46795b8014b66c523ceed6f4829c18bc3886c891b63fa902500ce3ddeb1fbec7e608ac70050b76a0a7fc081dbf1cb30b005981113e635eb501a973aba662d7f16fcc12897dd752d657d37774bb16197c0d9724eecc1ed65349fb6ac1f280749e7669766f8cd",
|
||||||
|
"enc": "0400bec215e31718cd2eff5ba61d55d062d723527ec2029d7679a9c867d5c68219c9b217a9d7f78562dc0af3242fef35d1d6f4a28ee75f0d4b31bc918937b559b70762004c4fd6ad7373db7e31da8735fbd6171bbdcfa770211420682c760a40a482cc24f4125edbea9cb31fe71d5d796cfe788dc408857697a52fef711fb921fa7c385218",
|
||||||
|
"encryptions": "94209973d36203eef2e56d155ef241d5",
|
||||||
|
"exports": "31f25ea5e192561bce5f2c2822a9432c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "9953fbd633be69d984fc4fffc4d7749f007dbf97102d36a647a8108b0bb7c609e826b026aec1cd47b93fc5acb7518fa455ed38d0c29e900c56990635612fd3d220d2",
|
||||||
|
"ikmR": "17320bc93d9bc1d422ba0c705bf693e9a51a855d6e09c11bddea5687adc1a1122ec81384dc7e47959cae01c420a69e8e39337d9ebf9a9b2f3905cb76a35b0693ac34",
|
||||||
|
"skRm": "01a27e65890d64a121cfe59b41484b63fd1213c989c00e05a049ac4ede1f5caeec52bf43a59bdc36731cb6f8a0b7d7724b047ff52803c421ee99d61d4ea2e569c825",
|
||||||
|
"pkRm": "0400eb4010ca82412c044b52bdc218625c4ea797e061236206843e318882b3c1642e7e14e7cc1b4b171a433075ac0c8563043829eee51059a8b68197c8a7f6922465650075f40b6f440fdf525e2512b0c2023709294d912d8c68f94140390bff228097ce2d5f89b2b21f50d4c0892cfb955c380293962d5fe72060913870b61adc8b111953",
|
||||||
|
"enc": "0401c1cf49cafa9e26e24a9e20d7fa44a50a4e88d27236ef17358e79f3615a97f825899a985b3edb5195cad24a4fb64828701e81fbfd9a7ef673efde508e789509bd7c00fd5bfe053377bbee22e40ae5d64aa6fb47b314b5ab7d71b652db9259962dce742317d54084f0cf62a4b7e3f3caa9e6afb8efd6bf1eb8a2e13a7e73ec9213070d68",
|
||||||
|
"encryptions": "69d16fa7c814cd8be9aa2122fda8768f",
|
||||||
|
"exports": "d295fad3aef8be1f89d785800f83a30b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "566568b6cbfd1c6c06d1b0a2dc22d4e4965858bf3d54bf6cba5c018be0fad7a5cd9237937800f3cb57f10fa5691faeecab1685aa6da9b667469224a0989ff82b822b",
|
||||||
|
"ikmR": "f9f594556282cfe3eb30958ca2ef90ecd2a6ffd2661d41eb39ba184f3dae9f914aad297dd80cc763cb6525437a61ceae448aeeb304de137dc0f28dd007f0d592e137",
|
||||||
|
"skRm": "0168c8bf969b30bd949e154bf2db1964535e3f230f6604545bc9a33e9cd80fb17f4002170a9c91d55d7dd21db48e687cea83083498768cc008c6adf1e0ca08a309bd",
|
||||||
|
"pkRm": "040086b1a785a52af34a9a830332999896e99c5df0007a2ec3243ee3676ba040e60fde21bacf8e5f8db26b5acd42a2c81160286d54a2f124ca8816ac697993727431e50002aa5f5ebe70d88ff56445ade400fb979b466c9046123bbf5be72db9d90d1cde0bb7c217cff8ea0484445150eaf60170b039f54a5f6baeb7288bc62b1dedb59a1b",
|
||||||
|
"enc": "0401f828650ec526a647386324a31dadf75b54550b06707ae3e1fb83874b2633c935bb862bc4f07791ccfafbb08a1f00e18c531a34fec76f2cf3d581e7915fa40bbc3b010ab7c3d9162ea69928e71640ecff08b97f4fa9e8c66dfe563a13bf561cee7635563f91d387e2a38ee674ea28b24c633a988d1a08968b455e96307c64bda3f094b7",
|
||||||
|
"encryptions": "586d5a92612828afbd7fdcea96006892",
|
||||||
|
"exports": "a70389af65de4452a3f3147b66bd5c73"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 1,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "5dfb76f8b4708970acb4a6efa35ec4f2cebd61a3276a711c2fa42ef0bc9c191ea9dac7c0ac907336d830cea4a8394ab69e9171f344c4817309f93170cb34914987a5",
|
||||||
|
"ikmR": "9fd2aad24a653787f53df4a0d514c6d19610ca803298d7812bc0460b76c21da99315ebfec2343b4848d34ce526f0d39ce5a8dfddd9544e1c4d4b9a62f4191d096b42",
|
||||||
|
"skRm": "01ca47cf2f6f36fef46a01a46b393c30672224dd566aa3dd07a229519c49632c83d800e66149c3a7a07b840060549accd0d480ec5c71d2a975f88f6aa2fc0810b393",
|
||||||
|
"pkRm": "040143b7db23907d3ae1c43ef4882a6cdb142ca05a21c2475985c199807dd143e898136c65faf1ca1b6c6c2e8a92d67a0ab9c24f8c5cff7610cb942a73eb2ec4217c26018d67621cc78a60ec4bd1e23f90eb772adba2cf5a566020ee651f017b280a155c016679bd7e7ebad49e28e7ab679f66765f4ef34eae6b38a99f31bc73ea0f0d694d",
|
||||||
|
"enc": "040073dda7343ce32926c028c3be28508cccb751e2d4c6187bcc4e9b1de82d3d70c5702c6c866a920d9d9a574f5a4d4a0102db76207d5b3b77da16bb57486c5cc2a95f006b5d2e15efb24e297bdf8f2b6d7b25bf226d1b6efca47627b484d2942c14df6fe018d82ab9fb7306370c248864ea48fe5ca94934993517aacaa3b6bca8f92efc84",
|
||||||
|
"exports": "d8fa94ac5e6829caf5ab4cdd1e05f5e1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 1,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "018b6bb1b8bbcefbd91e66db4e1300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"ikmR": "7bf9fd92611f2ff4e6c2ab4dd636a320e0397d6a93d014277b025a7533684c3255a02aa1f2a142be5391eebfc60a6a9c729b79c2428b8d78fa36497b1e89e446d402",
|
||||||
|
"skRm": "019db24a3e8b1f383436cd06997dd864eb091418ff561e3876cee2e4762a0cc0b69688af9a7a4963c90d394b2be579144af97d4933c0e6c2c2d13e7505ea51a06b0d",
|
||||||
|
"pkRm": "0401e06b350786c48a60dfc50eed324b58ecafc4efba26242c46c14274bd97f0989487a6fae0626188fea971ae1cb53f5d0e87188c1c62af92254f17138bbcebf5acd0018e574ee1d695813ce9dc45b404d2cf9c04f27627c4c55da1f936d813fd39435d0713d4a3cdc5409954a1180eb2672bdfc4e0e79c04eda89f857f625e058742a1c8",
|
||||||
|
"enc": "0400ac8d1611948105f23cf5e6842b07bd39b352d9d1e7bff2c93ac063731d6372e2661eff2afce604d4a679b49195f15e4fa228432aed971f2d46c1beb51fb3e5812501fe199c3d94c1b199393642500443dd82ce1c01701a1279cc3d74e29773030e26a70d3512f761e1eb0d7882209599eb9acd295f5939311c55e737f11c19988878d6",
|
||||||
|
"encryptions": "207972885962115e69daaa3bc5015151",
|
||||||
|
"exports": "8e9c577501320d86ee84407840188f5f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 2,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "7f06ab8215105fc46aceeb2e3dc5028b44364f960426eb0d8e4026c2f8b5d7e7a986688f1591abf5ab753c357a5d6f0440414b4ed4ede71317772ac98d9239f70904",
|
||||||
|
"ikmR": "2ad954bbe39b7122529f7dde780bff626cd97f850d0784a432784e69d86eccaade43b6c10a8ffdb94bf943c6da479db137914ec835a7e715e36e45e29b587bab3bf1",
|
||||||
|
"skRm": "01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c27196a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b2462847",
|
||||||
|
"pkRm": "0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa6837580e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f64704f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f66d2451ec64",
|
||||||
|
"enc": "040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0",
|
||||||
|
"encryptions": "31769e36bcca13288177eb1c92f616ae",
|
||||||
|
"exports": "fbffd93db9f000f51cf8ab4c1127fbda"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 3,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "f9d540fde009bb1e5e71617c122a079862306b97144c8c4dca45ef6605c2ec9c43527c150800f5608a7e4cff771226579e7c776fb3def4e22e68e9fdc92340e94b6e",
|
||||||
|
"ikmR": "5273f7762dea7a2408333dbf8db9f6ef2ac4c475ad9e81a3b0b8c8805304adf5c876105d8703b42117ad8ee350df881e3d52926aafcb5c90f649faf94be81952c78a",
|
||||||
|
"skRm": "015b59f17366a1d4442e5b92d883a8f35fe8d88fea0e5bac6dfac7153c78fd0c6248c618b083899a7d62ba6e00e8a22cdde628dd5399b9a3377bb898792ff6f54ab9",
|
||||||
|
"pkRm": "040084698a47358f06a92926ee826a6784341285ee45f4b8269de271a8c6f03d5e8e24f628de13f5c37377b7cabfbd67bc98f9e8e758dfbee128b2fe752cd32f0f3ccd0061baec1ed7c6b52b7558bc120f783e5999c8952242d9a20baf421ccfc2a2b87c42d7b5b806fea6d518d5e9cd7bfd6c85beb5adeb72da41ac3d4f27bba83cff24d7",
|
||||||
|
"enc": "0400edc201c9b32988897a7f7b19104ebb54fc749faa41a67e9931e87ec30677194898074afb9a5f40a97df2972368a0c594e5b60e90d1ff83e9e35f8ff3ad200fd6d70028b5645debe9f1f335dbc1225c066218e85cf82a05fbe361fa477740b906cb3083076e4d17232513d102627597d38e354762cf05b3bd0f33dc4d0fb78531afd3fd",
|
||||||
|
"encryptions": "aa69356025f552372770ef126fa2e59a",
|
||||||
|
"exports": "1fcffb5d8bc1d825daf904a0c6f4a4d3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": 0,
|
||||||
|
"kem_id": 18,
|
||||||
|
"kdf_id": 3,
|
||||||
|
"aead_id": 65535,
|
||||||
|
"info": "4f6465206f6e2061204772656369616e2055726e",
|
||||||
|
"ikmE": "3018d74c67d0c61b5e4075190621fc192996e928b8859f45b3ad2399af8599df69c34b7a3eefeda7ee49ae73d4579300b85dde1654c0dfc3a3f78143d239a628cf72",
|
||||||
|
"ikmR": "a243eff510b99140034c72587e9f131809b9bce03a9da3da458771297f535cede0f48167200bf49ac123b52adfd789cf0adfd5cded6be2f146aeb00c34d4e6d234fc",
|
||||||
|
"skRm": "0045fe00b1d55eb64182d334e301e9ac553d6dbafbf69935e65f5bf89c761b9188c0e4d50a0167de6b98af7bebd05b2627f45f5fca84690cd86a61ba5a612870cf53",
|
||||||
|
"pkRm": "0401635b3074ad37b752696d5ca311da9cc790a899116030e4c71b83edd06ced92fdd238f6c921132852f20e6a2cbcf2659739232f4a69390f2b14d80667bcf9b71983000a919d29366554f53107a6c4cc7f8b24fa2de97b42433610cbd236d5a2c668e991ff4c4383e9fe0a9e7858fc39064e31fca1964e809a2f898c32fba46ce33575b8",
|
||||||
|
"enc": "0400932d9ff83ca4b799968bda0dd9dac4d02c9232cdcf133db7c53cfbf3d80a299fd99bc42da38bb78f57976bdb69988819b6e2924fadacdad8c05052997cf50b29110139f000af5b2c599b05fc63537d60a8384ca984821f8cd12621577a974ebadaf98bfdad6d1643dd4316062d7c0bda5ba0f0a2719992e993af615568abf19a256993",
|
||||||
|
"exports": "29c0f6150908f6e0d979172f23f1d57b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -889,13 +889,29 @@ type Config struct {
|
||||||
// with a specific ECH config known to a client.
|
// with a specific ECH config known to a client.
|
||||||
type EncryptedClientHelloKey struct {
|
type EncryptedClientHelloKey struct {
|
||||||
// Config should be a marshalled ECHConfig associated with PrivateKey. This
|
// Config should be a marshalled ECHConfig associated with PrivateKey. This
|
||||||
// must match the config provided to clients byte-for-byte. The config
|
// must match the config provided to clients byte-for-byte. The config must
|
||||||
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
|
// use as KEM one of
|
||||||
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
|
//
|
||||||
// AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003).
|
// - DHKEM(P-256, HKDF-SHA256) (0x0010)
|
||||||
|
// - DHKEM(P-384, HKDF-SHA384) (0x0011)
|
||||||
|
// - DHKEM(P-521, HKDF-SHA512) (0x0012)
|
||||||
|
// - DHKEM(X25519, HKDF-SHA256) (0x0020)
|
||||||
|
//
|
||||||
|
// and as KDF one of
|
||||||
|
//
|
||||||
|
// - HKDF-SHA256 (0x0001)
|
||||||
|
// - HKDF-SHA384 (0x0002)
|
||||||
|
// - HKDF-SHA512 (0x0003)
|
||||||
|
//
|
||||||
|
// and as AEAD one of
|
||||||
|
//
|
||||||
|
// - AES-128-GCM (0x0001)
|
||||||
|
// - AES-256-GCM (0x0002)
|
||||||
|
// - ChaCha20Poly1305 (0x0003)
|
||||||
|
//
|
||||||
Config []byte
|
Config []byte
|
||||||
// PrivateKey should be a marshalled private key. Currently, we expect
|
// PrivateKey should be a marshalled private key, in the format expected by
|
||||||
// this to be the output of [ecdh.PrivateKey.Bytes].
|
// HPKE's DeserializePrivateKey (see RFC 9180), for the KEM used in Config.
|
||||||
PrivateKey []byte
|
PrivateKey []byte
|
||||||
// SendAsRetry indicates if Config should be sent as part of the list of
|
// 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
|
// retry configs when ECH is requested by the client but rejected by the
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,11 @@ import (
|
||||||
"crypto/internal/hpke"
|
"crypto/internal/hpke"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
type echCipher struct {
|
||||||
KDFID uint16
|
KDFID uint16
|
||||||
AEADID uint16
|
AEADID uint16
|
||||||
|
|
@ -162,25 +149,8 @@ func parseECHConfigList(data []byte) ([]echConfig, error) {
|
||||||
return configs, nil
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pickECHConfig(list []echConfig) *echConfig {
|
func pickECHConfig(list []echConfig) (*echConfig, hpke.KEMSender, hpke.KDF, hpke.AEAD) {
|
||||||
for _, ec := range list {
|
for _, ec := range list {
|
||||||
if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var validSCS bool
|
|
||||||
for _, cs := range ec.SymmetricCipherSuite {
|
|
||||||
if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
validSCS = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !validSCS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !validDNSName(string(ec.PublicName)) {
|
if !validDNSName(string(ec.PublicName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -196,25 +166,26 @@ func pickECHConfig(list []echConfig) *echConfig {
|
||||||
if unsupportedExt {
|
if unsupportedExt {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &ec
|
s, err := hpke.NewKEMSender(ec.KemID, ec.PublicKey)
|
||||||
}
|
if err != nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
|
|
||||||
for _, s := range suites {
|
|
||||||
// NOTE: all of the supported AEADs and KDFs are fine, rather than
|
|
||||||
// imposing some sort of preference here, we just pick the first valid
|
|
||||||
// suite.
|
|
||||||
if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
|
for _, cs := range ec.SymmetricCipherSuite {
|
||||||
continue
|
// All of the supported AEADs and KDFs are fine, rather than
|
||||||
|
// imposing some sort of preference here, we just pick the first
|
||||||
|
// valid suite.
|
||||||
|
kdf, err := hpke.NewKDF(cs.KDFID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
aead, err := hpke.NewAEAD(cs.AEADID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &ec, s, kdf, aead
|
||||||
}
|
}
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
|
func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
|
||||||
|
|
@ -592,18 +563,28 @@ func (c *Conn) processECHClientHello(outer *clientHelloMsg, echKeys []EncryptedC
|
||||||
skip, config, err := parseECHConfig(echKey.Config)
|
skip, config, err := parseECHConfig(echKey.Config)
|
||||||
if err != nil || skip {
|
if err != nil || skip {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err)
|
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config: %s", err)
|
||||||
}
|
}
|
||||||
if skip {
|
if skip {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
|
echPriv, err := hpke.NewKEMRecipient(config.KemID, echKey.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err)
|
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey PrivateKey: %s", err)
|
||||||
|
}
|
||||||
|
kdf, err := hpke.NewKDF(echCiphersuite.KDFID)
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlert(alertInternalError)
|
||||||
|
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config KDF: %s", err)
|
||||||
|
}
|
||||||
|
aead, err := hpke.NewAEAD(echCiphersuite.AEADID)
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlert(alertInternalError)
|
||||||
|
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config AEAD: %s", err)
|
||||||
}
|
}
|
||||||
info := append([]byte("tls ech\x00"), echKey.Config...)
|
info := append([]byte("tls ech\x00"), echKey.Config...)
|
||||||
hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
|
hpkeContext, err := hpke.NewRecipient(encap, echPriv, kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// attempt next trial decryption
|
// attempt next trial decryption
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func TestSkipBadConfigs(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
config := pickECHConfig(configs)
|
config, _, _, _ := pickECHConfig(configs)
|
||||||
if config != nil {
|
if config != nil {
|
||||||
t.Fatal("pickECHConfig picked an invalid config")
|
t.Fatal("pickECHConfig picked an invalid config")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,11 +205,11 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
echConfig := pickECHConfig(echConfigs)
|
echConfig, echPK, kdf, aead := pickECHConfig(echConfigs)
|
||||||
if echConfig == nil {
|
if echConfig == nil {
|
||||||
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
|
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
|
||||||
}
|
}
|
||||||
ech = &echClientContext{config: echConfig}
|
ech = &echClientContext{config: echConfig, kdfID: kdf.ID(), aeadID: aead.ID()}
|
||||||
hello.encryptedClientHello = []byte{1} // indicate inner hello
|
hello.encryptedClientHello = []byte{1} // indicate inner hello
|
||||||
// We need to explicitly set these 1.2 fields to nil, as we do not
|
// We need to explicitly set these 1.2 fields to nil, as we do not
|
||||||
// marshal them when encoding the inner hello, otherwise transcripts
|
// marshal them when encoding the inner hello, otherwise transcripts
|
||||||
|
|
@ -219,17 +219,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
|
||||||
hello.secureRenegotiationSupported = false
|
hello.secureRenegotiationSupported = false
|
||||||
hello.extendedMasterSecret = false
|
hello.extendedMasterSecret = false
|
||||||
|
|
||||||
echPK, err := hpke.ParseHPKEPublicKey(ech.config.KemID, ech.config.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
suite, err := pickECHCipherSuite(ech.config.SymmetricCipherSuite)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
ech.kdfID, ech.aeadID = suite.KDFID, suite.AEADID
|
|
||||||
info := append([]byte("tls ech\x00"), ech.config.raw...)
|
info := append([]byte("tls ech\x00"), ech.config.raw...)
|
||||||
ech.encapsulatedKey, ech.hpkeContext, err = hpke.SetupSender(ech.config.KemID, suite.KDFID, suite.AEADID, echPK, info)
|
ech.encapsulatedKey, ech.hpkeContext, err = hpke.NewSender(echPK, kdf, aead, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/internal/hpke"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls/internal/fips140tls"
|
"crypto/tls/internal/fips140tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
|
@ -2249,15 +2248,13 @@ func TestECH(t *testing.T) {
|
||||||
builder.AddUint16(extensionEncryptedClientHello)
|
builder.AddUint16(extensionEncryptedClientHello)
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
builder.AddUint8(id)
|
builder.AddUint8(id)
|
||||||
builder.AddUint16(hpke.DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
builder.AddUint16(0x0020 /* DHKEM(X25519, HKDF-SHA256) */)
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
builder.AddBytes(pubKey)
|
builder.AddBytes(pubKey)
|
||||||
})
|
})
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
for _, aeadID := range sortedSupportedAEADs {
|
builder.AddUint16(0x0001 /* HKDF-SHA256 */)
|
||||||
builder.AddUint16(hpke.KDF_HKDF_SHA256) // The only KDF we support
|
builder.AddUint16(0x0001 /* AES-128-GCM */)
|
||||||
builder.AddUint16(aeadID)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
builder.AddUint8(maxNameLen)
|
builder.AddUint8(maxNameLen)
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue