cmd/age,tag: implement age1tagpq1.../p256mlkem768tag recipients

Test vectors generated from hpkewg/hpke-pq@19adaeb (hpkewg/hpke-pq#28 +
hpkewg/hpke-pq#32) and cfrg/draft-irtf-cfrg-concrete-hybrid-kems@1bbca40
(cfrg/draft-irtf-cfrg-concrete-hybrid-kems#16), plus the following diff:

diff --git a/reference-implementation/src/bin/generate.rs b/reference-implementation/src/bin/generate.rs
index 25e32e5..bc8f209 100644
--- a/reference-implementation/src/bin/generate.rs
+++ b/reference-implementation/src/bin/generate.rs
@@ -26,6 +26,15 @@ fn generate_test_vectors() -> TestVectors {
     // 5. QSF-P384-MLKEM1024 + SHAKE256 + AES-256-GCM
     vectors.push(TestVector:🆕:<QsfP384MlKem1024, Shake256, Aes256Gcm>());
 
+    vectors = TestVectors::new();
+
+    // age1pq - xwing
+    vectors.push(TestVector:🆕:<QsfX25519MlKem768, HkdfSha256, ChaChaPoly>());
+    // age1tag - p256tag
+    vectors.push(TestVector:🆕:<DhkemP256HkdfSha256, HkdfSha256, ChaChaPoly>());
+    // age1tagpq - p256mlkem768tag
+    vectors.push(TestVector:🆕:<QsfP256MlKem768, HkdfSha256, ChaChaPoly>());
+
     vectors
 }
 
diff --git a/reference-implementation/src/test_vectors.rs b/reference-implementation/src/test_vectors.rs
index 24335aa..4134fb5 100644
--- a/reference-implementation/src/test_vectors.rs
+++ b/reference-implementation/src/test_vectors.rs
@@ -369,6 +369,10 @@ impl TestVector {
             (0x0051, 0x0011, 0x0002) => self.v::<QsfP384MlKem1024, Shake256, Aes256Gcm>(),
             (0x0051, 0x0011, 0xffff) => self.v::<QsfP384MlKem1024, Shake256, ExportOnly>(),
 
+            // age pq combinations
+            (0x647a, 0x0001, 0x0003) => self.v::<QsfX25519MlKem768, HkdfSha256, ChaChaPoly>(),
+            (0x0050, 0x0001, 0x0003) => self.v::<QsfP256MlKem768, HkdfSha256, ChaChaPoly>(),
+
             _ => Err(format!(
                 "Unsupported algorithm combination: KEM={:#x}, KDF={:#x}, AEAD={:#x}",
                 self.kem_id, self.kdf_id, self.aead_id
This commit is contained in:
Filippo Valsorda
2025-08-17 20:23:59 +01:00
committed by Filippo Valsorda
parent e9295dd867
commit e2d30695f2
8 changed files with 732 additions and 117 deletions

View File

@@ -31,7 +31,7 @@ func (gitHubRecipientError) Error() string {
func parseRecipient(arg string) (age.Recipient, error) {
switch {
case strings.HasPrefix(arg, "age1tag1"):
case strings.HasPrefix(arg, "age1tag1") || strings.HasPrefix(arg, "age1tagpq1"):
return tag.ParseRecipient(arg)
case strings.HasPrefix(arg, "age1") && strings.Count(arg, "1") > 1:
return plugin.NewRecipient(arg, pluginTerminalUI)

3
go.mod
View File

@@ -1,6 +1,6 @@
module filippo.io/age
go 1.19
go 1.24.0
require (
filippo.io/edwards25519 v1.1.0
@@ -13,6 +13,7 @@ require (
// Test dependencies.
require (
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805
filippo.io/mlkem768 v0.0.0-20250818110517-29047ffe79fb
github.com/rogpeppe/go-internal v1.12.0
golang.org/x/tools v0.22.0 // indirect
)

2
go.sum
View File

@@ -2,6 +2,8 @@ c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3I
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/mlkem768 v0.0.0-20250818110517-29047ffe79fb h1:9eVxcquiUiJn/f8DtnqmsN/8Asqw+h9b1+sM3T/Wl44=
filippo.io/mlkem768 v0.0.0-20250818110517-29047ffe79fb/go.mod h1:ncYN/Z4GaQBV6TIbmQ7+lIaI+qGXCmZr88zrXHneVHs=
filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw=
filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=

View File

@@ -8,8 +8,10 @@ import (
"crypto/cipher"
"crypto/ecdh"
"crypto/hkdf"
"crypto/mlkem"
"crypto/rand"
"crypto/sha256"
"crypto/sha3"
"encoding/binary"
"errors"
"hash"
@@ -131,6 +133,135 @@ func (dh *dhkemRecipient) Decap(encPubEph []byte) ([]byte, error) {
return dh.extractAndExpand(dhVal, kemContext)
}
type qsf struct {
id uint16
label string
}
func (q *qsf) ID() uint16 {
return q.id
}
func (q *qsf) sharedSecret(ssPQ, ssT, ctT, ekT []byte) []byte {
h := sha3.New256()
h.Write(ssPQ)
h.Write(ssT)
h.Write(ctT)
h.Write(ekT)
h.Write([]byte(q.label))
return h.Sum(nil)
}
type qsfSender struct {
qsf
t *ecdh.PublicKey
pq *mlkem.EncapsulationKey768
}
// QSFSender returns a KEMSender implementing QSF-P256-MLKEM768-SHAKE256-SHA3256
// or QSF-X25519-MLKEM768-SHA3256-SHAKE256 (aka X-Wing) from draft-ietf-hpke-pq
// and draft-irtf-cfrg-concrete-hybrid-kems-00.
func QSFSender(t *ecdh.PublicKey, pq *mlkem.EncapsulationKey768) (KEMSender, error) {
switch t.Curve() {
case ecdh.P256():
return &qsfSender{
t: t, pq: pq,
qsf: qsf{
id: 0x0050,
label: "QSF-P256-MLKEM768-SHAKE256-SHA3256",
},
}, nil
case ecdh.X25519():
return &qsfSender{
t: t, pq: pq,
qsf: qsf{
id: 0x647a,
label: /**/ `\./` +
/* */ `/^\`,
},
}, nil
default:
return nil, errors.New("unsupported curve")
}
}
var testingOnlyEncapsulate func() (ss, ct []byte)
func (s *qsfSender) Encap() (sharedSecret []byte, encapPub []byte, err error) {
skE, err := s.t.Curve().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
if testingOnlyGenerateKey != nil {
skE = testingOnlyGenerateKey()
}
ssT, err := skE.ECDH(s.t)
if err != nil {
return nil, nil, err
}
ctT := skE.PublicKey().Bytes()
ssPQ, ctPQ := s.pq.Encapsulate()
if testingOnlyEncapsulate != nil {
ssPQ, ctPQ = testingOnlyEncapsulate()
}
ss := s.sharedSecret(ssPQ, ssT, ctT, s.t.Bytes())
ct := append(ctPQ, ctT...)
return ss, ct, nil
}
type qsfRecipient struct {
qsf
t *ecdh.PrivateKey
pq *mlkem.DecapsulationKey768
}
// QSFRecipient returns a KEMRecipient implementing QSF-P256-MLKEM768-SHAKE256-SHA3256
// or QSF-MLKEM768-X25519-SHA3256-SHAKE256 (aka X-Wing) from draft-ietf-hpke-pq
// and draft-irtf-cfrg-concrete-hybrid-kems-00.
func QSFRecipient(t *ecdh.PrivateKey, pq *mlkem.DecapsulationKey768) (KEMRecipient, error) {
switch t.Curve() {
case ecdh.P256():
return &qsfRecipient{
t: t, pq: pq,
qsf: qsf{
id: 0x0050,
label: "QSF-P256-MLKEM768-SHAKE256-SHA3256",
},
}, nil
case ecdh.X25519():
return &qsfRecipient{
t: t, pq: pq,
qsf: qsf{
id: 0x647a,
label: /**/ `\./` +
/* */ `/^\`,
},
}, nil
default:
return nil, errors.New("unsupported curve")
}
}
func (r *qsfRecipient) Decap(enc []byte) ([]byte, error) {
ctPQ, ctT := enc[:mlkem.CiphertextSize768], enc[mlkem.CiphertextSize768:]
ssPQ, err := r.pq.Decapsulate(ctPQ)
if err != nil {
return nil, err
}
pub, err := r.t.Curve().NewPublicKey(ctT)
if err != nil {
return nil, err
}
ssT, err := r.t.ECDH(pub)
if err != nil {
return nil, err
}
ss := r.sharedSecret(ssPQ, ssT, ctT, r.t.PublicKey().Bytes())
return ss, nil
}
type KDF interface {
LabeledExtract(sid, salt []byte, label string, inputKey []byte) ([]byte, error)
LabeledExpand(suiteID, randomKey []byte, label string, info []byte, length uint16) ([]byte, error)

View File

@@ -7,13 +7,19 @@ package hpke
import (
"bytes"
"crypto/ecdh"
"crypto/elliptic"
"crypto/mlkem"
"crypto/sha3"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"strconv"
"strings"
"testing"
"filippo.io/mlkem768"
)
func mustDecodeHex(t *testing.T, in string) []byte {
@@ -25,106 +31,85 @@ 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/hpke-pq.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"`
EncapRand string `json:"encap_rand"`
IkmR string `json:"ikmR"`
SkRm string `json:"skRm"`
PkRm string `json:"pkRm"`
Enc string `json:"enc"`
SuiteID string `json:"suite_id"`
Key string `json:"key"`
BaseNonce string `json:"base_nonce"`
Encryptions []struct {
Aad string `json:"aad"`
Ct string `json:"ct"`
Nonce string `json:"nonce"`
Pt string `json:"pt"`
} `json:"encryptions"`
}
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)
name := fmt.Sprintf("kem %04x kdf %04x aead %04x",
vector.KEM, vector.KDF, vector.AEAD)
t.Run(name, func(t *testing.T) {
info := mustDecodeHex(t, vector.Info)
pubKeyBytes := mustDecodeHex(t, vector.PkRm)
pubT, pubPQ := parsePublicKey(t, vector.KEM, pubKeyBytes)
kemID, err := strconv.Atoi(setup["kem_id"])
var kemSender KEMSender
if pubPQ != nil {
kemSender, err = QSFSender(pubT, pubPQ)
} else {
kemSender, err = DHKEMSender(pubT)
}
if err != nil {
t.Fatal(err)
}
kdfID, err := strconv.Atoi(setup["kdf_id"])
kdf, err := getKDF(vector.KDF)
if err != nil {
t.Fatal(err)
}
aeadID, err := strconv.Atoi(setup["aead_id"])
if err != nil {
t.Fatal(err)
}
info := mustDecodeHex(t, setup["info"])
pubKeyBytes := mustDecodeHex(t, setup["pkRm"])
pub, err := parsePublicKey(uint16(kemID), pubKeyBytes)
aead, err := getAEAD(vector.AEAD)
if err != nil {
t.Fatal(err)
}
ephemeralPrivKey := mustDecodeHex(t, setup["skEm"])
encapsRand := mustDecodeHex(t, vector.EncapRand)
setupEncapDerand(t, vector.KEM, encapsRand, pubPQ, kdf)
testingOnlyGenerateKey = func() *ecdh.PrivateKey {
priv, err := parsePrivateKey(uint16(kemID), ephemeralPrivKey)
if err != nil {
t.Fatal(err)
}
return priv
}
t.Cleanup(func() { testingOnlyGenerateKey = nil })
kemSender, err := DHKEMSender(pub)
if err != nil {
t.Fatal(err)
}
kdf, err := getKDF(uint16(kdfID))
if err != nil {
t.Fatal(err)
}
aead, err := getAEAD(uint16(aeadID))
if err != nil {
t.Fatal(err)
}
encap, sender, err := SetupSender(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 := parsePrivateKey(uint16(kemID), privKeyBytes)
if err != nil {
t.Fatal(err)
}
privKeyBytes := mustDecodeHex(t, vector.SkRm)
privT, privQ := parsePrivateKey(t, vector.KEM, privKeyBytes)
kemRecipient, err := DHKEMRecipient(priv)
var kemRecipient KEMRecipient
if privQ != nil {
kemRecipient, err = QSFRecipient(privT, privQ)
} else {
kemRecipient, err = DHKEMRecipient(privT)
}
if err != nil {
t.Fatal(err)
}
@@ -133,33 +118,33 @@ func TestRFC9180Vectors(t *testing.T) {
t.Fatal(err)
}
for _, ctx := range []*context{sender.context, recipient.context} {
expectedKey := mustDecodeHex(t, setup["key"])
if !bytes.Equal(ctx.key, expectedKey) {
t.Errorf("unexpected key, got: %x, want %x", ctx.key, expectedKey)
for i, ctx := range []*context{sender.context, recipient.context} {
name := []string{"sender", "recipient"}[i]
expectedSuiteID := mustDecodeHex(t, vector.SuiteID)
if !bytes.Equal(ctx.suiteID, expectedSuiteID) {
t.Errorf("%s: unexpected suite ID, got: %x, want %x", name, ctx.suiteID, expectedSuiteID)
}
expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"])
expectedKey := mustDecodeHex(t, vector.Key)
if !bytes.Equal(ctx.key, expectedKey) {
t.Errorf("%s: unexpected key, got: %x, want %x", name, ctx.key, expectedKey)
}
expectedBaseNonce := mustDecodeHex(t, vector.BaseNonce)
if !bytes.Equal(ctx.baseNonce, expectedBaseNonce) {
t.Errorf("unexpected base nonce, got: %x, want %x", ctx.baseNonce, expectedBaseNonce)
t.Errorf("%s: unexpected base nonce, got: %x, want %x", name, ctx.baseNonce, expectedBaseNonce)
}
}
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"])
for i, enc := range vector.Encryptions {
name := fmt.Sprintf("encryption %d", i)
t.Run(name, func(t *testing.T) {
expectedNonce := mustDecodeHex(t, enc.Nonce)
computedNonce := sender.nextNonce()
if !bytes.Equal(computedNonce, expectedNonce) {
t.Errorf("unexpected nonce: got %x, want %x", computedNonce, expectedNonce)
}
expectedCiphertext := mustDecodeHex(t, enc["ct"])
ciphertext, err := sender.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"]))
expectedCiphertext := mustDecodeHex(t, enc.Ct)
ciphertext, err := sender.Seal(mustDecodeHex(t, enc.Aad), mustDecodeHex(t, enc.Pt))
if err != nil {
t.Fatal(err)
}
@@ -167,8 +152,8 @@ func TestRFC9180Vectors(t *testing.T) {
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"]))
expectedPlaintext := mustDecodeHex(t, enc.Pt)
plaintext, err := recipient.Open(mustDecodeHex(t, enc.Aad), mustDecodeHex(t, enc.Ct))
if err != nil {
t.Fatal(err)
}
@@ -181,21 +166,155 @@ func TestRFC9180Vectors(t *testing.T) {
}
}
func parsePublicKey(kemID uint16, keyBytes []byte) (*ecdh.PublicKey, error) {
func parsePublicKey(t *testing.T, kemID uint16, keyBytes []byte) (*ecdh.PublicKey, *mlkem.EncapsulationKey768) {
switch kemID {
case 0x0010: // DHKEM(P-256, HKDF-SHA256)
return ecdh.P256().NewPublicKey(keyBytes)
k, err := ecdh.P256().NewPublicKey(keyBytes)
if err != nil {
t.Fatal(err)
}
return k, nil
case 0x0050: // QSF-P256-MLKEM768-SHAKE256-SHA3256
pq, err := mlkem.NewEncapsulationKey768(keyBytes[:mlkem.EncapsulationKeySize768])
if err != nil {
t.Fatal(err)
}
k, err := ecdh.P256().NewPublicKey(keyBytes[mlkem.EncapsulationKeySize768:])
if err != nil {
t.Fatal(err)
}
return k, pq
case 0x647a: // QSF-X25519-MLKEM768-SHAKE256-SHA3256
pq, err := mlkem.NewEncapsulationKey768(keyBytes[:mlkem.EncapsulationKeySize768])
if err != nil {
t.Fatal(err)
}
k, err := ecdh.X25519().NewPublicKey(keyBytes[mlkem.EncapsulationKeySize768:])
if err != nil {
t.Fatal(err)
}
return k, pq
default:
return nil, errors.New("unsupported KEM")
t.Fatalf("unsupported KEM %04x", kemID)
panic("unreachable")
}
}
func parsePrivateKey(kemID uint16, keyBytes []byte) (*ecdh.PrivateKey, error) {
func p256KeyFromSeedQSF(t *testing.T, seed []byte) *ecdh.PrivateKey {
t.Helper()
if len(seed) != 48 {
t.Fatalf("invalid seed length %d, expected 48", len(seed))
}
s := new(big.Int).Mod(new(big.Int).SetBytes(seed), elliptic.P256().Params().P)
sb := make([]byte, 32)
s.FillBytes(sb)
k, err := ecdh.P256().NewPrivateKey(sb)
if err != nil {
t.Fatalf("failed to create P-256 private key: %v", err)
}
return k
}
func p256KeyFromSeedDHKEM(t *testing.T, seed []byte, kdf KDF, suiteID []byte) *ecdh.PrivateKey {
// RFC 9180, Section 7.1.3. Only for testing, without rejection handling.
t.Helper()
if len(seed) != 32 {
t.Fatalf("invalid seed length %d, expected 32", len(seed))
}
prk, err := kdf.LabeledExtract(suiteID, nil, "dkp_prk", seed)
if err != nil {
t.Fatalf("failed to extract PRK: %v", err)
}
s, err := kdf.LabeledExpand(suiteID, prk, "candidate", []byte{0x00}, 32)
if err != nil {
t.Fatalf("failed to expand candidate: %v", err)
}
k, err := ecdh.P256().NewPrivateKey(s)
if err != nil {
t.Fatalf("failed to create P-256 private key: %v", err)
}
return k
}
func setupEncapDerand(t *testing.T, kemID uint16, randBytes []byte, pubPQ *mlkem.EncapsulationKey768, kdf KDF) {
switch kemID {
case 0x0010: // DHKEM(P-256, HKDF-SHA256)
return ecdh.P256().NewPrivateKey(keyBytes)
suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), kemID)
k := p256KeyFromSeedDHKEM(t, randBytes, kdf, suiteID)
testingOnlyGenerateKey = func() *ecdh.PrivateKey { return k }
t.Cleanup(func() { testingOnlyGenerateKey = nil })
case 0x0050: // QSF-P256-MLKEM768-SHAKE256-SHA3256
pqRand, tRand := randBytes[:32], randBytes[32:]
k := p256KeyFromSeedQSF(t, tRand)
testingOnlyGenerateKey = func() *ecdh.PrivateKey { return k }
t.Cleanup(func() { testingOnlyGenerateKey = nil })
testingOnlyEncapsulate = func() ([]byte, []byte) {
ct, ss, err := mlkem768.EncapsulateDerand(pubPQ.Bytes(), pqRand)
if err != nil {
t.Fatal(err)
}
return ss, ct
}
t.Cleanup(func() { testingOnlyEncapsulate = nil })
case 0x647a: // QSF-X25519-MLKEM768-SHAKE256-SHA3256
pqRand, tRand := randBytes[:32], randBytes[32:]
k, err := ecdh.X25519().NewPrivateKey(tRand)
if err != nil {
t.Fatal(err)
}
testingOnlyGenerateKey = func() *ecdh.PrivateKey { return k }
t.Cleanup(func() { testingOnlyGenerateKey = nil })
testingOnlyEncapsulate = func() ([]byte, []byte) {
ct, ss, err := mlkem768.EncapsulateDerand(pubPQ.Bytes(), pqRand)
if err != nil {
t.Fatal(err)
}
return ss, ct
}
t.Cleanup(func() { testingOnlyEncapsulate = nil })
default:
return nil, errors.New("unsupported KEM")
t.Fatal("unsupported KEM")
}
}
func parsePrivateKey(t *testing.T, kemID uint16, keyBytes []byte) (*ecdh.PrivateKey, *mlkem.DecapsulationKey768) {
switch kemID {
case 0x0010: // DHKEM(P-256, HKDF-SHA256)
k, err := ecdh.P256().NewPrivateKey(keyBytes)
if err != nil {
t.Fatal(err)
}
return k, nil
case 0x0050: // QSF-P256-MLKEM768-SHAKE256-SHA3256
s := sha3.NewSHAKE256()
s.Write(keyBytes)
exp := make([]byte, mlkem.SeedSize+48)
s.Read(exp)
pq, err := mlkem.NewDecapsulationKey768(exp[:mlkem.SeedSize])
if err != nil {
t.Fatal(err)
}
k := p256KeyFromSeedQSF(t, exp[mlkem.SeedSize:])
return k, pq
case 0x647a: // QSF-X25519-MLKEM768-SHAKE256-SHA3256
s := sha3.NewSHAKE256()
s.Write(keyBytes)
exp := make([]byte, mlkem.SeedSize+32)
s.Read(exp)
pq, err := mlkem.NewDecapsulationKey768(exp[:mlkem.SeedSize])
if err != nil {
t.Fatal(err)
}
k, err := ecdh.X25519().NewPrivateKey(exp[mlkem.SeedSize:])
if err != nil {
t.Fatal(err)
}
return k, pq
default:
t.Fatalf("unsupported KEM %04x", kemID)
panic("unreachable")
}
}

320
tag/internal/hpke/testdata/hpke-pq.json vendored Normal file
View File

@@ -0,0 +1,320 @@
[
{
"mode": 0,
"kem_id": 25722,
"kdf_id": 1,
"aead_id": 3,
"info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665",
"encap_rand": "19f270b5955d8c21d2033111b9d16d0c06c282a75eea4f7dc945e0939d6fa8d983985a0d098204532afa26bb0df2442d900999c8f6d53d1e619633a2270ea622",
"ikmR": "b78c64611bd91ab62f5d75855092796fae54f28863d47c58d3973b3748b0196b",
"skRm": "b78c64611bd91ab62f5d75855092796fae54f28863d47c58d3973b3748b0196b",
"pkRm": "193c149214bfa9d3c14ee192e5844cea7a0a3066b196a6527feba3f41370d65572bc65a5a80709867a299b5877a6a94f6e034576f58d3d534e05bba480e41ac5a63110c67e0bd4bea5e82fa2d0a5cbe6905001be7b267d3e283163c49569f9b343f56c4acaa55e5b24667b45aa86503c2780f5b97df0b29561790ad4f2118ca12daaeb9385d61e2703b6880c0acf8b9b19ec1a578c97f7aa036db2488f840094187dfd6067e6f744d9e50e4b441fc564308426ca92dcc89d41b28125a9544c4a2dbaacd43b8817699ebb104c1937be19e349cf5918a2d35c8ba301d4691b3d657b1d032971835b3a7cb4d554171f83a55f3455d1d34e44cb2f8ce699416c5a4eb79adefc3525b42adb433500c2987c0459e736a05399628b4179fd246ea06b090be610064856be191369b93d900a88fc39c93a941c37583529ec5757ab21004c3f97fc11ead8a015d75a5d9355c1287c84a36086e209b05a3038c2a64e21672bea1ff7c385904b099f747791161ca8b09bf222b3a4f615679b959f6424cf520078c42e7545ad80cb6a17d5be8f711a208653293782cc3c94a091cac50bb3b55b13f8c234f4c70b6bfa72e09357548386e785b523571a0c51a6874567a35b037e01b83340a575275c022a2426b0380a4a0f9f0491b7bacee32bbf8eab8a70e62abe31292b7503f274063415c7d676a862d2175738069a0644e8ab95bef87b1568192edc7a05a77843c70bbebb98f24cce20cc0aceb3713e6065b69b9e29987eaba0c669d3c7c3e8221f4680bf45825042892bb0556a694b7d954498435949917093581ee09a0c874c6a323c7d7447472c192cd5e3007742092369c606ab9e2279bbd06c7d2bcc8e43cc69487604294a457580af5962125fe25664e5c2abeb4be3b1943e9b5dfd14a68b873c7b3049647c2188d1b93b7947b38bc7665043193252f1f6450d640832b33cc7274eaa72734720afc0b9b2fed7c838a3778769299761508d507b76ccce76525c822b9a4f8ca3165c3fcd35b0af2c4d3d34c5aec05dd1fcc0222093f7ca5fec1a9ae0c48fc37862c1582d9c042fbdfca0de8550dbda839cf48c1e816bff17c8d7f5470f2463ea77c9c84a42737b3117e2a3086607543514927bac51b5b5d258bc71204519ec980927c2d2c31fe9d86a5b3869654230013583bff3a7c99210817588853acf6350c1976bbb9df0a82ee6a7a2d8305c575a46b570bfd39dde7754ef6273a1b30b78ec85dc6c1bcc49c6a7bc1591e4384acb13d2dbbd58a7153bdcb07e727fb765cd0b57667da0c32ec254c393a171c6abf3b88531827307d95f40b78f4c152183544f77f548e6c064a7aa567e40ae2ce0912e09b3f24169c78528121b10618c6e17341e7b8b26c9d821e584503c786102fb9110dac136fa52ce3acb9b492c9632aa84f7674898620119c1bf1103a6405fdcb25b40928857bb54a9297d80bc1cca7639b9f4422a666b3bd122e9c9baabe1a04dd950c3919784c647823b6f472673fc0012b960b546d17b7fa94d27d836c5709f86a9022a382518172fd6aa5be0a373ab12bd93687f6a52743f685f62b1472d5b0b59a740d484c56a2bcf0df47133dc95aa37529dda6b36679e1a924df2c88ee180134f41bdb90c4e77b2fc9be781a77f7e9beeb7badd04f884b7547b0279ece1cef0486d045bf812a32d802ff5f5a54549e93ebcab143e7b70c46148421f2d",
"enc": "c40b97a1af0f4066fa9626ba68b1980185dac3a7cb1ad6ca63650e78b4d305ed2ee557ae5dbd3df9c807cce1aa88d739d35feb4d06735484cd8507cd4eeb4c0fb2c1abef0c7e0cb1177841ba7397d8f6a1a2d226de046659903df93cd26322786b1626cd86579d03cc9d5c568bdb6123826380f46e2990fa9bae9dffb0126ca61da6326528a215c84bbd401999b9861cb81b8cca0ac72298dfa400589ba91c87dc1bc48981439bf02120637fac354e5bfea3b0c6de84b2e726a450b791ab38b96fadc74f6348118f9359b9eaf860463c76f7d1d9aafb213230bdf4dd9cb536c8a5c913307fea36dbffc53b59dbb6b88fb71c4526508eed79cb33d59c996e828e89ef29383a6236605020fdb1202b5aa1e7b30c54da7490e8228cfa460f95a0c17778609acac11ae969c54543f05078568b330ea6795aab1d049ebf871881a3192da35d045b3bbd6e5542fff3d060e880e6dc10cc27c123b003aa8c5e0ca36fea6bd20c34ad8ac8b759df0c87e3fa780a1c75b543a7d85ec7ee187eab34d53477cde9dd22503e602ccc9fa9bf725a0c6a176058ae05b2e44d5b790a46b432ca3f9c69b83f49c1b71b8585cdc0a4bc676f1d8744bf9d8a036c2628b11281ac2f243113a08dd81716f88c697a2b123c2e8bd61c74ce4d64edaca7313aaaa877f6f8c9c1f766aabf8734e6ac6117b7c3e8c1dfc1cd6f082f675cf28507cc09162b441180daaf1784f702c173f914478e1bef288e0d5ed644fa4e3d66e8e1de8fd9fc32a4f0cf31791c84d4a362ffd730caa6bbc0ee9d2d2e41422b5f4337b55566512580b5866bcd5390fa72b6a07396281818e2cfefbdef7c9859d4cde52ec098ef802c835eb7eeb453f4ad819a90929b2664533f427f56709f238753d284894bb0393d2dd7c3f31c80535764f5d4e20062b8fa7a025f1583aa3b8c145d064ce22730e68bc322d78d225110b0851f193555c3e07e517f3e79b445f5d7356f1ca681f7c678a018f250b18a08c6b87d83415707c33404609c969b6865cb5f0bdbc78f736b790ef588fee12fb38ea7f083608ab362e3c72a5e8b6c086223ecd234e1358f50823e11099337134bbec85a203e32b5915ca100c9fcd505b9f36055fff011e2f691f57fc6aa567b79492e8cbca09e174c2d2446b240ad20c9d9c16b321c1cdb16c709b1130ff0b3456abe305a9e2c0a514ce41251044fdc83b99d20c03b62b7bfd5a3e3bc4df60e5cbc7544de9d34c97d0bf9248d30b7eb686f0c520624eff3a60cd9b5999a3fef79184d0c63d1dde140de5a92d3f997ca4ceb0f002e5437e7ce6dd6640668dbc5afeb5795b65720f87f363fda1a0f9fcc38ba21e8f9b280083049a3378c6c119fe761759faf6e79d1fb487447b87e4daff98f36521bff863f9556ba7f0fbcc6c99c9b6c1db9f278fa6bf9dcc24790825947ed8b1b9404372e38fe07dee698b47c449b608718f92ef66dc19658db870997e21c1c262ab89a68504286214a03cd291d2a41d749c5f56633542f50ebdeaccbdfcbc7999ab8880319b9ce0ef73d15958f5f168b8ad29e108a20186d906567497f5b2218d06a35",
"shared_secret": "51d7c1b3038e92e6e402de53a915c490c0a3333f7a56fd4c5ab9e2d9e29c8e80",
"suite_id": "48504b45647a00010003",
"key": "edce21c81f84b213a0997c408a566fa901f97dcd27427d2bb94ca660119a8683",
"base_nonce": "2fda4392a0b23a352d70f0e0",
"exporter_secret": "0f2816531c4db55d8d2e45c0d55ff00fa07cfa98216e4dbe58632b8e0160d5c9",
"encryptions": [
{
"aad": "436f756e742d30",
"ct": "b177c7b2d4d4dd604722231ee430f4c6334bfef1fe7bd9e95782f7995e37dcc0a2b902a9675cde8fd05f6d9a0f2f109de0533e16c34ed557516050eb9d620d2768e05278d451aeb390c9",
"nonce": "2fda4392a0b23a352d70f0e0",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d31",
"ct": "c4cc0bf69d0a6f059a61f32243955851757274af96075ba473cafa2615bbc1abc0fd1b8522213ea7b93a77881ce531818644266d2deb9c9ec2cea9c2922fceaa7e79b2d32678590a450c",
"nonce": "2fda4392a0b23a352d70f0e1",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d32",
"ct": "249a52262884fd861965d335eab7f6674460177390f607b83b9ac26c126d28141bffd5538607c73e9b1a3f2931e1e65f00034189a062d80f2560c00f24b506cab0d02d4ab95a3260d58e",
"nonce": "2fda4392a0b23a352d70f0e2",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d33",
"ct": "829ec3fb97f05b6ca0c392add4fa3ff518256c739072b84fc78315e15fc0cca9b129f02313729af796d27f0155fdb0e65dbc1c1bb68022a0ee47545d88fbcc3ec60e978f01656faa1a11",
"nonce": "2fda4392a0b23a352d70f0e3",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d34",
"ct": "bab611de27d0d7f951e4f9368d6486d51104da8e887ad8e20637b9db506fde90034f29610f88e0ef70358f836366b9c20b83999cd3a5076a6256e20223e172ed54b15e4b2548caeffdff",
"nonce": "2fda4392a0b23a352d70f0e4",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d35",
"ct": "fb480f295b8f3e41c5cfa7aa3064f727e648c2d43982dcbcc1d04f8c2f538055bda8aac03e9c7a77eb50eef87a3dd3f0a47fb7230433e13d91ff194912846f69effe3f4a223eb7e965fc",
"nonce": "2fda4392a0b23a352d70f0e5",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d36",
"ct": "0da3d3112e53368b006bbd510d33769f3487fec9563f62abac563287a3a53ad032591cb57128a12fcbe689be33e92b02e96ecb5c12fed23aa517ef7be59f4dca9060ca359c971e014a2f",
"nonce": "2fda4392a0b23a352d70f0e6",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d37",
"ct": "8c3a4ca5373704e664f5266cff459b40f0d2e1b855134b7e668453eb95bce1b7a09eefffcae866b7bd886a9aaba10dc1b012cf1037b0d80963c71aaf2c619ac877144dbf01a9d57390df",
"nonce": "2fda4392a0b23a352d70f0e7",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d38",
"ct": "144e33c04ab411a116253b6858ea27d57ccb0b247f83c013d10193e50010504155b4b8b4572b83410b8f008749b873d5c159027334c074762f860f4d60e41c207838e0f444545af1152c",
"nonce": "2fda4392a0b23a352d70f0e8",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d39",
"ct": "d92a59325eac0496a2c33c48e12bc76b637150d4db794ad5c8ea14bf8c742351b6e74b65464dc50af0ec4d364219ed36f56ece2b5260e850d5c24660e2240f440e07fdb462be20fac811",
"nonce": "2fda4392a0b23a352d70f0e9",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
}
],
"exports": [
{
"exporter_context": "70736575646f72616e646f6d30",
"L": 32,
"exported_value": "dfe6bdeea3a82eef95414480fdda8ecf9ea8d43846dd86072348a07183b52215"
},
{
"exporter_context": "70736575646f72616e646f6d31",
"L": 32,
"exported_value": "cef76709dd0e384a97a5babcb77b221c2c30b223d3926b4b353b64767dbabedf"
},
{
"exporter_context": "70736575646f72616e646f6d32",
"L": 32,
"exported_value": "b12381e05242f2a609545b9a3d826ecf2183f871314ee52c6f64760c6f636c86"
},
{
"exporter_context": "70736575646f72616e646f6d33",
"L": 32,
"exported_value": "f0245f260ba33d995197bf06e6b8330cdac8b72dc3fc6e712ed4d3f6241b93dd"
},
{
"exporter_context": "70736575646f72616e646f6d34",
"L": 32,
"exported_value": "2b279cb0c27ecce59d305e369b31b2c467a60946014278ca8bdca12c384f7a8d"
}
]
},
{
"mode": 0,
"kem_id": 16,
"kdf_id": 1,
"aead_id": 3,
"info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665",
"encap_rand": "ae3c5f0e0d711f220f174b948620b6a9a84931f1510a1e78fe75735ffa585c29",
"ikmR": "f60dafbaa4dae9c499d09cedd84143297a66c23097bc4e69d1e5c89d1d6d7fc2",
"skRm": "5d1e0a06d9d5159783d89efb66b82fdf81f16f1ff5cd81e39a117275312a80d0",
"pkRm": "0469d46c0d5acbf0813fec4cbf81309675e822b6740983a55d5eb905d5e07a86dc70378bbfa6a1d9aa7269f98eeed2d9882346d5b0f5477e84918445853c267065",
"enc": "04885fb4ad2c5088593ee72afb295a709684ba2c016561b27d62d4fc39d2c884e5df85d77d3366d922726ebe95fd3aa2b8019fc1cde75b53684f21e8612ff48f6c",
"shared_secret": "3b26aca70d3510ae3acab4c117ede13249de20dc1fad0b0c6262137457c333e8",
"suite_id": "48504b45001000010003",
"key": "7aaed1082c439b9520fdaf4b7da76a52e53f92b592f35266b3683a72297436d2",
"base_nonce": "4e5d41b58438f9ddd494a510",
"exporter_secret": "c01ee22ac8dd68260f02c9e29aa0745819a8327443eadcf6a4e57c69c613ffb8",
"encryptions": [
{
"aad": "436f756e742d30",
"ct": "1e26c3096c6c768dcc2ec1ec41d188bbeaf6bc6c6bd24e43ba313fac8d9eb3140592204e29cd1ee40aa5ccfacdd2c36e6cad773baf7bb9cfa8f53e626a4728d910043eda957fc01b04c8",
"nonce": "4e5d41b58438f9ddd494a510",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d31",
"ct": "e3a595f805e18a948dc5a84cdf693f8903ead0ab73c7cc9bdab7f749d607967c7672f2ffced518116bf55a1db2157b73079ea4cb1a07b3df6826858e609e0e1a1900e6a6a867ff33d8f3",
"nonce": "4e5d41b58438f9ddd494a511",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d32",
"ct": "0dd72194de92d55f648f029ccf7665a4635356c3c0ad9d9877ca99bfeb0c26f0a9194c00e025019dccb2015bfabed58542798caf305a25d03b934add6c8894632c9490cab1d5d8b8fb52",
"nonce": "4e5d41b58438f9ddd494a512",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d33",
"ct": "053ef6c31808e8c250debc14794e135b077441746b4760f13fe4a90cbeffcaf63ae284c1156aa0db6a9f8d5dc8189e6e9d284f0dbe96b8dea196a631d0f287ff3bb3df185a136b6e4a12",
"nonce": "4e5d41b58438f9ddd494a513",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d34",
"ct": "d8add82d80ba3e65c72d5b34a9edd19980ca2fc28b473867c4205d4fc7b4f0a3d84070624d4f6d922311e80dc4de402f90a745cd58bf022b00852ecc4187c27da22518b4132121601562",
"nonce": "4e5d41b58438f9ddd494a514",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d35",
"ct": "d13fbdda5d6048bbdef281fb2207487f71144c0e4025c439eac0a73f679e0c2f3ac2cf748f1a3af8607434507f85f63f06f9ee1891a951b3d5222c807acbad3759fe49e0b0cd86fa4872",
"nonce": "4e5d41b58438f9ddd494a515",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d36",
"ct": "f05bcdce13576896bc8044965477e1c2450cec84ceaad9a04c93fae1ee2863fbe9c6d1944f681b7aa44ae803e9849b8a5f47ea6464f26f1b5e19af28daa56ff5cc969f5d21ce061fc446",
"nonce": "4e5d41b58438f9ddd494a516",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d37",
"ct": "5c6fe7f4e7f61305e99f8de862102d00bb0300f5342c78b535a2159359d5fe7512bd3232988e97e6b46a988e3e0cf1207e749e3d2206631c994792dc1075f8d63f49ec8f02956239dce0",
"nonce": "4e5d41b58438f9ddd494a517",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d38",
"ct": "e7af3bea35ed4d63b9a4a9a9125112e13e783620f6767c92cf1daad63a27c6dd68f87d17009e70094b2ca9eee4ffaabc9a45d966fd74c7ac2cae4aa3bf4007dae88d7929a2623569ce04",
"nonce": "4e5d41b58438f9ddd494a518",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d39",
"ct": "082552f1ca06613e68279ef115742dd353259260ca055b38436d570f3dc404e335baddd4368ae21cc24e1e1850e97d7580659c0796221ed37b561732a01bc2234bc4dc653af483e88955",
"nonce": "4e5d41b58438f9ddd494a519",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
}
],
"exports": [
{
"exporter_context": "70736575646f72616e646f6d30",
"L": 32,
"exported_value": "4d06b6f07867c5031ba504e4f5467c316fca853f4f79b8246311af7d4bb77263"
},
{
"exporter_context": "70736575646f72616e646f6d31",
"L": 32,
"exported_value": "304ca02f1e562aff01e6059955a8c1aff4189e5b274e8d2f4f46cb56333fa48b"
},
{
"exporter_context": "70736575646f72616e646f6d32",
"L": 32,
"exported_value": "25003705b62981a81cb6a6b7c5766f62163f2e52bac488ae47e10a2eb439a68e"
},
{
"exporter_context": "70736575646f72616e646f6d33",
"L": 32,
"exported_value": "c787490330f5c4b667f2f51a03117c36e983b87e1cc7c2c8def1e0df42cbd034"
},
{
"exporter_context": "70736575646f72616e646f6d34",
"L": 32,
"exported_value": "157124564aab5e3ec50342ed8b76cff77ff589314b636b66df4d8aff5f96716e"
}
]
},
{
"mode": 0,
"kem_id": 80,
"kdf_id": 1,
"aead_id": 3,
"info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665",
"encap_rand": "bd31d63122a4c4cf37244a31ba6acec390ce06f412ad3cbef973c03f3a32602e899409cbd7b4f9ea2a29d5f45952dc1368836b7d1b2a627e1fa94bcc799fcdd20d63f763872837ffe279632acc12d85f",
"ikmR": "9ed4e7555bc2c65f43e2ff3a5beb826daca1a79e7bc89ddae587659ac87e82fb",
"skRm": "9ed4e7555bc2c65f43e2ff3a5beb826daca1a79e7bc89ddae587659ac87e82fb",
"pkRm": "65070243e439adeac9e3bc7bb3c569d22027d1009640b7ae404a6ed0849c2f9599e16943d105b81a196f1f2990ac0204f8e35041032cddb1170a39c79c79568b01bcb62549fcc78bde6546387ac5e64767a1ca9f65cc2fd8704ad199c90d8184257cac4e853d0a0c89eed239f3d5bb52a4005f11cce49bb7569b8553968923056902fa5ac080bc80e404116143eba0210f2b43231a28058824f7136fcb1b14759a3e81e5ca292505747659c488abcf25219ac7cfa5887b2bba7a9754922148cd45054e3b5b57ac9b6666b17f13787284359eb695109923491513930d173dc8c2708dabc6303cac77eb51f349a31ed3c108fb8a3f8537247bc7fae93c706b264ca0323e122c5b5b78b4f0c385867fdb6440ecd804eff8be81362043156740a183016a4f418573db6ccbedc9b9dcc9347eca8912f46bcf02a115c67b206994becc3104c45f04154a0aac9391d57e2d835479385eea571db7921ccbb35575ca8bae196a9784a4de76ac6d003d6be1a090dbc993795e39f5243924717b8462e2e7b564b7770e81abcf47a96abb872c758d68d7a0cb1ba00ee43fab1abd3435b31bc33f2c3b2a0b51c4c60c5e2c280bbfc423d07c31e4e6475ee138daab3d8c799d59d04eca814204e58168798e4188b53685c0df2085f006c709e3b63e7a16c3027a12ac081be6cc822376bdf8168e8253b1271d63501480b535127a068eeab7a7da2c26764dbb73b1f749a5ab572ef0904d7ed072782206df76a3e27570be88a33f364d65e5679d3c3ff3522f843396aa71536b3578fb8336f7f5adf1a59308007e5da949fc80332b40098da8611a0139e3315fc90c2454a2243759b8d1f6a5925685637a43a0f14199e9bce0213d5d20c808694680a01c75c057f3b19fb8218e18d3cad6743616e8814381a25a54a1a32a5f1f045288f5b567f03d7a052cd54400da215c2d7a0acc7c4fb0f06c1024a7100940e328843037449b04a0c2114937fa279ab94ec83a7e3b232df9b5a947c0a3bdf06b31544d253bbc0439050fd352149119d5526cf8c9abe565b6414b721b76422c752a4176205f82a3d89a0a4b5b20b56bb9bd0bb43c51814843ab8f402f34820cfadc71558713e41023373ab5bf0ac5a54541bfb0ad54e17e177a9b8ddb284741beb188c1e396006479044a12afe0281096a0c554b4a843c97d45ca4def0bb68e8345453c6899fa63c3208b98b60b48424cc2926e96514ca1d5bb09d2c3db043f29fcbeee064c8b860d9a01d094aacb2f351f76c70f2774b55a24a74fd29007f206a1acbde0044820cbb708e2a9b08a11910989accab8d001c7d83cb3063393e5bc1450577658b6b5ae1b9fe3c0c36e39658f33abba40ce7a357706186af3f621db8952fa7311d03c05a50274159009951233e6c15cfc2151b7a6c44596beb4090d9bb39c6b1aa75d3c638ad0768b2180dd672ab179654b144f8f1c237a5785315b162858570f1a2bd7a5201133cfa9eb0725f154011a960862bbf48bba699288966a4543a99f26ca44fa65aef8c4bb02f833b3685def4b270ecb470c3c7b0bc0ceb74137c64449963751989c63196acb64bc63ea9a48d96672f44035949b3c6439a5cec59433c335d832bbffc92a1f3e03e8a5d3272e35b725657f724711059361818c23285f53dca378840a4b5e04486919d414ea7b6203a1e71a8204b78696220a9f385232f097e28505c93b963ac7d2a3d6cc53794d14e70319332bc67c9b93c787fef6808dd84e10a208bb3890",
"enc": "0ec138280990dd2deeeb9ceba63f944e12e551e788dd268e8680622ec47a9500ad1f83f21f3ffffd764e318c86fce86ca75e5de9c9124d8ab5b4baa36b64743ed449738166677a6564c2ee9b4abd33d42c2c7e66bc9bf1fef96028214934deedd763be2e2567f6021aad99ba3a477d1565f4ae35a129549e620464daa564f7569db5a5d24ac6a01f43e7879885349c547d288fe253c3feadb244510b38cd65344477b2bff0e2b12db4f69b3cc0219b868d11a1a6d61ee2e76c1b598920c30149004b77d523d3991863df21011bd4ca589c1081880c00fce107292d0bfd770dd42a68e7b12ebb173766421986c0015ad7cabc1c191a26e84d692f167deac56cae31fc992a9fc6b2fd15e7c161f926d46e5d9ee478c90ea5195005f62de0d75beafeb828c2336c6070fc254e5aacd4ae74ff148b614468aaeeccd0c0089de312524e343b72805d71bd534c4e3daadeac64a1cc683dc311c917663d81e0a937f553e59c8f17ec754b476212c6a4e155c05ee2c2b79dcd3bc0e75aa2b7185a2345b981c710083574389f2710f2c658d6ae236ed0e8e75ccf3aaeb9bc34fd7251306128d2b3a3cd921c51f93ede6ae7a68192775efd4c242f80fa87142394615dd1a27e92fa4c9a7030f416e183c42b63e73ab2dd42775fbbc26e0040defd97530d1142da3f5bd0a3f021478e1c46f45f0ff520a474c067544fc3ead1d7782ee666082a40c1dfe12a7d679ca19e5a0775c444e48a7e1c6a4223178dabaa5f99b202cddf0834373e90f54368a7892b17784b4ec04ae7546d0cf29914d1672c9e67cae3fc92943487053f00ad23c1adfa4f70c4297dc68914eefcf9f0b37f06832651766a67ba5bce8a020433ae1fad356cdccecb9b8d37b8fc41217e1d5093ffd156aaca4fe7821151c03e730806ec770be5977e7e68ce1c02ffcfd20f773dece498f1683d568eb171f5122fa1e2ae1aa186a4782ca2adc40927f05b59b577fe0c7e95591b7936517780fdec2177eb1061347716e931f97f0d7da89fee1ea790564af8eaf7314aaf5196af51e8590d1bda044b0f504f0040f83798b22c1722bcf7a0bc713efed24dcbe2c30c77cdf4f22fda3a96ddebcf7c3546be2daf728bc7f312cff4b5706af353d74a519652cd274a0bd4116168a138d311da2877aa190803aef91455d9faa733eb0912177739f47b1951f7da23104f5b9e249f25270bdb0e58c290d3c64f681ab9202b42987d392d3f94d58c1e81a4f4e62293e95fb011cb2b8f49f5ca2fd85f399a386f91ead63bbd97cf2bb68b372cbd1b081ac9bcf14109ab0298f4b0d7c9b16f75090b1c49721b71747a357065c4833cccd52a2c7406f1c141e8cc669678b62fdfe119610dbdec0ec87b0ea54c71f64ea0598672b40becd957594e2c07d5cb0e90dc4378ed110a1b4450f42497c2efd699f91a774ca29a370f76d4fb04dd1ac3fa0779805587819ab6f1785a3330ef60aec8e707e945c776611f530f245e6f554ab408fc1193d0f08dd7773629132e65ae4f3e70bcb0a71004494def2db94f30c53f4c17e39ebf15efbae07810dce16868256c9abfb5b1c339e670c223739a81057db8e7d8bda8ecd9a7cdadaf4350f050ecd10e679137eb1e",
"shared_secret": "61765d0dc46c62192c9800d74ab8ec77c633810720055663fbe6a0d57bded97c",
"suite_id": "48504b45005000010003",
"key": "9b8e5d4f868de455a2f29cd8dc127513be97ec9e0f616e45b7ece8d8e55ac17d",
"base_nonce": "599544220e8706a477505030",
"exporter_secret": "dbfe1ea45410d7b5c857e41c5ce41fad4ba0d9a3ad5617b9a13e0fd26c62b6b9",
"encryptions": [
{
"aad": "436f756e742d30",
"ct": "def109ce3c4d4d489e585b1cebf86c679665121425c729754c034036b914f7f0ca14c52da51419e2c9a677f1186994c8eb6e707f42acd66f07d15bba920eb87d6687158ebe8f9615215e",
"nonce": "599544220e8706a477505030",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d31",
"ct": "677528b4d1a8eb24b9964c95f7a844bd97e6b1e40eed75575326336e4d6fc57d9b41fc42bf2bd61152a76578ff5729279c5d975e2f4f1fae7263a9370a40c9ae28105064487ad4feb3ff",
"nonce": "599544220e8706a477505031",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d32",
"ct": "0d11a1ac1f6b6d29702c358793f440685b74505f8b0d318997a2fb6c451ccd633e342690120a9c767eb0614fe3cfd518484c974df0c1459881c897b7ed590785ccdea03baa086d5da33b",
"nonce": "599544220e8706a477505032",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d33",
"ct": "0f23a23ae250f808c2a29ba52c214b10ca6cc95fbbc99e48d35ac8bb38f9a251bceaac0d73ef168ec68f645e1cf3ef56175fd63608e4cf0eeb02aaa2005535530d77da6a80ab63aac334",
"nonce": "599544220e8706a477505033",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d34",
"ct": "5e3c3d049535375c3a6de075c1cc5b619b734c99b9c2be9e9413087fe66576a9ca7629094e3293fc8fb443c21464e9070dd8b14d31701bc61a9aa8dbe0a6c45531322f6616d96197c79c",
"nonce": "599544220e8706a477505034",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d35",
"ct": "f8ffd8ad031ea2e8dac99d810b2f969db2ca7c1a5d581f8a8f16a9db6b3cc7a8620ea16ff438568100c40b094cf951c53abc30c5ff6f2e2a25b3242ed04b193d29138de02c792d0b883c",
"nonce": "599544220e8706a477505035",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d36",
"ct": "fee465b871efa4e2f428f9a66458cfa2a7b99774ebeb630ed548f2fa22caaf5ab50e9fed77293aa312c7c5209100a97455dc92f4b4cb4c4e07b3547e7e73228e18f152c67915c71cdcac",
"nonce": "599544220e8706a477505036",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d37",
"ct": "582afc61ffb3ffd7704d76088d2c74cc2530f6bdd6593cbb2977239b6f484716bdedb0d6b1b129f45e1d4afc8f407d17fadd3a3d971c82f8369fd6772d5f2d5274cfff48c3d2db63a44d",
"nonce": "599544220e8706a477505037",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d38",
"ct": "81ce8e9ae429b85b4dd4b40d7f7bb426565d23f2c3d63f74ea96dbba881dfcfe33b95c8202ff37b15bb14f85d52de1a506c3e2b42d650e850fe97a63017670ee52815705dd61421589fc",
"nonce": "599544220e8706a477505038",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
},
{
"aad": "436f756e742d39",
"ct": "691608a0e0e29d0c2def20bf6f0991bb7eccf57f26722975f3640b4dc23f4be29cfa60352ef3831e6b560e896c766a0126746c380dd3f695b82e4039549202ef01809ee5fde406f2c8a1",
"nonce": "599544220e8706a477505039",
"pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739"
}
],
"exports": [
{
"exporter_context": "70736575646f72616e646f6d30",
"L": 32,
"exported_value": "fe89a3b25515355f1e831529a2306040e6a9edb1643acab8bb9182dc09a029f5"
},
{
"exporter_context": "70736575646f72616e646f6d31",
"L": 32,
"exported_value": "d79a2633bd36ff4220e355f381303398abc48ceca0491410791afb733c5fc882"
},
{
"exporter_context": "70736575646f72616e646f6d32",
"L": 32,
"exported_value": "60a3cfd587ce2bf68235328399ca47ef336f63a2c16a57129aa824b8238e8e85"
},
{
"exporter_context": "70736575646f72616e646f6d33",
"L": 32,
"exported_value": "678cbb9bda711aa6e4f1108db0fa9e9e0585764895d9adbd3b5c633d92dacdc0"
},
{
"exporter_context": "70736575646f72616e646f6d34",
"L": 32,
"exported_value": "1df9fd502558a98bdd8c80b54d2811e9537850d6b8254567a23c6f049818fc17"
}
]
}
]

View File

@@ -1,7 +0,0 @@
[
{
"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"
}
]

View File

@@ -7,6 +7,7 @@ package tag
import (
"crypto/ecdh"
"crypto/hkdf"
"crypto/mlkem"
"crypto/sha256"
"fmt"
@@ -20,32 +21,44 @@ import (
type Recipient struct {
kem hpke.KEMSender
compressed [33]byte
uncompressed [65]byte
mlkem *mlkem.EncapsulationKey768
compressed [compressedPointSize]byte
uncompressed [uncompressedPointSize]byte
}
var _ age.Recipient = &Recipient{}
// ParseRecipient returns a new [Recipient] from a Bech32 public key
// encoding with the "age1tag1" prefix.
// encoding with the "age1tag1" or "age1tagpq1" prefix.
func ParseRecipient(s string) (*Recipient, error) {
t, k, err := plugin.ParseRecipient(s)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
if t != "tag" {
switch t {
case "tag":
r, err := NewRecipient(k)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
case "tagpq":
r, err := NewHybridRecipient(k)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
default:
return nil, fmt.Errorf("malformed recipient %q: invalid type %q", s, t)
}
r, err := NewRecipient(k)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
}
const compressedPointSize = 1 + 32
const uncompressedPointSize = 1 + 32 + 32
// NewRecipient returns a new [Recipient] from a raw public key.
func NewRecipient(publicKey []byte) (*Recipient, error) {
if len(publicKey) != 1+32 {
if len(publicKey) != compressedPointSize {
return nil, fmt.Errorf("invalid tag recipient public key size %d", len(publicKey))
}
p, err := nistec.NewP256Point().SetBytes(publicKey)
@@ -66,12 +79,44 @@ func NewRecipient(publicKey []byte) (*Recipient, error) {
return r, nil
}
// NewHybridRecipient returns a new [Recipient] from raw concatenated public keys.
func NewHybridRecipient(publicKey []byte) (*Recipient, error) {
if len(publicKey) != compressedPointSize+mlkem.EncapsulationKeySize768 {
return nil, fmt.Errorf("invalid tagpq recipient public key size %d", len(publicKey))
}
p, err := nistec.NewP256Point().SetBytes(publicKey)
if err != nil {
return nil, fmt.Errorf("invalid tagpq recipient DH public key: %v", err)
}
k, err := ecdh.P256().NewPublicKey(p.Bytes())
if err != nil {
return nil, fmt.Errorf("invalid tagpq recipient DH public key: %v", err)
}
pq, err := mlkem.NewEncapsulationKey768(publicKey[compressedPointSize:])
if err != nil {
return nil, fmt.Errorf("invalid tagpq recipient PQ public key: %v", err)
}
kem, err := hpke.QSFSender(k, pq)
if err != nil {
return nil, fmt.Errorf("failed to create DHKEM sender: %v", err)
}
r := &Recipient{kem: kem, mlkem: pq}
copy(r.compressed[:], publicKey[:compressedPointSize])
copy(r.uncompressed[:], p.Bytes())
return r, nil
}
var p256TagLabel = []byte("age-encryption.org/p256tag")
var p256MLKEM768TagLabel = []byte("age-encryption.org/p256mlkem768tag")
func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
label, arg := p256TagLabel, "p256tag"
if r.mlkem != nil {
label, arg = p256MLKEM768TagLabel, "p256mlkem768tag"
}
enc, s, err := hpke.SetupSender(r.kem,
hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(),
p256TagLabel)
hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), label)
if err != nil {
return nil, fmt.Errorf("failed to set up HPKE sender: %v", err)
}
@@ -80,13 +125,14 @@ func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
return nil, fmt.Errorf("failed to encrypt file key: %v", err)
}
tag, err := hkdf.Extract(sha256.New, append(enc, r.uncompressed[:]...), p256TagLabel)
tag, err := hkdf.Extract(sha256.New,
append(enc[:uncompressedPointSize], r.uncompressed[:]...), label)
if err != nil {
return nil, fmt.Errorf("failed to compute tag: %v", err)
}
l := &age.Stanza{
Type: "p256tag",
Type: arg,
Args: []string{
format.EncodeToString(tag[:4]),
format.EncodeToString(enc),
@@ -99,5 +145,8 @@ func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
// String returns the Bech32 public key encoding of r.
func (r *Recipient) String() string {
if r.mlkem != nil {
return plugin.EncodeRecipient("tagpq", append(r.compressed[:], r.mlkem.Bytes()...))
}
return plugin.EncodeRecipient("tag", r.compressed[:])
}