mirror of
https://github.com/FiloSottile/age.git
synced 2025-12-23 05:25:14 +00:00
tag: use filippo.io/hpke
This commit is contained in:
committed by
Filippo Valsorda
parent
e2d30695f2
commit
6ece9e45ee
8
go.mod
8
go.mod
@@ -4,16 +4,16 @@ go 1.24.0
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0
|
||||
filippo.io/hpke v0.4.0
|
||||
filippo.io/nistec v0.0.3
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/term v0.21.0
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
)
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -2,17 +2,17 @@ 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/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
|
||||
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
|
||||
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=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hpke
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/hkdf"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha3"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"math/bits"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type KEMSender interface {
|
||||
Encap() (sharedSecret, enc []byte, err error)
|
||||
ID() uint16
|
||||
}
|
||||
|
||||
type KEMRecipient interface {
|
||||
Decap(enc []byte) (sharedSecret []byte, err error)
|
||||
ID() uint16
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// DHKEMSender returns a KEMSender implementing DHKEM(P-256, HKDF-SHA256).
|
||||
func DHKEMSender(pub *ecdh.PublicKey) (KEMSender, error) {
|
||||
switch pub.Curve() {
|
||||
case ecdh.P256():
|
||||
return &dhkemSender{
|
||||
pub: pub,
|
||||
dhKEM: dhKEM{
|
||||
kdf: HKDFSHA256(),
|
||||
id: 0x0010,
|
||||
nSecret: 32,
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported curve")
|
||||
}
|
||||
}
|
||||
|
||||
// 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.PrivateKey
|
||||
}
|
||||
|
||||
// DHKEMRecipient returns a KEMRecipient implementing DHKEM(P-256, HKDF-SHA256).
|
||||
func DHKEMRecipient(priv *ecdh.PrivateKey) (KEMRecipient, error) {
|
||||
switch priv.Curve() {
|
||||
case ecdh.P256():
|
||||
return &dhkemRecipient{
|
||||
priv: priv,
|
||||
dhKEM: dhKEM{
|
||||
kdf: HKDFSHA256(),
|
||||
id: 0x0010,
|
||||
nSecret: 32,
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported curve")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
ID() uint16
|
||||
}
|
||||
|
||||
type hkdfKDF struct {
|
||||
hash func() hash.Hash
|
||||
id uint16
|
||||
}
|
||||
|
||||
func HKDFSHA256() KDF {
|
||||
return &hkdfKDF{hash: sha256.New, id: 0x0001}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
type AEAD interface {
|
||||
AEAD(key []byte) (cipher.AEAD, error)
|
||||
KeySize() int
|
||||
NonceSize() int
|
||||
ID() uint16
|
||||
}
|
||||
|
||||
type aead struct {
|
||||
keySize int
|
||||
nonceSize int
|
||||
aead func([]byte) (cipher.AEAD, error)
|
||||
id uint16
|
||||
}
|
||||
|
||||
func ChaCha20Poly1305() AEAD {
|
||||
return &aead{
|
||||
keySize: chacha20poly1305.KeySize,
|
||||
nonceSize: chacha20poly1305.NonceSize,
|
||||
aead: chacha20poly1305.New,
|
||||
id: 0x0003,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aead) ID() uint16 {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aead) AEAD(key []byte) (cipher.AEAD, error) {
|
||||
if len(key) != a.keySize {
|
||||
return nil, errors.New("invalid key size")
|
||||
}
|
||||
return a.aead(key)
|
||||
}
|
||||
|
||||
func (a *aead) KeySize() int {
|
||||
return a.keySize
|
||||
}
|
||||
|
||||
func (a *aead) NonceSize() int {
|
||||
return a.nonceSize
|
||||
}
|
||||
|
||||
type context struct {
|
||||
aead cipher.AEAD
|
||||
suiteID []byte
|
||||
|
||||
key []byte
|
||||
baseNonce []byte
|
||||
|
||||
seqNum uint128
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
*context
|
||||
}
|
||||
|
||||
type Recipient struct {
|
||||
*context
|
||||
}
|
||||
|
||||
func newContext(sharedSecret []byte, kemID uint16, kdf KDF, aead AEAD, info []byte) (*context, error) {
|
||||
sid := suiteID(kemID, kdf.ID(), aead.ID())
|
||||
|
||||
pskIDHash, err := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoHash, err := kdf.LabeledExtract(sid, nil, "info_hash", info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ksContext := append([]byte{0}, pskIDHash...)
|
||||
ksContext = append(ksContext, infoHash...)
|
||||
|
||||
secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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(aead.NonceSize()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a, err := aead.AEAD(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &context{
|
||||
aead: a,
|
||||
suiteID: sid,
|
||||
key: key,
|
||||
baseNonce: baseNonce,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetupSender(kem KEMSender, kdf KDF, aead AEAD, info []byte) ([]byte, *Sender, error) {
|
||||
sharedSecret, encapsulatedKey, err := kem.Encap()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return encapsulatedKey, &Sender{context}, nil
|
||||
}
|
||||
|
||||
func SetupRecipient(kem KEMRecipient, kdf KDF, aead AEAD, info, enc []byte) (*Recipient, error) {
|
||||
sharedSecret, err := kem.Decap(enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context, err := newContext(sharedSecret, kem.ID(), kdf, aead, 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]
|
||||
}
|
||||
return nonce
|
||||
}
|
||||
|
||||
func (ctx *context) incrementNonce() {
|
||||
ctx.seqNum = ctx.seqNum.addOne()
|
||||
}
|
||||
|
||||
func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
|
||||
ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
|
||||
s.incrementNonce()
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) {
|
||||
plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.incrementNonce()
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func suiteID(kemID, kdfID, aeadID uint16) []byte {
|
||||
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
|
||||
}
|
||||
|
||||
type uint128 struct {
|
||||
hi, lo uint64
|
||||
}
|
||||
|
||||
func (u uint128) addOne() uint128 {
|
||||
lo, carry := bits.Add64(u.lo, 1, 0)
|
||||
return uint128{u.hi + carry, lo}
|
||||
}
|
||||
|
||||
func (u uint128) bytes() []byte {
|
||||
b := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(b[0:], u.hi)
|
||||
binary.BigEndian.PutUint64(b[8:], u.lo)
|
||||
return b
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hpke
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdh"
|
||||
"crypto/elliptic"
|
||||
"crypto/mlkem"
|
||||
"crypto/sha3"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"filippo.io/mlkem768"
|
||||
)
|
||||
|
||||
func mustDecodeHex(t *testing.T, in string) []byte {
|
||||
t.Helper()
|
||||
b, err := hex.DecodeString(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
vectorsJSON, err := os.ReadFile("testdata/hpke-pq.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var vectors []struct {
|
||||
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 {
|
||||
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)
|
||||
|
||||
var kemSender KEMSender
|
||||
if pubPQ != nil {
|
||||
kemSender, err = QSFSender(pubT, pubPQ)
|
||||
} else {
|
||||
kemSender, err = DHKEMSender(pubT)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kdf, err := getKDF(vector.KDF)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aead, err := getAEAD(vector.AEAD)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
encapsRand := mustDecodeHex(t, vector.EncapRand)
|
||||
setupEncapDerand(t, vector.KEM, encapsRand, pubPQ, kdf)
|
||||
|
||||
encap, sender, err := SetupSender(kemSender, kdf, aead, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedEncap := mustDecodeHex(t, vector.Enc)
|
||||
if !bytes.Equal(encap, expectedEncap) {
|
||||
t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap)
|
||||
}
|
||||
|
||||
privKeyBytes := mustDecodeHex(t, vector.SkRm)
|
||||
privT, privQ := parsePrivateKey(t, vector.KEM, privKeyBytes)
|
||||
|
||||
var kemRecipient KEMRecipient
|
||||
if privQ != nil {
|
||||
kemRecipient, err = QSFRecipient(privT, privQ)
|
||||
} else {
|
||||
kemRecipient, err = DHKEMRecipient(privT)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
recipient, err := SetupRecipient(kemRecipient, kdf, aead, info, encap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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("%s: unexpected base nonce, got: %x, want %x", name, ctx.baseNonce, expectedBaseNonce)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(plaintext, expectedPlaintext) {
|
||||
t.Errorf("unexpected plaintext: got %x want %x", plaintext, expectedPlaintext)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parsePublicKey(t *testing.T, kemID uint16, keyBytes []byte) (*ecdh.PublicKey, *mlkem.EncapsulationKey768) {
|
||||
switch kemID {
|
||||
case 0x0010: // DHKEM(P-256, HKDF-SHA256)
|
||||
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:
|
||||
t.Fatalf("unsupported KEM %04x", kemID)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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:
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func getKDF(kdfID uint16) (KDF, error) {
|
||||
switch kdfID {
|
||||
case 0x0001: // HKDF-SHA256
|
||||
return HKDFSHA256(), nil
|
||||
default:
|
||||
return nil, errors.New("unsupported KDF")
|
||||
}
|
||||
}
|
||||
|
||||
func getAEAD(aeadID uint16) (AEAD, error) {
|
||||
switch aeadID {
|
||||
case 0x0003: // ChaCha20Poly1305
|
||||
return ChaCha20Poly1305(), nil
|
||||
default:
|
||||
return nil, errors.New("unsupported AEAD")
|
||||
}
|
||||
}
|
||||
320
tag/internal/hpke/testdata/hpke-pq.json
vendored
320
tag/internal/hpke/testdata/hpke-pq.json
vendored
@@ -1,320 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
157
tag/internal/tagtest/tagtest.go
Normal file
157
tag/internal/tagtest/tagtest.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2025 The age 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 tagtest
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hkdf"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"filippo.io/age/tag"
|
||||
"filippo.io/hpke"
|
||||
"filippo.io/nistec"
|
||||
)
|
||||
|
||||
type ClassicIdentity struct {
|
||||
t *testing.T
|
||||
k hpke.PrivateKey
|
||||
}
|
||||
|
||||
var _ age.Identity = &ClassicIdentity{}
|
||||
|
||||
func NewClassicIdentity(t *testing.T) *ClassicIdentity {
|
||||
k, err := hpke.DHKEM(ecdh.P256()).GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key: %v", err)
|
||||
}
|
||||
return &ClassicIdentity{k: k}
|
||||
}
|
||||
|
||||
func (i *ClassicIdentity) Recipient() *tag.Recipient {
|
||||
uncompressed := i.k.PublicKey().Bytes()
|
||||
p, err := nistec.NewP256Point().SetBytes(uncompressed)
|
||||
if err != nil {
|
||||
i.t.Fatalf("failed to parse public key: %v", err)
|
||||
}
|
||||
r, err := tag.NewClassicRecipient(p.BytesCompressed())
|
||||
if err != nil {
|
||||
i.t.Fatalf("failed to create recipient: %v", err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (i *ClassicIdentity) Unwrap(ss []*age.Stanza) ([]byte, error) {
|
||||
for _, s := range ss {
|
||||
if s.Type != "p256tag" {
|
||||
continue
|
||||
}
|
||||
if len(s.Args) != 2 {
|
||||
return nil, fmt.Errorf("malformed stanza")
|
||||
}
|
||||
tagArg, err := format.DecodeString(s.Args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed tag: %v", err)
|
||||
}
|
||||
if len(tagArg) != 4 {
|
||||
return nil, fmt.Errorf("invalid tag length: %d", len(tagArg))
|
||||
}
|
||||
enc, err := format.DecodeString(s.Args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed encapsulated key: %v", err)
|
||||
}
|
||||
if len(enc) != 65 {
|
||||
return nil, fmt.Errorf("invalid encapsulated key length: %d", len(enc))
|
||||
}
|
||||
if len(s.Body) != 32 {
|
||||
return nil, fmt.Errorf("invalid encrypted file key length: %d", len(s.Body))
|
||||
}
|
||||
|
||||
expTag, err := hkdf.Extract(sha256.New, append(enc, i.k.PublicKey().Bytes()...), []byte("age-encryption.org/p256tag"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute tag: %v", err)
|
||||
}
|
||||
if subtle.ConstantTimeCompare(tagArg, expTag[:4]) != 1 {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
r, err := hpke.NewRecipient(enc, i.k, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("age-encryption.org/p256tag"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unwrap file key: %v", err)
|
||||
}
|
||||
return r.Open(nil, s.Body)
|
||||
}
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
type HybridIdentity struct {
|
||||
t *testing.T
|
||||
k hpke.PrivateKey
|
||||
}
|
||||
|
||||
var _ age.Identity = &HybridIdentity{}
|
||||
|
||||
func NewHybridIdentity(t *testing.T) *HybridIdentity {
|
||||
k, err := hpke.MLKEM768P256().GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key: %v", err)
|
||||
}
|
||||
return &HybridIdentity{k: k}
|
||||
}
|
||||
|
||||
func (i *HybridIdentity) Recipient() *tag.Recipient {
|
||||
r, err := tag.NewHybridRecipient(i.k.PublicKey().Bytes())
|
||||
if err != nil {
|
||||
i.t.Fatalf("failed to create recipient: %v", err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (i *HybridIdentity) Unwrap(ss []*age.Stanza) ([]byte, error) {
|
||||
for _, s := range ss {
|
||||
if s.Type != "mlkem768p256tag" {
|
||||
continue
|
||||
}
|
||||
if len(s.Args) != 2 {
|
||||
return nil, fmt.Errorf("malformed stanza")
|
||||
}
|
||||
tagArg, err := format.DecodeString(s.Args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed tag: %v", err)
|
||||
}
|
||||
if len(tagArg) != 4 {
|
||||
return nil, fmt.Errorf("invalid tag length: %d", len(tagArg))
|
||||
}
|
||||
enc, err := format.DecodeString(s.Args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed encapsulated key: %v", err)
|
||||
}
|
||||
if len(enc) != 1153 {
|
||||
return nil, fmt.Errorf("invalid encapsulated key length: %d", len(enc))
|
||||
}
|
||||
if len(s.Body) != 32 {
|
||||
return nil, fmt.Errorf("invalid encrypted file key length: %d", len(s.Body))
|
||||
}
|
||||
|
||||
expTag, err := hkdf.Extract(sha256.New, append(enc[1088:], i.k.PublicKey().Bytes()[1184:]...), []byte("age-encryption.org/mlkem768p256tag"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute tag: %v", err)
|
||||
}
|
||||
if subtle.ConstantTimeCompare(tagArg, expTag[:4]) != 1 {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
r, err := hpke.NewRecipient(enc, i.k, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("age-encryption.org/mlkem768p256tag"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unwrap file key: %v", err)
|
||||
}
|
||||
return r.Open(nil, s.Body)
|
||||
}
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
123
tag/tag.go
123
tag/tag.go
@@ -2,6 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tag implements tagged P-256 or hybrid P-256 + ML-KEM-768 recipients,
|
||||
// which can be used with identities stored on hardware keys, usually supported
|
||||
// by dedicated plugins.
|
||||
//
|
||||
// The tag reduces privacy, by allowing an observer to correlate files with a
|
||||
// recipient (but not files amongst them without knowledge of the recipient),
|
||||
// but this is also a desirable property for hardware keys that require user
|
||||
// interaction for each decryption operation.
|
||||
package tag
|
||||
|
||||
import (
|
||||
@@ -14,16 +22,17 @@ import (
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"filippo.io/age/plugin"
|
||||
"filippo.io/age/tag/internal/hpke"
|
||||
"filippo.io/hpke"
|
||||
"filippo.io/nistec"
|
||||
)
|
||||
|
||||
// Recipient is a tagged P-256 or hybrid P-256 + ML-KEM-768 recipient.
|
||||
//
|
||||
// The latter recipient is safe against future cryptographically-relevant
|
||||
// quantum computers, and can only be used along with other post-quantum
|
||||
// recipients.
|
||||
type Recipient struct {
|
||||
kem hpke.KEMSender
|
||||
|
||||
mlkem *mlkem.EncapsulationKey768
|
||||
compressed [compressedPointSize]byte
|
||||
uncompressed [uncompressedPointSize]byte
|
||||
pk hpke.PublicKey
|
||||
}
|
||||
|
||||
var _ age.Recipient = &Recipient{}
|
||||
@@ -37,7 +46,7 @@ func ParseRecipient(s string) (*Recipient, error) {
|
||||
}
|
||||
switch t {
|
||||
case "tag":
|
||||
r, err := NewRecipient(k)
|
||||
r, err := NewClassicRecipient(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
|
||||
}
|
||||
@@ -54,10 +63,9 @@ func ParseRecipient(s string) (*Recipient, error) {
|
||||
}
|
||||
|
||||
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) {
|
||||
// NewClassicRecipient returns a new P-256 [Recipient] from a raw public key.
|
||||
func NewClassicRecipient(publicKey []byte) (*Recipient, error) {
|
||||
if len(publicKey) != compressedPointSize {
|
||||
return nil, fmt.Errorf("invalid tag recipient public key size %d", len(publicKey))
|
||||
}
|
||||
@@ -65,70 +73,64 @@ func NewRecipient(publicKey []byte) (*Recipient, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
|
||||
}
|
||||
k, err := ecdh.P256().NewPublicKey(p.Bytes())
|
||||
k, err := hpke.DHKEM(ecdh.P256()).NewPublicKey(p.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
|
||||
}
|
||||
kem, err := hpke.DHKEMSender(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create DHKEM sender: %v", err)
|
||||
}
|
||||
r := &Recipient{kem: kem}
|
||||
copy(r.compressed[:], publicKey)
|
||||
copy(r.uncompressed[:], p.Bytes())
|
||||
return r, nil
|
||||
return &Recipient{k}, nil
|
||||
}
|
||||
|
||||
// NewHybridRecipient returns a new [Recipient] from raw concatenated public keys.
|
||||
// NewHybridRecipient returns a new hybrid P-256 + ML-KEM-768 [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)
|
||||
k, err := hpke.MLKEM768P256().NewPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tagpq recipient DH public key: %v", err)
|
||||
return nil, fmt.Errorf("invalid tagpq recipient 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
|
||||
return &Recipient{k}, nil
|
||||
}
|
||||
|
||||
var p256TagLabel = []byte("age-encryption.org/p256tag")
|
||||
var p256MLKEM768TagLabel = []byte("age-encryption.org/p256mlkem768tag")
|
||||
// Hybrid reports whether r is a hybrid P-256 + ML-KEM-768 recipient.
|
||||
func (r *Recipient) Hybrid() bool {
|
||||
return r.pk.KEM().ID() == hpke.MLKEM768P256().ID()
|
||||
}
|
||||
|
||||
func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
|
||||
label, arg := p256TagLabel, "p256tag"
|
||||
if r.mlkem != nil {
|
||||
label, arg = p256MLKEM768TagLabel, "p256mlkem768tag"
|
||||
s, _, err := r.WrapWithLabels(fileKey)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// WrapWithLabels implements [age.RecipientWithLabels], returning a single
|
||||
// "postquantum" label if r is a hybrid P-256 + ML-KEM-768 recipient. This
|
||||
// ensures a hybrid Recipient can't be mixed with other recipients that would
|
||||
// defeat its post-quantum security.
|
||||
//
|
||||
// To unsafely bypass this restriction, wrap Recipient in an [age.Recipient]
|
||||
// type that doesn't expose WrapWithLabels.
|
||||
func (r *Recipient) WrapWithLabels(fileKey []byte) ([]*age.Stanza, []string, error) {
|
||||
label, arg := "age-encryption.org/p256tag", "p256tag"
|
||||
if r.Hybrid() {
|
||||
label, arg = "age-encryption.org/mlkem768p256tag", "mlkem768p256tag"
|
||||
}
|
||||
|
||||
enc, s, err := hpke.SetupSender(r.kem,
|
||||
hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), label)
|
||||
enc, s, err := hpke.NewSender(r.pk, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte(label))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set up HPKE sender: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to set up HPKE sender: %v", err)
|
||||
}
|
||||
ct, err := s.Seal(nil, fileKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt file key: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to encrypt file key: %v", err)
|
||||
}
|
||||
|
||||
tag, err := hkdf.Extract(sha256.New,
|
||||
append(enc[:uncompressedPointSize], r.uncompressed[:]...), label)
|
||||
tagEnc, tagRecipient := enc, r.pk.Bytes()
|
||||
if r.Hybrid() {
|
||||
// In hybrid mode, the tag is computed over just the P-256 part.
|
||||
tagEnc = enc[mlkem.CiphertextSize768:]
|
||||
tagRecipient = tagRecipient[mlkem.EncapsulationKeySize768:]
|
||||
}
|
||||
tag, err := hkdf.Extract(sha256.New, append(tagEnc, tagRecipient...), []byte(label))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compute tag: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to compute tag: %v", err)
|
||||
}
|
||||
|
||||
l := &age.Stanza{
|
||||
@@ -140,13 +142,20 @@ func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
|
||||
Body: ct,
|
||||
}
|
||||
|
||||
return []*age.Stanza{l}, nil
|
||||
if r.Hybrid() {
|
||||
return []*age.Stanza{l}, []string{"postquantum"}, nil
|
||||
}
|
||||
return []*age.Stanza{l}, nil, nil
|
||||
}
|
||||
|
||||
// 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()...))
|
||||
if r.Hybrid() {
|
||||
return plugin.EncodeRecipient("tagpq", r.pk.Bytes())
|
||||
}
|
||||
return plugin.EncodeRecipient("tag", r.compressed[:])
|
||||
p, err := nistec.NewP256Point().SetBytes(r.pk.Bytes())
|
||||
if err != nil {
|
||||
panic("internal error: invalid P-256 public key")
|
||||
}
|
||||
return plugin.EncodeRecipient("tag", p.BytesCompressed())
|
||||
}
|
||||
|
||||
109
tag/tag_test.go
Normal file
109
tag/tag_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 The age 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 tag_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/tag"
|
||||
"filippo.io/age/tag/internal/tagtest"
|
||||
)
|
||||
|
||||
func TestClassicRoundTrip(t *testing.T) {
|
||||
i := tagtest.NewClassicIdentity(t)
|
||||
r := i.Recipient()
|
||||
|
||||
if r.Hybrid() {
|
||||
t.Error("classic recipient incorrectly reports as hybrid")
|
||||
}
|
||||
|
||||
r1, err := tag.ParseRecipient(r.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r1.String() != r.String() {
|
||||
t.Errorf("recipient did not round-trip through parsing: got %q, want %q", r1.String(), r.String())
|
||||
}
|
||||
if r1.Hybrid() {
|
||||
t.Error("parsed classic recipient incorrectly reports as hybrid")
|
||||
}
|
||||
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := &bytes.Buffer{}
|
||||
w, err := age.Encrypt(encrypted, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := w.Write(plaintext); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
decrypted, err := age.Decrypt(encrypted, i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := io.ReadAll(decrypted)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(plaintext, out) {
|
||||
t.Errorf("invalid output: %q, expected %q", out, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHybridRoundTrip(t *testing.T) {
|
||||
i := tagtest.NewHybridIdentity(t)
|
||||
r := i.Recipient()
|
||||
|
||||
if !r.Hybrid() {
|
||||
t.Error("hybrid recipient incorrectly reports as classic")
|
||||
}
|
||||
|
||||
r1, err := tag.ParseRecipient(r.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r1.String() != r.String() {
|
||||
t.Errorf("recipient did not round-trip through parsing: got %q, want %q", r1.String(), r.String())
|
||||
}
|
||||
if !r1.Hybrid() {
|
||||
t.Error("parsed hybrid recipient incorrectly reports as classic")
|
||||
}
|
||||
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := &bytes.Buffer{}
|
||||
w, err := age.Encrypt(encrypted, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := w.Write(plaintext); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
decrypted, err := age.Decrypt(encrypted, i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := io.ReadAll(decrypted)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(plaintext, out) {
|
||||
t.Errorf("invalid output: %q, expected %q", out, plaintext)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user