]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/hpke: modularize API and support more ciphersuites
authorFilippo Valsorda <filippo@golang.org>
Sat, 6 Sep 2025 19:37:09 +0000 (21:37 +0200)
committerGopher Robot <gobot@golang.org>
Fri, 21 Nov 2025 20:41:58 +0000 (12:41 -0800)
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>
12 files changed:
src/crypto/internal/hpke/aead.go [new file with mode: 0644]
src/crypto/internal/hpke/hpke.go
src/crypto/internal/hpke/hpke_test.go
src/crypto/internal/hpke/kdf.go [new file with mode: 0644]
src/crypto/internal/hpke/kem.go [new file with mode: 0644]
src/crypto/internal/hpke/testdata/rfc9180-vectors.json [deleted file]
src/crypto/internal/hpke/testdata/rfc9180.json [new file with mode: 0644]
src/crypto/tls/common.go
src/crypto/tls/ech.go
src/crypto/tls/ech_test.go
src/crypto/tls/handshake_client.go
src/crypto/tls/tls_test.go

diff --git a/src/crypto/internal/hpke/aead.go b/src/crypto/internal/hpke/aead.go
new file mode 100644 (file)
index 0000000..1a606c6
--- /dev/null
@@ -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
+}
index 599bf97771c8d1cff8eda1eb5a53dbf25c6d5819..e7cc55a88c5b3cd9e56b5dc70d824a933eafbedd 100644 (file)
 // Use of this source code is governed by a BSD-style
 // 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
 
 import (
-       "crypto"
-       "crypto/aes"
        "crypto/cipher"
-       "crypto/ecdh"
-       "crypto/hkdf"
-       "crypto/rand"
+       "encoding/binary"
        "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 {
-       aead cipher.AEAD
-
-       sharedSecret []byte
-
        suiteID []byte
 
-       key            []byte
-       baseNonce      []byte
-       exporterSecret []byte
+       export func(string, uint16) ([]byte, error)
 
-       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 {
        *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 {
        *context
 }
 
-var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
-       block, err := aes.NewCipher(key)
-       if err != nil {
-               return nil, err
-       }
-       return cipher.NewGCM(block)
-}
-
-type AEADID uint16
-
-const (
-       AEAD_AES_128_GCM      = 0x0001
-       AEAD_AES_256_GCM      = 0x0002
-       AEAD_ChaCha20Poly1305 = 0x0003
-)
-
-var SupportedAEADs = map[uint16]struct {
-       keySize   int
-       nonceSize int
-       aead      func([]byte) (cipher.AEAD, error)
-}{
-       // RFC 9180, Section 7.3
-       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
+func newContext(sharedSecret []byte, kemID uint16, kdf KDF, aead AEAD, info []byte) (*context, error) {
+       sid := suiteID(kemID, kdf.ID(), aead.ID())
 
-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)
+       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)
+       infoHash, err := kdf.labeledExtract(sid, nil, "info_hash", info)
        if err != nil {
                return nil, err
        }
        ksContext := append([]byte{0}, pskIDHash...)
        ksContext = append(ksContext, infoHash...)
 
-       secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
+       secret, err := kdf.labeledExtract(sid, sharedSecret, "secret", nil)
        if err != nil {
                return nil, err
        }
-       key, err := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
+       key, err := kdf.labeledExpand(sid, secret, "key", ksContext, uint16(aead.keySize()))
        if err != nil {
                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 {
                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 {
                return nil, err
        }
-
-       aead, err := aeadInfo.aead(key)
+       expSecret, err := kdf.labeledExpand(sid, secret, "exp", ksContext, uint16(len(secret)))
        if err != nil {
                return nil, err
        }
+       export := func(exporterContext string, length uint16) ([]byte, error) {
+               return kdf.labeledExpand(sid, expSecret, "sec", []byte(exporterContext), length)
+       }
 
        return &context{
-               aead:           aead,
-               sharedSecret:   sharedSecret,
-               suiteID:        sid,
-               key:            key,
-               baseNonce:      baseNonce,
-               exporterSecret: exporterSecret,
+               aead:      a,
+               suiteID:   sid,
+               export:    export,
+               baseNonce: baseNonce,
        }, nil
 }
 
-func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) {
-       kem, err := newDHKem(kemID)
+// NewSender returns a sending HPKE context for the provided KEM encapsulation
+// 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 {
                return nil, nil, err
        }
-       sharedSecret, encapsulatedKey, err := kem.Encap(pub)
+       context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
        if err != nil {
                return nil, nil, err
        }
-
-       context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
-       if err != nil {
-               return nil, nil, err
-       }
-
        return encapsulatedKey, &Sender{context}, nil
 }
 
-func SetupRecipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Recipient, error) {
-       kem, err := newDHKem(kemID)
+// NewRecipient returns a receiving HPKE context for the provided KEM
+// 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 {
                return nil, err
        }
-       sharedSecret, err := kem.Decap(encPubEph, priv)
+       context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
        if err != nil {
                return nil, err
        }
-
-       context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
-       if err != nil {
-               return nil, err
-       }
-
        return &Recipient{context}, nil
 }
 
-func (ctx *context) nextNonce() []byte {
-       nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():]
-       for i := range ctx.baseNonce {
-               nonce[i] ^= ctx.baseNonce[i]
+// 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")
        }
-       return nonce
+       ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
+       s.seqNum++
+       return ciphertext, nil
 }
 
-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")
+// 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
        }
-       ctx.seqNum = ctx.seqNum.addOne()
+       ct, err = s.Seal(nil, plaintext)
+       if err != nil {
+               return nil, nil, err
+       }
+       return enc, ct, err
 }
 
-func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
-       ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
-       s.incrementNonce()
-       return ciphertext, nil
+// 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.incrementNonce()
+       r.seqNum++
        return plaintext, nil
 }
 
-func suiteID(kemID, kdfID, aeadID uint16) []byte {
-       suiteID := make([]byte, 0, 4+2+2+2)
-       suiteID = append(suiteID, []byte("HPKE")...)
-       suiteID = byteorder.BEAppendUint16(suiteID, kemID)
-       suiteID = byteorder.BEAppendUint16(suiteID, kdfID)
-       suiteID = byteorder.BEAppendUint16(suiteID, aeadID)
-       return suiteID
-}
-
-func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) {
-       kemInfo, ok := SupportedKEMs[kemID]
-       if !ok {
-               return nil, errors.New("unsupported KEM id")
+// 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 kemInfo.curve.NewPublicKey(bytes)
+       return r.Open(nil, ciphertext)
 }
 
-func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) {
-       kemInfo, ok := SupportedKEMs[kemID]
-       if !ok {
-               return nil, errors.New("unsupported KEM id")
+// 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 kemInfo.curve.NewPrivateKey(bytes)
-}
-
-type uint128 struct {
-       hi, lo uint64
+       return r.export(exporterContext, uint16(length))
 }
 
-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 (ctx *context) nextNonce() []byte {
+       nonce := make([]byte, ctx.aead.NonceSize())
+       binary.BigEndian.PutUint64(nonce[len(nonce)-8:], ctx.seqNum)
+       for i := range ctx.baseNonce {
+               nonce[i] ^= ctx.baseNonce[i]
+       }
+       return nonce
 }
 
-func (u uint128) bytes() []byte {
-       b := make([]byte, 16)
-       byteorder.BEPutUint64(b[0:], u.hi)
-       byteorder.BEPutUint64(b[8:], u.lo)
-       return b
+func suiteID(kemID, kdfID, aeadID uint16) []byte {
+       suiteID := make([]byte, 0, 4+2+2+2)
+       suiteID = append(suiteID, []byte("HPKE")...)
+       suiteID = binary.BigEndian.AppendUint16(suiteID, kemID)
+       suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID)
+       suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID)
+       return suiteID
 }
index 395552476fc4bb6de6f18a9a668136aacb1bc177..3655900b0a868e75bf6b789d11adfe1a39218d49 100644 (file)
@@ -6,16 +6,14 @@ package hpke
 
 import (
        "bytes"
+       "crypto/ecdh"
+       "crypto/sha3"
        "encoding/hex"
        "encoding/json"
+       "fmt"
+       "io"
        "os"
-       "strconv"
-       "strings"
        "testing"
-
-       "crypto/ecdh"
-       _ "crypto/sha256"
-       _ "crypto/sha512"
 )
 
 func mustDecodeHex(t *testing.T, in string) []byte {
@@ -27,169 +25,244 @@ func mustDecodeHex(t *testing.T, in string) []byte {
        return b
 }
 
-func parseVectorSetup(vector string) map[string]string {
-       vals := map[string]string{}
-       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")
+func TestVectors(t *testing.T) {
+       vectorsJSON, err := os.ReadFile("testdata/rfc9180.json")
        if err != nil {
                t.Fatal(err)
        }
-
        var vectors []struct {
-               Name        string
-               Setup       string
-               Encryptions string
+               Mode        uint16 `json:"mode"`
+               KEM         uint16 `json:"kem_id"`
+               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 {
                t.Fatal(err)
        }
 
        for _, vector := range vectors {
-               t.Run(vector.Name, func(t *testing.T) {
-                       setup := parseVectorSetup(vector.Setup)
-
-                       kemID, err := strconv.Atoi(setup["kem_id"])
-                       if err != nil {
-                               t.Fatal(err)
+               name := fmt.Sprintf("mode %04x kem %04x kdf %04x aead %04x",
+                       vector.Mode, vector.KEM, vector.KDF, vector.AEAD)
+               t.Run(name, func(t *testing.T) {
+                       if vector.Mode != 0 {
+                               t.Skip("only mode 0 (base) is supported")
                        }
-                       if _, ok := SupportedKEMs[uint16(kemID)]; !ok {
-                               t.Skip("unsupported KEM")
+                       if vector.KEM == 0x0021 {
+                               t.Skip("KEM 0x0021 (DHKEM(X448)) not supported")
                        }
-                       kdfID, err := strconv.Atoi(setup["kdf_id"])
+
+                       kdf, err := NewKDF(vector.KDF)
                        if err != nil {
                                t.Fatal(err)
                        }
-                       if _, ok := SupportedKDFs[uint16(kdfID)]; !ok {
-                               t.Skip("unsupported KDF")
+                       if kdf.ID() != vector.KDF {
+                               t.Errorf("unexpected KDF ID: got %04x, want %04x", kdf.ID(), vector.KDF)
                        }
-                       aeadID, err := strconv.Atoi(setup["aead_id"])
+
+                       aead, err := NewAEAD(vector.AEAD)
                        if err != nil {
                                t.Fatal(err)
                        }
-                       if _, ok := SupportedAEADs[uint16(aeadID)]; !ok {
-                               t.Skip("unsupported AEAD")
+                       if aead.ID() != vector.AEAD {
+                               t.Errorf("unexpected AEAD ID: got %04x, want %04x", aead.ID(), vector.AEAD)
                        }
 
-                       info := mustDecodeHex(t, setup["info"])
-                       pubKeyBytes := mustDecodeHex(t, setup["pkRm"])
-                       pub, err := ParseHPKEPublicKey(uint16(kemID), pubKeyBytes)
+                       pubKeyBytes := mustDecodeHex(t, vector.PkRm)
+                       kemSender, err := NewKEMSender(vector.KEM, pubKeyBytes)
                        if err != nil {
                                t.Fatal(err)
                        }
-
-                       ephemeralPrivKey := mustDecodeHex(t, setup["skEm"])
-
-                       testingOnlyGenerateKey = func() (*ecdh.PrivateKey, error) {
-                               return SupportedKEMs[uint16(kemID)].curve.NewPrivateKey(ephemeralPrivKey)
+                       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)
                        }
-                       t.Cleanup(func() { testingOnlyGenerateKey = nil })
 
-                       encap, sender, err := SetupSender(
-                               uint16(kemID),
-                               uint16(kdfID),
-                               uint16(aeadID),
-                               pub,
-                               info,
-                       )
+                       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 {
                                t.Fatal(err)
                        }
 
-                       expectedEncap := mustDecodeHex(t, setup["enc"])
+                       expectedEncap := mustDecodeHex(t, vector.Enc)
                        if !bytes.Equal(encap, expectedEncap) {
                                t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap)
                        }
 
-                       privKeyBytes := mustDecodeHex(t, setup["skRm"])
-                       priv, err := ParseHPKEPrivateKey(uint16(kemID), privKeyBytes)
+                       privKeyBytes := mustDecodeHex(t, vector.SkRm)
+                       kemRecipient, err := NewKEMRecipient(vector.KEM, privKeyBytes)
                        if err != nil {
                                t.Fatal(err)
                        }
-
-                       recipient, err := SetupRecipient(
-                               uint16(kemID),
-                               uint16(kdfID),
-                               uint16(aeadID),
-                               priv,
-                               info,
-                               encap,
-                       )
+                       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)
                        }
-
-                       for _, ctx := range []*context{sender.context, recipient.context} {
-                               expectedSharedSecret := mustDecodeHex(t, setup["shared_secret"])
-                               if !bytes.Equal(ctx.sharedSecret, expectedSharedSecret) {
-                                       t.Errorf("unexpected shared secret, got: %x, want %x", ctx.sharedSecret, expectedSharedSecret)
+                       // 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)
                                }
-                               expectedKey := mustDecodeHex(t, setup["key"])
-                               if !bytes.Equal(ctx.key, expectedKey) {
-                                       t.Errorf("unexpected key, got: %x, want %x", ctx.key, expectedKey)
+                               kemRecipientBytes2, err := kem2.Bytes()
+                               if err != nil {
+                                       t.Fatal(err)
                                }
-                               expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"])
-                               if !bytes.Equal(ctx.baseNonce, expectedBaseNonce) {
-                                       t.Errorf("unexpected base nonce, got: %x, want %x", ctx.baseNonce, expectedBaseNonce)
+                               if !bytes.Equal(kemRecipientBytes2, kemRecipientBytes) {
+                                       t.Errorf("X25519 re-serialized key differs: got %x, want %x", kemRecipientBytes2, kemRecipientBytes)
                                }
-                               expectedExporterSecret := mustDecodeHex(t, setup["exporter_secret"])
-                               if !bytes.Equal(ctx.exporterSecret, expectedExporterSecret) {
-                                       t.Errorf("unexpected exporter secret, got: %x, want %x", ctx.exporterSecret, expectedExporterSecret)
+                               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)
+                       }
 
-                       for _, enc := range parseVectorEncryptions(vector.Encryptions) {
-                               t.Run("seq num "+enc["sequence number"], func(t *testing.T) {
-                                       seqNum, err := strconv.Atoi(enc["sequence number"])
-                                       if err != nil {
-                                               t.Fatal(err)
-                                       }
-                                       sender.seqNum = uint128{lo: uint64(seqNum)}
-                                       recipient.seqNum = uint128{lo: uint64(seqNum)}
-                                       expectedNonce := mustDecodeHex(t, enc["nonce"])
-                                       computedNonce := sender.nextNonce()
-                                       if !bytes.Equal(computedNonce, expectedNonce) {
-                                               t.Errorf("unexpected nonce: got %x, want %x", computedNonce, expectedNonce)
-                                       }
+                       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 {
+                               t.Fatal(err)
+                       }
 
-                                       expectedCiphertext := mustDecodeHex(t, enc["ct"])
-                                       ciphertext, err := sender.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"]))
+                       if aead != ExportOnly() {
+                               source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
+                               for range 1000 {
+                                       aad, plaintext := drawRandomInput(t, source), drawRandomInput(t, source)
+                                       ciphertext, err := sender.Seal(aad, plaintext)
                                        if err != nil {
                                                t.Fatal(err)
                                        }
-                                       if !bytes.Equal(ciphertext, expectedCiphertext) {
-                                               t.Errorf("unexpected ciphertext: got %x want %x", ciphertext, expectedCiphertext)
-                                       }
-
-                                       expectedPlaintext := mustDecodeHex(t, enc["pt"])
-                                       plaintext, err := recipient.Open(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["ct"]))
+                                       sink.Write(ciphertext)
+                                       got, err := recipient.Open(aad, ciphertext)
                                        if err != nil {
                                                t.Fatal(err)
                                        }
-                                       if !bytes.Equal(plaintext, expectedPlaintext) {
-                                               t.Errorf("unexpected plaintext: got %x want %x", plaintext, expectedPlaintext)
+                                       if !bytes.Equal(got, plaintext) {
+                                               t.Errorf("unexpected plaintext: got %x want %x", got, plaintext)
                                        }
-                               })
+                               }
+                               encryptions := make([]byte, 16)
+                               sink.Read(encryptions)
+                               expectedEncryptions := mustDecodeHex(t, vector.Encryptions)
+                               if !bytes.Equal(encryptions, expectedEncryptions) {
+                                       t.Errorf("unexpected accumulated encryptions, got: %x, want %x", encryptions, expectedEncryptions)
+                               }
+                       } else {
+                               if _, err := sender.Seal(nil, nil); err == nil {
+                                       t.Error("expected error from Seal with export-only AEAD")
+                               }
+                               if _, err := recipient.Open(nil, nil); err == nil {
+                                       t.Error("expected error from Open with export-only AEAD")
+                               }
+                       }
+
+                       source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
+                       for l := range 1000 {
+                               context := string(drawRandomInput(t, source))
+                               value, err := sender.Export(context, l)
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               sink.Write(value)
+                               got, err := recipient.Export(context, l)
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               if !bytes.Equal(got, value) {
+                                       t.Errorf("recipient: unexpected exported secret: got %x want %x", got, value)
+                               }
+                       }
+                       exports := make([]byte, 16)
+                       sink.Read(exports)
+                       expectedExports := mustDecodeHex(t, vector.Exports)
+                       if !bytes.Equal(exports, expectedExports) {
+                               t.Errorf("unexpected accumulated exports, got: %x, want %x", exports, expectedExports)
                        }
                })
        }
 }
+
+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()")
+       }
+}
diff --git a/src/crypto/internal/hpke/kdf.go b/src/crypto/internal/hpke/kdf.go
new file mode 100644 (file)
index 0000000..c5d6147
--- /dev/null
@@ -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))
+}
diff --git a/src/crypto/internal/hpke/kem.go b/src/crypto/internal/hpke/kem.go
new file mode 100644 (file)
index 0000000..2f5d295
--- /dev/null
@@ -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)
+}
diff --git a/src/crypto/internal/hpke/testdata/rfc9180-vectors.json b/src/crypto/internal/hpke/testdata/rfc9180-vectors.json
deleted file mode 100644 (file)
index 3badbc6..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[{"Name":"DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM","Setup":"mode: 0\nkem_id: 32\nkdf_id: 1\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234\npkEm: 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431\nskEm: 52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736\nikmR: 6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037\npkRm: 3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d\nskRm: 4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8\nenc: 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431\nshared_secret: fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc\nkey_schedule_context: 00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449\nsecret: 12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397\nkey: 4531685d41d65f03dc48f6b8302c05b0\nbase_nonce: 56d890e5accaaf011cff4b7d\nexporter_secret: 45ff1c2e220db587171952c0592d5f5ebe103f1561a2614e38f2ffd47e99e3f8","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 56d890e5accaaf011cff4b7d\nct: f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 56d890e5accaaf011cff4b7c\nct: af2d7e9ac9ae7e270f46ba1f975be53c09f8d875bdc8535458c2494e8a6eab251c03d0c22a56b8ca42c2063b84\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 56d890e5accaaf011cff4b7f\nct: 498dfcabd92e8acedc281e85af1cb4e3e31c7dc394a1ca20e173cb72516491588d96a19ad4a683518973dcc180\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 56d890e5accaaf011cff4b79\nct: 583bd32bc67a5994bb8ceaca813d369bca7b2a42408cddef5e22f880b631215a09fc0012bc69fccaa251c0246d\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 56d890e5accaaf011cff4b82\nct: 7175db9717964058640a3a11fb9007941a5d1757fda1a6935c805c21af32505bf106deefec4a49ac38d71c9e0a\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 56d890e5accaaf011cff4a7d\nct: 957f9800542b0b8891badb026d79cc54597cb2d225b54c00c5238c25d05c30e3fbeda97d2e0e1aba483a2df9f2"},{"Name":"DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305","Setup":"mode: 0\nkem_id: 32\nkdf_id: 1\naead_id: 3\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b\npkEm: 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a\nskEm: f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600\nikmR: 1ac01f181fdf9f352797655161c58b75c656a6cc2716dcb66372da835542e1df\npkRm: 4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a\nskRm: 8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb\nenc: 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a\nshared_secret: 0bbe78490412b4bbea4812666f7916932b828bba79942424abb65244930d69a7\nkey_schedule_context: 00431df6cd95e11ff49d7013563baf7f11588c75a6611e e2a4404a49306ae4cfc5b69c5718a60cc5876c358d3f7fc31ddb598503f67be58ea1e798c0bb19eb9796\nsecret: 5b9cd775e64b437a2335cf499361b2e0d5e444d5cb41a8a53336d8fe402282c6\nkey: ad2744de8e17f4ebba575b3f5f5a8fa1f69c2a07f6e7500bc60ca6e3e3ec1c91\nbase_nonce: 5c4d98150661b848853b547f\nexporter_secret: a3b010d4994890e2c6968a36f64470d3c824c8f5029942feb11e7a74b2921922","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 5c4d98150661b848853b547f\nct: 1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 5c4d98150661b848853b547e\nct: 6b53c051e4199c518de79594e1c4ab18b96f081549d45ce015be002090bb119e85285337cc95ba5f59992dc98c\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 5c4d98150661b848853b547d\nct: 71146bd6795ccc9c49ce25dda112a48f202ad220559502cef1f34271e0cb4b02b4f10ecac6f48c32f878fae86b\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 5c4d98150661b848853b547b\nct: 63357a2aa291f5a4e5f27db6baa2af8cf77427c7c1a909e0b37214dd47db122bb153495ff0b02e9e54a50dbe16\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 5c4d98150661b848853b5480\nct: 18ab939d63ddec9f6ac2b60d61d36a7375d2070c9b683861110757062c52b8880a5f6b3936da9cd6c23ef2a95c\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 5c4d98150661b848853b557f\nct: 7a4a13e9ef23978e2c520fd4d2e757514ae160cd0cd05e556ef692370ca53076214c0c40d4c728d6ed9e727a5b"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM","Setup":"mode: 0\nkem_id: 16\nkdf_id: 1\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e\npkEm: 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4\nskEm: 4995788ef4b9d6132b249ce59a77281493eb39af373d236a1fe415cb0c2d7beb\nikmR: 668b37171f1072f3cf12ea8a236a45df23fc13b82af3609ad1e354f6ef817550\npkRm: 04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0\nskRm: f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2\nenc: 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4\nshared_secret: c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8\nkey_schedule_context: 00b88d4e6d91759e65e87c470e8b9141113e9ad5f0c8ceefc1e088c82e6980500798e486f9c9c09c9b5c753ac72d6005de254c607d1b534ed11d493ae1c1d9ac85\nsecret: 2eb7b6bf138f6b5aff857414a058a3f1750054a9ba1f72c2cf0684a6f20b10e1\nkey: 868c066ef58aae6dc589b6cfdd18f97e\nbase_nonce: 4e0bc5018beba4bf004cca59\nexporter_secret: 14ad94af484a7ad3ef40e9f3be99ecc6fa9036df9d4920548424df127ee0d99f","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 4e0bc5018beba4bf004cca59\nct: 5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 4e0bc5018beba4bf004cca58\nct: fa6f037b47fc21826b610172ca9637e82d6e5801eb31cbd3748271affd4ecb06646e0329cbdf3c3cd655b28e82\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 4e0bc5018beba4bf004cca5b\nct: 895cabfac50ce6c6eb02ffe6c048bf53b7f7be9a91fc559402cbc5b8dcaeb52b2ccc93e466c28fb55fed7a7fec\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 4e0bc5018beba4bf004cca5d\nct: 8787491ee8df99bc99a246c4b3216d3d57ab5076e18fa27133f520703bc70ec999dd36ce042e44f0c3169a6a8f\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 4e0bc5018beba4bf004ccaa6\nct: 2ad71c85bf3f45c6eca301426289854b31448bcf8a8ccb1deef3ebd87f60848aa53c538c30a4dac71d619ee2cd\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 4e0bc5018beba4bf004ccb59\nct: 10f179686aa2caec1758c8e554513f16472bd0a11e2a907dde0b212cbe87d74f367f8ffe5e41cd3e9962a6afb2"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA512, AES-128-GCM","Setup":"mode: 0\nkem_id: 16\nkdf_id: 3\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 4ab11a9dd78c39668f7038f921ffc0993b368171d3ddde8031501ee1e08c4c9a\npkEm: 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580\nskEm: 2292bf14bb6e15b8c81a0f45b7a6e93e32d830e48cca702e0affcfb4d07e1b5c\nikmR: ea9ff7cc5b2705b188841c7ace169290ff312a9cb31467784ca92d7a2e6e1be8\npkRm: 04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd\nskRm: 3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38\nenc: 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580\nshared_secret: 02f584736390fc93f5b4ad039826a3fa08e9911bd1215a3db8e8791ba533cafd\nkey_schedule_context: 005b8a3617af7789ee716e7911c7e77f84cdc4cc46e60fb7e19e4059f9aeadc00585e26874d1ddde76e551a7679cd47168c466f6e1f705cc9374c192778a34fcd5ca221d77e229a9d11b654de7942d685069c633b2362ce3b3d8ea4891c9a2a87a4eb7cdb289ba5e2ecbf8cd2c8498bb4a383dc021454d70d46fcbbad1252ef4f9\nsecret: 0c7acdab61693f936c4c1256c78e7be30eebfe466812f9cc49f0b58dc970328dfc03ea359be0250a471b1635a193d2dfa8cb23c90aa2e25025b892a725353eeb\nkey: 090ca96e5f8aa02b69fac360da50ddf9\nbase_nonce: 9c995e621bf9a20c5ca45546\nexporter_secret: 4a7abb2ac43e6553f129b2c5750a7e82d149a76ed56dc342d7bca61e26d494f4855dff0d0165f27ce57756f7f16baca006539bb8e4518987ba610480ac03efa8","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 9c995e621bf9a20c5ca45546\nct: d3cf4984931484a080f74c1bb2a6782700dc1fef9abe8442e44a6f09044c88907200b332003543754eb51917ba\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 9c995e621bf9a20c5ca45547\nct: d14414555a47269dfead9fbf26abb303365e40709a4ed16eaefe1f2070f1ddeb1bdd94d9e41186f124e0acc62d\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 9c995e621bf9a20c5ca45544\nct: 9bba136cade5c4069707ba91a61932e2cbedda2d9c7bdc33515aa01dd0e0f7e9d3579bf4016dec37da4aafa800\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 9c995e621bf9a20c5ca45542\nct: a531c0655342be013bf32112951f8df1da643602f1866749519f5dcb09cc68432579de305a77e6864e862a7600\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 9c995e621bf9a20c5ca455b9\nct: be5da649469efbad0fb950366a82a73fefeda5f652ec7d3731fac6c4ffa21a7004d2ab8a04e13621bd3629547d\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 9c995e621bf9a20c5ca45446\nct: 62092672f5328a0dde095e57435edf7457ace60b26ee44c9291110ec135cb0e14b85594e4fea11247d937deb62"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305","Setup":"mode: 0\nkem_id: 16\nkdf_id: 1\naead_id: 3\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: f1f1a3bc95416871539ecb51c3a8f0cf608afb40fbbe305c0a72819d35c33f1f\npkEm: 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291\nskEm: 7550253e1147aae48839c1f8af80d2770fb7a4c763afe7d0afa7e0f42a5b3689\nikmR: 61092f3f56994dd424405899154a9918353e3e008171517ad576b900ddb275e7\npkRm: 04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a006\nskRm: a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b\nenc: 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291\nshared_secret: 806520f82ef0b03c823b7fc524b6b55a088f566b9751b89551c170f4113bd850\nkey_schedule_context: 00b738cd703db7b4106e93b4621e9a19c89c838e55964240e5d3f331aaf8b0d58b2e986ea1c671b61cf45eec134dac0bae58ec6f63e790b1400b47c33038b0269c\nsecret: fe891101629aa355aad68eff3cc5170d057eca0c7573f6575e91f9783e1d4506\nkey: a8f45490a92a3b04d1dbf6cf2c3939ad8bfc9bfcb97c04bffe116730c9dfe3fc\nbase_nonce: 726b4390ed2209809f58c693\nexporter_secret: 4f9bd9b3a8db7d7c3a5b9d44fdc1f6e37d5d77689ade5ec44a7242016e6aa205","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 726b4390ed2209809f58c693\nct: 6469c41c5c81d3aa85432531ecf6460ec945bde1eb428cb2fedf7a29f5a685b4ccb0d057f03ea2952a27bb458b\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 726b4390ed2209809f58c692\nct: f1564199f7e0e110ec9c1bcdde332177fc35c1adf6e57f8d1df24022227ffa8716862dbda2b1dc546c9d114374\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 726b4390ed2209809f58c691\nct: 39de89728bcb774269f882af8dc5369e4f3d6322d986e872b3a8d074c7c18e8549ff3f85b6d6592ff87c3f310c\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 726b4390ed2209809f58c697\nct: bc104a14fbede0cc79eeb826ea0476ce87b9c928c36e5e34dc9b6905d91473ec369a08b1a25d305dd45c6c5f80\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 726b4390ed2209809f58c66c\nct: 8f2814a2c548b3be50259713c6724009e092d37789f6856553d61df23ebc079235f710e6af3c3ca6eaba7c7c6c\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 726b4390ed2209809f58c793\nct: b45b69d419a9be7219d8c94365b89ad6951caf4576ea4774ea40e9b7047a09d6537d1aa2f7c12d6ae4b729b4d0"},{"Name":"DHKEM(P-521, HKDF-SHA512), HKDF-SHA512, AES-256-GCM","Setup":"mode: 0\nkem_id: 18\nkdf_id: 3\naead_id: 2\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 7f06ab8215105fc46aceeb2e3dc5028b44364f960426eb0d8e4026c2f8b5d7e7a986688f1591abf5ab753c357a5d6f0440414b4ed4ede71317772ac98d9239f70904\npkEm: 040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0\nskEm: 014784c692da35df6ecde98ee43ac425dbdd0969c0c72b42f2e708ab9d535415a8569bdacfcc0a114c85b8e3f26acf4d68115f8c91a66178cdbd03b7bcc5291e374b\nikmR: 2ad954bbe39b7122529f7dde780bff626cd97f850d0784a432784e69d86eccaade43b6c10a8ffdb94bf943c6da479db137914ec835a7e715e36e45e29b587bab3bf1\npkRm: 0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa6837580e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f64704f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f66d2451ec64\nskRm: 01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c27196a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b2462847\nenc: 040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0\nshared_secret: 776ab421302f6eff7d7cb5cb1adaea0cd50872c71c2d63c30c4f1d5e43653336fef33b103c67e7a98add2d3b66e2fda95b5b2a667aa9dac7e59cc1d46d30e818\nkey_schedule_context: 0083a27c5b2358ab4dae1b2f5d8f57f10ccccc822a473326f543f239a70aee46347324e84e02d7651a10d08fb3dda739d22d50c53fbfa8122baacd0f9ae5913072ef45baa1f3a4b169e141feb957e48d03f28c837d8904c3d6775308c3d3faa75dd64adfa44e1a1141edf9349959b8f8e5291cbdc56f62b0ed6527d692e85b09a4\nsecret: 49fd9f53b0f93732555b2054edfdc0e3101000d75df714b98ce5aa295a37f1b18dfa86a1c37286d805d3ea09a20b72f93c21e83955a1f01eb7c5eead563d21e7\nkey: 751e346ce8f0ddb2305c8a2a85c70d5cf559c53093656be636b9406d4d7d1b70\nbase_nonce: 55ff7a7d739c69f44b25447b\nexporter_secret: e4ff9dfbc732a2b9c75823763c5ccc954a2c0648fc6de80a58581252d0ee3215388a4455e69086b50b87eb28c169a52f42e71de4ca61c920e7bd24c95cc3f992","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 55ff7a7d739c69f44b25447b\nct: 170f8beddfe949b75ef9c387e201baf4132fa7374593dfafa90768788b7b2b200aafcc6d80ea4c795a7c5b841a\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 55ff7a7d739c69f44b25447a\nct: d9ee248e220ca24ac00bbbe7e221a832e4f7fa64c4fbab3945b6f3af0c5ecd5e16815b328be4954a05fd352256\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 55ff7a7d739c69f44b254479\nct: 142cf1e02d1f58d9285f2af7dcfa44f7c3f2d15c73d460c48c6e0e506a3144bae35284e7e221105b61d24e1c7a\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 55ff7a7d739c69f44b25447f\nct: 3bb3a5a07100e5a12805327bf3b152df728b1c1be75a9fd2cb2bf5eac0cca1fb80addb37eb2a32938c7268e3e5\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 55ff7a7d739c69f44b254484\nct: 4f268d0930f8d50b8fd9d0f26657ba25b5cb08b308c92e33382f369c768b558e113ac95a4c70dd60909ad1adc7\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 55ff7a7d739c69f44b25457b\nct: dbbfc44ae037864e75f136e8b4b4123351d480e6619ae0e0ae437f036f2f8f1ef677686323977a1ccbb4b4f16a"}]
diff --git a/src/crypto/internal/hpke/testdata/rfc9180.json b/src/crypto/internal/hpke/testdata/rfc9180.json
new file mode 100644 (file)
index 0000000..a056d3f
--- /dev/null
@@ -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"
+    }
+]
index 6fe6f34cd210d8bdffb6b8e32e0c8051a08d4256..c8e65e4d3c0ee5969bb5340c1b01b2580ceecba6 100644 (file)
@@ -889,13 +889,29 @@ type Config struct {
 // with a specific ECH config known to a client.
 type EncryptedClientHelloKey struct {
        // Config should be a marshalled ECHConfig associated with PrivateKey. This
-       // must match the config provided to clients byte-for-byte. The config
-       // should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
-       // HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
-       // AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003).
+       // must match the config provided to clients byte-for-byte. The config must
+       // use as KEM one of
+       //
+       //   - 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
-       // PrivateKey should be a marshalled private key. Currently, we expect
-       // this to be the output of [ecdh.PrivateKey.Bytes].
+       // PrivateKey should be a marshalled private key, in the format expected by
+       // HPKE's DeserializePrivateKey (see RFC 9180), for the KEM used in Config.
        PrivateKey []byte
        // SendAsRetry indicates if Config should be sent as part of the list of
        // retry configs when ECH is requested by the client but rejected by the
index d3472d8dc4aafb9102b928704fddfd4dacb28994..c56642f738be1ee9858fc35f225385bc7d164605 100644 (file)
@@ -9,24 +9,11 @@ import (
        "crypto/internal/hpke"
        "errors"
        "fmt"
-       "slices"
        "strings"
 
        "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 {
        KDFID  uint16
        AEADID uint16
@@ -162,25 +149,8 @@ func parseECHConfigList(data []byte) ([]echConfig, error) {
        return configs, nil
 }
 
-func pickECHConfig(list []echConfig) *echConfig {
+func pickECHConfig(list []echConfig) (*echConfig, hpke.KEMSender, hpke.KDF, hpke.AEAD) {
        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)) {
                        continue
                }
@@ -196,25 +166,26 @@ func pickECHConfig(list []echConfig) *echConfig {
                if unsupportedExt {
                        continue
                }
-               return &ec
-       }
-       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 {
+               s, err := hpke.NewKEMSender(ec.KemID, ec.PublicKey)
+               if err != nil {
                        continue
                }
-               if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
-                       continue
+               for _, cs := range ec.SymmetricCipherSuite {
+                       // 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) {
@@ -592,18 +563,28 @@ func (c *Conn) processECHClientHello(outer *clientHelloMsg, echKeys []EncryptedC
                skip, config, err := parseECHConfig(echKey.Config)
                if err != nil || skip {
                        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 {
                        continue
                }
-               echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
+               echPriv, err := hpke.NewKEMRecipient(config.KemID, echKey.PrivateKey)
+               if err != nil {
+                       c.sendAlert(alertInternalError)
+                       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 EncryptedClientHelloKeys PrivateKey: %s", err)
+                       return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config AEAD: %s", err)
                }
                info := append([]byte("tls ech\x00"), echKey.Config...)
-               hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
+               hpkeContext, err := hpke.NewRecipient(encap, echPriv, kdf, aead, info)
                if err != nil {
                        // attempt next trial decryption
                        continue
index 96312a44d7b3a8b400def5311d95160fdba227f6..5adbb0293ddb8608becb7f4958ce3915171466c7 100644 (file)
@@ -41,7 +41,7 @@ func TestSkipBadConfigs(t *testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       config := pickECHConfig(configs)
+       config, _, _, _ := pickECHConfig(configs)
        if config != nil {
                t.Fatal("pickECHConfig picked an invalid config")
        }
index 90c5bdacd823188935a5b729346683a57daf7ded..533b8ba31ebb2ba1687b1c83da7a6e33b8bca68d 100644 (file)
@@ -205,11 +205,11 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
                if err != nil {
                        return nil, nil, nil, err
                }
-               echConfig := pickECHConfig(echConfigs)
+               echConfig, echPK, kdf, aead := pickECHConfig(echConfigs)
                if echConfig == nil {
                        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
                // We need to explicitly set these 1.2 fields to nil, as we do not
                // marshal them when encoding the inner hello, otherwise transcripts
@@ -219,17 +219,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
                hello.secureRenegotiationSupported = 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...)
-               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 {
                        return nil, nil, nil, err
                }
index 6539009df6242ab8d283650f5f6ef3567a4dec46..93ec8643f679743fcb3046642f6c65367883b21a 100644 (file)
@@ -11,7 +11,6 @@ import (
        "crypto/ecdh"
        "crypto/ecdsa"
        "crypto/elliptic"
-       "crypto/internal/hpke"
        "crypto/rand"
        "crypto/tls/internal/fips140tls"
        "crypto/x509"
@@ -2249,15 +2248,13 @@ func TestECH(t *testing.T) {
                builder.AddUint16(extensionEncryptedClientHello)
                builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
                        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.AddBytes(pubKey)
                        })
                        builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
-                               for _, aeadID := range sortedSupportedAEADs {
-                                       builder.AddUint16(hpke.KDF_HKDF_SHA256) // The only KDF we support
-                                       builder.AddUint16(aeadID)
-                               }
+                               builder.AddUint16(0x0001 /* HKDF-SHA256 */)
+                               builder.AddUint16(0x0001 /* AES-128-GCM */)
                        })
                        builder.AddUint8(maxNameLen)
                        builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {