key_package.go raw
1 package mls
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "time"
8
9 "golang.org/x/crypto/cryptobyte"
10 )
11
12 // A KeyPackage provides some public information about a user, such as
13 // a supported protocol version and cipher suite, public keys, and credentials.
14 //
15 // Key packages should not be used more than once.
16 type KeyPackage struct {
17 version protocolVersion
18 cipherSuite CipherSuite
19 initKey hpkePublicKey
20 leafNode leafNode
21 extensions []extension
22 signature []byte
23 }
24
25 // UnmarshalKeyPackage reads a key package encoded as an MLS message.
26 func UnmarshalKeyPackage(raw []byte) (*KeyPackage, error) {
27 var msg mlsMessage
28 if err := unmarshal(raw, &msg); err != nil {
29 return nil, err
30 } else if msg.wireFormat != wireFormatMLSKeyPackage {
31 return nil, fmt.Errorf("mls: expected a key package message, got wire format %v", msg.wireFormat)
32 }
33 return msg.keyPackage, nil
34 }
35
36 // Bytes encodes the key package wrapped in an MLSMessage envelope
37 // (version + wireFormat + keyPackage). For the bare TLS serialization
38 // without the envelope, use RawBytes().
39 func (pkg *KeyPackage) Bytes() []byte {
40 raw, err := marshal(&mlsMessage{
41 version: protocolVersionMLS10,
42 wireFormat: wireFormatMLSKeyPackage,
43 keyPackage: pkg,
44 })
45 if err != nil {
46 // should never happen
47 panic(fmt.Errorf("mls: failed to marshal key package message: %v", err))
48 }
49 return raw
50 }
51
52 // RawBytes encodes the key package as bare TLS bytes without the MLSMessage
53 // envelope. This is the format used by NIP-EE (kind 443 events).
54 func (pkg *KeyPackage) RawBytes() []byte {
55 var b cryptobyte.Builder
56 pkg.marshal(&b)
57 raw, err := b.Bytes()
58 if err != nil {
59 panic(fmt.Errorf("mls: failed to marshal raw key package: %v", err))
60 }
61 return raw
62 }
63
64 // UnmarshalRawKeyPackage reads a key package from bare TLS bytes (no MLSMessage
65 // envelope). This is the format used by NIP-EE (kind 443 events).
66 func UnmarshalRawKeyPackage(raw []byte) (*KeyPackage, error) {
67 s := cryptobyte.String(raw)
68 pkg := new(KeyPackage)
69 if err := pkg.unmarshal(&s); err != nil {
70 return nil, err
71 }
72 if !s.Empty() {
73 return nil, fmt.Errorf("mls: raw key package contains %d excess bytes", len(s))
74 }
75 return pkg, nil
76 }
77
78 func (pkg *KeyPackage) unmarshal(s *cryptobyte.String) error {
79 *pkg = KeyPackage{}
80
81 ok := s.ReadUint16((*uint16)(&pkg.version)) &&
82 s.ReadUint16((*uint16)(&pkg.cipherSuite)) &&
83 readOpaqueVec(s, (*[]byte)(&pkg.initKey))
84 if !ok {
85 return io.ErrUnexpectedEOF
86 }
87
88 if pkg.version != protocolVersionMLS10 {
89 return fmt.Errorf("mls: invalid protocol version %d", pkg.version)
90 }
91
92 if err := pkg.leafNode.unmarshal(s); err != nil {
93 return err
94 }
95
96 exts, err := unmarshalExtensionVec(s)
97 if err != nil {
98 return err
99 }
100 pkg.extensions = exts
101
102 if !readOpaqueVec(s, &pkg.signature) {
103 return err
104 }
105
106 return nil
107 }
108
109 func (pkg *KeyPackage) marshalTBS(b *cryptobyte.Builder) {
110 b.AddUint16(uint16(pkg.version))
111 b.AddUint16(uint16(pkg.cipherSuite))
112 writeOpaqueVec(b, []byte(pkg.initKey))
113 pkg.leafNode.marshal(b)
114 marshalExtensionVec(b, pkg.extensions)
115 }
116
117 func (pkg *KeyPackage) marshal(b *cryptobyte.Builder) {
118 pkg.marshalTBS(b)
119 writeOpaqueVec(b, pkg.signature)
120 }
121
122 func (pkg *KeyPackage) sign(signerPriv signaturePrivateKey) error {
123 var b cryptobyte.Builder
124 pkg.marshalTBS(&b)
125 rawTBS, err := b.Bytes()
126 if err != nil {
127 return err
128 }
129
130 sig, err := pkg.cipherSuite.signWithLabel(signerPriv, []byte("KeyPackageTBS"), rawTBS)
131 if err != nil {
132 return err
133 }
134
135 pkg.signature = sig
136 return nil
137 }
138
139 func (pkg *KeyPackage) verifySignature() bool {
140 var b cryptobyte.Builder
141 pkg.marshalTBS(&b)
142 rawTBS, err := b.Bytes()
143 if err != nil {
144 return false
145 }
146
147 return pkg.cipherSuite.verifyWithLabel(pkg.leafNode.signatureKey, []byte("KeyPackageTBS"), rawTBS, pkg.signature)
148 }
149
150 // verify performs KeyPackage verification as described in RFC 9420 section 10.1.
151 func (pkg *KeyPackage) verify(ctx *groupContext) error {
152 if pkg.version != ctx.version {
153 return fmt.Errorf("mls: key package version doesn't match group context")
154 }
155 if pkg.cipherSuite != ctx.cipherSuite {
156 return fmt.Errorf("mls: cipher suite doesn't match group context")
157 }
158 if pkg.leafNode.leafNodeSource != leafNodeSourceKeyPackage {
159 return fmt.Errorf("mls: key package contains a leaf node with an invalid source")
160 }
161 if !pkg.verifySignature() {
162 return fmt.Errorf("mls: invalid key package signature")
163 }
164 if bytes.Equal(pkg.leafNode.encryptionKey, pkg.initKey) {
165 return fmt.Errorf("mls: key package encryption key and init key are identical")
166 }
167 return nil
168 }
169
170 // GenerateRef generates this key package's reference.
171 func (pkg *KeyPackage) GenerateRef() (KeyPackageRef, error) {
172 var b cryptobyte.Builder
173 pkg.marshal(&b)
174 raw, err := b.Bytes()
175 if err != nil {
176 return nil, err
177 }
178
179 hash, err := pkg.cipherSuite.refHash([]byte("MLS 1.0 KeyPackage Reference"), raw)
180 if err != nil {
181 return nil, err
182 }
183
184 return KeyPackageRef(hash), nil
185 }
186
187 // KeyPackageRef is a hash uniquely identifying a key package.
188 type KeyPackageRef []byte
189
190 // Equal checks whether two key package references are equal.
191 func (ref KeyPackageRef) Equal(other KeyPackageRef) bool {
192 return bytes.Equal([]byte(ref), []byte(other))
193 }
194
195 // PrivateKeyPackage holds private information about a user.
196 type PrivateKeyPackage struct {
197 InitKey []byte
198 EncryptionKey []byte
199 SignatureKey []byte
200 }
201
202 // KeyPairPackage holds both public and private information about a user.
203 type KeyPairPackage struct {
204 Public KeyPackage
205 Private PrivateKeyPackage
206 }
207
208 // KeyPackageOptions configures key package generation.
209 type KeyPackageOptions struct {
210 // CapabilityExtensions are extension types to advertise in the leaf
211 // node capabilities (e.g., ExtensionTypeLastResort, ExtensionTypeNostrGroupData).
212 // RatchetTree is always included.
213 CapabilityExtensions []extensionType
214
215 // LeafExtensions are extensions to include in the leaf node itself
216 // (e.g., a LastResort extension with empty data).
217 LeafExtensions []extension
218
219 // KeyPackageExtensions are extensions to include at the key package level.
220 KeyPackageExtensions []extension
221 }
222
223 // GenerateKeyPairPackage generates a new key pair package.
224 func GenerateKeyPairPackage(cs CipherSuite, credential *Credential) (*KeyPairPackage, error) {
225 return GenerateKeyPairPackageWithOptions(cs, credential, nil)
226 }
227
228 // GenerateKeyPairPackageWithOptions generates a new key pair package with
229 // custom capabilities and extensions.
230 func GenerateKeyPairPackageWithOptions(cs CipherSuite, credential *Credential, opts *KeyPackageOptions) (*KeyPairPackage, error) {
231 initPub, initPriv, err := cs.generateEncryptionKeyPair()
232 if err != nil {
233 return nil, err
234 }
235
236 encPub, encPriv, err := cs.generateEncryptionKeyPair()
237 if err != nil {
238 return nil, err
239 }
240
241 sigPub, sigPriv, err := cs.signatureScheme().GenerateKeyPair()
242 if err != nil {
243 return nil, err
244 }
245
246 var capExts []extensionType
247 var leafExts []extension
248 var kpExts []extension
249
250 if opts != nil {
251 capExts = opts.CapabilityExtensions
252 leafExts = opts.LeafExtensions
253 kpExts = opts.KeyPackageExtensions
254 }
255 if capExts == nil {
256 // Default: only ratchetTree for backwards compat when no opts given
257 capExts = []extensionType{extensionTypeRatchetTree}
258 }
259
260 keyPkg := KeyPackage{
261 version: protocolVersionMLS10,
262 cipherSuite: cs,
263 initKey: initPub,
264 leafNode: leafNode{
265 encryptionKey: encPub,
266 signatureKey: sigPub,
267 leafNodeSource: leafNodeSourceKeyPackage,
268 credential: *credential,
269 capabilities: capabilities{
270 versions: []protocolVersion{protocolVersionMLS10},
271 cipherSuites: []CipherSuite{cs},
272 extensions: capExts,
273 proposals: []proposalType{proposalTypeAdd, proposalTypeUpdate, proposalTypeRemove},
274 credentials: []credentialType{credentialTypeBasic},
275 },
276 lifetime: newLifetime(time.Now().Add(-1*time.Hour), time.Now().Add(84*24*time.Hour)),
277 extensions: leafExts,
278 },
279 extensions: kpExts,
280 }
281
282 if err := keyPkg.leafNode.sign(cs, nil, 0, sigPriv); err != nil {
283 return nil, fmt.Errorf("failed to sign leaf node: %v", err)
284 }
285 if err := keyPkg.sign(sigPriv); err != nil {
286 return nil, fmt.Errorf("failed to sign key package: %v", err)
287 }
288
289 return &KeyPairPackage{
290 Public: keyPkg,
291 Private: PrivateKeyPackage{
292 InitKey: initPriv,
293 EncryptionKey: encPriv,
294 SignatureKey: sigPriv,
295 },
296 }, nil
297 }
298