Skip to content

Commit e5aac5c

Browse files
committed
tag: use new filippo.io/hpke API
1 parent 1172e28 commit e5aac5c

File tree

3 files changed

+44
-67
lines changed

3 files changed

+44
-67
lines changed

go.mod

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ go 1.24.0
44

55
require (
66
filippo.io/edwards25519 v1.1.0
7-
filippo.io/hpke v0.2.0
7+
filippo.io/hpke v0.3.2
88
filippo.io/nistec v0.0.3
9-
golang.org/x/crypto v0.41.0
10-
golang.org/x/sys v0.35.0
11-
golang.org/x/term v0.34.0
9+
golang.org/x/crypto v0.44.0
10+
golang.org/x/sys v0.38.0
11+
golang.org/x/term v0.37.0
1212
)
1313

14-
require filippo.io/bigmod v0.1.0 // indirect
15-
1614
// Test dependencies.
1715
require (
1816
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805

go.sum

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
22
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
3-
filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8=
4-
filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
53
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
64
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
7-
filippo.io/hpke v0.2.0 h1:CyMUXx5gHxxCciek5DtzCXDGy2+kOag0GFxXSH0nD0I=
8-
filippo.io/hpke v0.2.0/go.mod h1:Kn5Q71LEUiHIGCCdOm3JXhj2taQWEnAJUKFnO8OzSKs=
5+
filippo.io/hpke v0.3.2 h1:qKabTApzDPuylJ36fxpck0DBlgzG1dk5fIENbyoE/FY=
6+
filippo.io/hpke v0.3.2/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
97
filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw=
108
filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw=
119
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
1210
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
13-
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
14-
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
15-
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
16-
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
17-
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
18-
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
11+
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
12+
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
13+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
14+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
15+
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
16+
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
1917
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
2018
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=

tag/tag.go

Lines changed: 32 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5+
// Package tag implements tagged P-256 or hybrid P-256 + ML-KEM-768 recipients,
6+
// which can be used with identities stored on hardware keys, usually supported
7+
// by dedicated plugins.
8+
//
9+
// The tag reduces privacy, by allowing an observer to correlate files with a
10+
// recipient (but not files amongst them without knowledge of the recipient),
11+
// but this is also a desirable property for hardware keys that require user
12+
// interaction for each decryption operation.
513
package tag
614

715
import (
@@ -18,12 +26,9 @@ import (
1826
"filippo.io/nistec"
1927
)
2028

29+
// Recipient is a tagged P-256 or hybrid P-256 + ML-KEM-768 recipient.
2130
type Recipient struct {
22-
kem hpke.KEMSender
23-
24-
mlkem *mlkem.EncapsulationKey768
25-
compressed [compressedPointSize]byte
26-
uncompressed [uncompressedPointSize]byte
31+
kem hpke.PublicKey
2732
}
2833

2934
var _ age.Recipient = &Recipient{}
@@ -54,9 +59,8 @@ func ParseRecipient(s string) (*Recipient, error) {
5459
}
5560

5661
const compressedPointSize = 1 + 32
57-
const uncompressedPointSize = 1 + 32 + 32
5862

59-
// NewRecipient returns a new [Recipient] from a raw public key.
63+
// NewRecipient returns a new P-256 [Recipient] from a raw public key.
6064
func NewRecipient(publicKey []byte) (*Recipient, error) {
6165
if len(publicKey) != compressedPointSize {
6266
return nil, fmt.Errorf("invalid tag recipient public key size %d", len(publicKey))
@@ -65,58 +69,30 @@ func NewRecipient(publicKey []byte) (*Recipient, error) {
6569
if err != nil {
6670
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
6771
}
68-
k, err := ecdh.P256().NewPublicKey(p.Bytes())
72+
k, err := hpke.DHKEM(ecdh.P256()).NewPublicKey(p.Bytes())
6973
if err != nil {
7074
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
7175
}
72-
kem, err := hpke.DHKEMSender(k)
73-
if err != nil {
74-
return nil, fmt.Errorf("failed to create DHKEM sender: %v", err)
75-
}
76-
r := &Recipient{kem: kem}
77-
copy(r.compressed[:], publicKey)
78-
copy(r.uncompressed[:], p.Bytes())
79-
return r, nil
76+
return &Recipient{k}, nil
8077
}
8178

82-
// NewHybridRecipient returns a new [Recipient] from raw concatenated public keys.
79+
// NewHybridRecipient returns a new hybrid P-256 + ML-KEM-768 [Recipient] from
80+
// raw concatenated public keys.
8381
func NewHybridRecipient(publicKey []byte) (*Recipient, error) {
84-
if len(publicKey) != compressedPointSize+mlkem.EncapsulationKeySize768 {
85-
return nil, fmt.Errorf("invalid tagpq recipient public key size %d", len(publicKey))
86-
}
87-
p, err := nistec.NewP256Point().SetBytes(publicKey)
88-
if err != nil {
89-
return nil, fmt.Errorf("invalid tagpq recipient DH public key: %v", err)
90-
}
91-
k, err := ecdh.P256().NewPublicKey(p.Bytes())
82+
k, err := hpke.MLKEM768P256().NewPublicKey(publicKey)
9283
if err != nil {
93-
return nil, fmt.Errorf("invalid tagpq recipient DH public key: %v", err)
84+
return nil, fmt.Errorf("invalid tagpq recipient public key: %v", err)
9485
}
95-
pq, err := mlkem.NewEncapsulationKey768(publicKey[compressedPointSize:])
96-
if err != nil {
97-
return nil, fmt.Errorf("invalid tagpq recipient PQ public key: %v", err)
98-
}
99-
kem, err := hpke.QSFSender(k, pq)
100-
if err != nil {
101-
return nil, fmt.Errorf("failed to create DHKEM sender: %v", err)
102-
}
103-
r := &Recipient{kem: kem, mlkem: pq}
104-
copy(r.compressed[:], publicKey[:compressedPointSize])
105-
copy(r.uncompressed[:], p.Bytes())
106-
return r, nil
86+
return &Recipient{k}, nil
10787
}
10888

109-
var p256TagLabel = []byte("age-encryption.org/p256tag")
110-
var p256MLKEM768TagLabel = []byte("age-encryption.org/p256mlkem768tag")
111-
11289
func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
113-
label, arg := p256TagLabel, "p256tag"
114-
if r.mlkem != nil {
115-
label, arg = p256MLKEM768TagLabel, "p256mlkem768tag"
90+
label, arg := "age-encryption.org/p256tag", "p256tag"
91+
if r.kem.KEM().ID() == hpke.MLKEM768P256().ID() {
92+
label, arg = "age-encryption.org/mlkem768p256tag", "p256mlkem768tag"
11693
}
11794

118-
enc, s, err := hpke.NewSender(r.kem,
119-
hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), label)
95+
enc, s, err := hpke.NewSender(r.kem, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte(label))
12096
if err != nil {
12197
return nil, fmt.Errorf("failed to set up HPKE sender: %v", err)
12298
}
@@ -125,8 +101,13 @@ func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
125101
return nil, fmt.Errorf("failed to encrypt file key: %v", err)
126102
}
127103

128-
tag, err := hkdf.Extract(sha256.New,
129-
append(enc[:uncompressedPointSize], r.uncompressed[:]...), label)
104+
tagEnc, tagRecipient := enc, r.kem.Bytes()
105+
if r.kem.KEM().ID() == hpke.MLKEM768P256().ID() {
106+
// In hybrid mode, the tag is computed over just the P-256 part.
107+
tagEnc = enc[mlkem.CiphertextSize768:]
108+
tagRecipient = tagRecipient[mlkem.EncapsulationKeySize768:]
109+
}
110+
tag, err := hkdf.Extract(sha256.New, append(tagEnc, tagRecipient...), []byte(label))
130111
if err != nil {
131112
return nil, fmt.Errorf("failed to compute tag: %v", err)
132113
}
@@ -145,8 +126,8 @@ func (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
145126

146127
// String returns the Bech32 public key encoding of r.
147128
func (r *Recipient) String() string {
148-
if r.mlkem != nil {
149-
return plugin.EncodeRecipient("tagpq", append(r.compressed[:], r.mlkem.Bytes()...))
129+
if r.kem.KEM().ID() == hpke.MLKEM768P256().ID() {
130+
return plugin.EncodeRecipient("tagpq", r.kem.Bytes())
150131
}
151-
return plugin.EncodeRecipient("tag", r.compressed[:])
132+
return plugin.EncodeRecipient("tag", r.kem.Bytes())
152133
}

0 commit comments

Comments
 (0)