From 7c985a2df46fa961746a3493de9bddc48cd89548 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 6 Sep 2025 21:37:09 +0200 Subject: [PATCH] crypto/internal/hpke: modularize API and support more ciphersuites Updates #75300 Change-Id: I6a6a6964de449b36bc6f5594e08c3c47a0a2f17f Reviewed-on: https://go-review.googlesource.com/c/go/+/701435 Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Mark Freeman Reviewed-by: Junyang Shao --- src/crypto/internal/hpke/aead.go | 130 ++++++ src/crypto/internal/hpke/hpke.go | 377 ++++++------------ src/crypto/internal/hpke/hpke_test.go | 301 ++++++++------ src/crypto/internal/hpke/kdf.go | 80 ++++ src/crypto/internal/hpke/kem.go | 355 +++++++++++++++++ .../hpke/testdata/rfc9180-vectors.json | 1 - .../internal/hpke/testdata/rfc9180.json | 332 +++++++++++++++ src/crypto/tls/common.go | 28 +- src/crypto/tls/ech.go | 81 ++-- src/crypto/tls/ech_test.go | 2 +- src/crypto/tls/handshake_client.go | 15 +- src/crypto/tls/tls_test.go | 9 +- 12 files changed, 1264 insertions(+), 447 deletions(-) create mode 100644 src/crypto/internal/hpke/aead.go create mode 100644 src/crypto/internal/hpke/kdf.go create mode 100644 src/crypto/internal/hpke/kem.go delete mode 100644 src/crypto/internal/hpke/testdata/rfc9180-vectors.json create mode 100644 src/crypto/internal/hpke/testdata/rfc9180.json diff --git a/src/crypto/internal/hpke/aead.go b/src/crypto/internal/hpke/aead.go new file mode 100644 index 0000000000..1a606c68db --- /dev/null +++ b/src/crypto/internal/hpke/aead.go @@ -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 +} diff --git a/src/crypto/internal/hpke/hpke.go b/src/crypto/internal/hpke/hpke.go index 599bf97771..e7cc55a88c 100644 --- a/src/crypto/internal/hpke/hpke.go +++ b/src/crypto/internal/hpke/hpke.go @@ -2,354 +2,217 @@ // 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 } diff --git a/src/crypto/internal/hpke/hpke_test.go b/src/crypto/internal/hpke/hpke_test.go index 395552476f..3655900b0a 100644 --- a/src/crypto/internal/hpke/hpke_test.go +++ b/src/crypto/internal/hpke/hpke_test.go @@ -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 index 0000000000..c5d6147f4c --- /dev/null +++ b/src/crypto/internal/hpke/kdf.go @@ -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 index 0000000000..2f5d2956ea --- /dev/null +++ b/src/crypto/internal/hpke/kem.go @@ -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 index 3badbc641e..0000000000 --- a/src/crypto/internal/hpke/testdata/rfc9180-vectors.json +++ /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 index 0000000000..a056d3fc1e --- /dev/null +++ b/src/crypto/internal/hpke/testdata/rfc9180.json @@ -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" + } +] diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 6fe6f34cd2..c8e65e4d3c 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -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 diff --git a/src/crypto/tls/ech.go b/src/crypto/tls/ech.go index d3472d8dc4..c56642f738 100644 --- a/src/crypto/tls/ech.go +++ b/src/crypto/tls/ech.go @@ -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 diff --git a/src/crypto/tls/ech_test.go b/src/crypto/tls/ech_test.go index 96312a44d7..5adbb0293d 100644 --- a/src/crypto/tls/ech_test.go +++ b/src/crypto/tls/ech_test.go @@ -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") } diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 90c5bdacd8..533b8ba31e 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -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 } diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 6539009df6..93ec8643f6 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -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) { -- 2.52.0