framing.mx raw
1 package mls
2
3 // MLS framing types (RFC 9420 ยง6).
4 // Pure serialization - crypto operations (sign/encrypt/decrypt) are
5 // in framing_crypto.mx once the cipher suite layer is available.
6
7 var (
8 errInvalidContentType error
9 errInvalidSenderType error
10 errInvalidWireFormat error
11 errInvalidVersion error
12 errNonZeroPadding error
13 )
14
15 // --- Protocol version ---
16
17 type protocolVersion uint16
18
19 const (
20 protocolVersionMLS10 protocolVersion = 1
21 )
22
23 // --- Content type ---
24
25 type contentType uint8
26
27 const (
28 contentTypeApplication contentType = 1
29 contentTypeProposal contentType = 2
30 contentTypeCommit contentType = 3
31 )
32
33 func (ct *contentType) unmarshal(r *Reader) (err error) {
34 b, ok := r.readByte()
35 if !ok {
36 return errUnexpectedEOF
37 }
38 *ct = contentType(b)
39 switch *ct {
40 case contentTypeApplication, contentTypeProposal, contentTypeCommit:
41 return nil
42 default:
43 return errInvalidContentType
44 }
45 }
46
47 func (ct *contentType) marshal(w *Writer) {
48 w.addByte(byte(ct))
49 }
50
51 // --- Sender type ---
52
53 type senderType uint8
54
55 const (
56 senderTypeMember senderType = 1
57 senderTypeExternal senderType = 2
58 senderTypeNewMemberProposal senderType = 3
59 senderTypeNewMemberCommit senderType = 4
60 )
61
62 func (st *senderType) unmarshal(r *Reader) (err error) {
63 b, ok := r.readByte()
64 if !ok {
65 return errUnexpectedEOF
66 }
67 *st = senderType(b)
68 switch *st {
69 case senderTypeMember, senderTypeExternal, senderTypeNewMemberProposal, senderTypeNewMemberCommit:
70 return nil
71 default:
72 return errInvalidSenderType
73 }
74 }
75
76 func (st *senderType) marshal(w *Writer) {
77 w.addByte(byte(st))
78 }
79
80 // --- Sender ---
81
82 type sender struct {
83 senderType senderType
84 leafIndex leafIndex // for senderTypeMember
85 senderIndex uint32 // for senderTypeExternal
86 }
87
88 func (snd *sender) unmarshal(r *Reader) (err error) {
89 *snd = sender{}
90 if e := snd.senderType.unmarshal(r); e != nil {
91 return e
92 }
93 switch snd.senderType {
94 case senderTypeMember:
95 v, ok := r.readUint32()
96 if !ok {
97 return errUnexpectedEOF
98 }
99 snd.leafIndex = leafIndex(v)
100 case senderTypeExternal:
101 v, ok := r.readUint32()
102 if !ok {
103 return errUnexpectedEOF
104 }
105 snd.senderIndex = v
106 }
107 return nil
108 }
109
110 func (snd *sender) marshal(w *Writer) {
111 snd.senderType.marshal(w)
112 switch snd.senderType {
113 case senderTypeMember:
114 w.addUint32(uint32(snd.leafIndex))
115 case senderTypeExternal:
116 w.addUint32(snd.senderIndex)
117 }
118 }
119
120 // --- Wire format ---
121
122 type wireFormat uint16
123
124 // http://www.iana.org/assignments/mls/mls.xhtml#mls-wire-formats
125 const (
126 wireFormatMLSPublicMessage wireFormat = 0x0001
127 wireFormatMLSPrivateMessage wireFormat = 0x0002
128 wireFormatMLSWelcome wireFormat = 0x0003
129 wireFormatMLSGroupInfo wireFormat = 0x0004
130 wireFormatMLSKeyPackage wireFormat = 0x0005
131 )
132
133 func (wf *wireFormat) unmarshal(r *Reader) (err error) {
134 v, ok := r.readUint16()
135 if !ok {
136 return errUnexpectedEOF
137 }
138 *wf = wireFormat(v)
139 switch *wf {
140 case wireFormatMLSPublicMessage, wireFormatMLSPrivateMessage,
141 wireFormatMLSWelcome, wireFormatMLSGroupInfo, wireFormatMLSKeyPackage:
142 return nil
143 default:
144 return errInvalidWireFormat
145 }
146 }
147
148 func (wf *wireFormat) marshal(w *Writer) {
149 w.addUint16(uint16(wf))
150 }
151
152 // --- Group identifier ---
153
154 // groupIDEqual reports whether two application-specific group identifiers
155 // are equal.
156 func groupIDEqual(ref []byte, other []byte) (ok bool) {
157 if len(ref) != len(other) {
158 return false
159 }
160 for i := range ref {
161 if ref[i] != other[i] {
162 return false
163 }
164 }
165 return true
166 }
167
168 // --- FramedContent ---
169
170 type framedContent struct {
171 groupID []byte
172 epoch uint64
173 sender sender
174 authenticatedData []byte
175
176 contentType contentType
177 applicationData []byte // for contentTypeApplication
178 proposal *proposal // for contentTypeProposal
179 commit *commit // for contentTypeCommit
180 }
181
182 func (content *framedContent) unmarshal(r *Reader) (err error) {
183 *content = framedContent{}
184
185 var ok bool
186 content.groupID, ok = r.readOpaqueVec()
187 if !ok {
188 return errUnexpectedEOF
189 }
190 content.epoch, ok = r.readUint64()
191 if !ok {
192 return errUnexpectedEOF
193 }
194 if e := content.sender.unmarshal(r); e != nil {
195 return e
196 }
197 content.authenticatedData, ok = r.readOpaqueVec()
198 if !ok {
199 return errUnexpectedEOF
200 }
201 if e := content.contentType.unmarshal(r); e != nil {
202 return e
203 }
204
205 switch content.contentType {
206 case contentTypeApplication:
207 content.applicationData, ok = r.readOpaqueVec()
208 if !ok {
209 return errUnexpectedEOF
210 }
211 return nil
212 case contentTypeProposal:
213 content.proposal = &proposal{}
214 return content.proposal.unmarshal(r)
215 case contentTypeCommit:
216 content.commit = &commit{}
217 return content.commit.unmarshal(r)
218 default:
219 panic("unreachable")
220 }
221 }
222
223 func (content *framedContent) marshal(w *Writer) {
224 w.writeOpaqueVec([]byte(content.groupID))
225 w.addUint64(content.epoch)
226 content.sender.marshal(w)
227 w.writeOpaqueVec(content.authenticatedData)
228 content.contentType.marshal(w)
229 switch content.contentType {
230 case contentTypeApplication:
231 w.writeOpaqueVec(content.applicationData)
232 case contentTypeProposal:
233 content.proposal.marshal(w)
234 case contentTypeCommit:
235 content.commit.marshal(w)
236 default:
237 panic("unreachable")
238 }
239 }
240
241 // --- MLSMessage (top-level wire message) ---
242
243 type mlsMessage struct {
244 version protocolVersion
245 wireFormat wireFormat
246 publicMessage *publicMessage // for wireFormatMLSPublicMessage
247 privateMessage *privateMessage // for wireFormatMLSPrivateMessage
248 welcome *Welcome // for wireFormatMLSWelcome
249 groupInfo *groupInfo // for wireFormatMLSGroupInfo
250 keyPackage *KeyPackage // for wireFormatMLSKeyPackage
251 }
252
253 func (msg *mlsMessage) unmarshal(r *Reader) (err error) {
254 *msg = mlsMessage{}
255
256 v, ok := r.readUint16()
257 if !ok {
258 return errUnexpectedEOF
259 }
260 msg.version = protocolVersion(v)
261 if msg.version != protocolVersionMLS10 {
262 return errInvalidVersion
263 }
264
265 if e := msg.wireFormat.unmarshal(r); e != nil {
266 return e
267 }
268
269 switch msg.wireFormat {
270 case wireFormatMLSPublicMessage:
271 msg.publicMessage = &publicMessage{}
272 return msg.publicMessage.unmarshal(r)
273 case wireFormatMLSPrivateMessage:
274 msg.privateMessage = &privateMessage{}
275 return msg.privateMessage.unmarshal(r)
276 case wireFormatMLSWelcome:
277 msg.welcome = &Welcome{}
278 return msg.welcome.unmarshal(r)
279 case wireFormatMLSGroupInfo:
280 msg.groupInfo = &groupInfo{}
281 return msg.groupInfo.unmarshal(r)
282 case wireFormatMLSKeyPackage:
283 msg.keyPackage = &KeyPackage{}
284 return msg.keyPackage.unmarshal(r)
285 default:
286 panic("unreachable")
287 }
288 }
289
290 func (msg *mlsMessage) marshal(w *Writer) {
291 w.addUint16(uint16(msg.version))
292 msg.wireFormat.marshal(w)
293 switch msg.wireFormat {
294 case wireFormatMLSPublicMessage:
295 msg.publicMessage.marshal(w)
296 case wireFormatMLSPrivateMessage:
297 msg.privateMessage.marshal(w)
298 case wireFormatMLSWelcome:
299 msg.welcome.marshal(w)
300 case wireFormatMLSGroupInfo:
301 msg.groupInfo.marshal(w)
302 case wireFormatMLSKeyPackage:
303 msg.keyPackage.marshal(w)
304 default:
305 panic("unreachable")
306 }
307 }
308
309 // --- FramedContentAuthData ---
310
311 type framedContentAuthData struct {
312 signature []byte
313 confirmationTag []byte // for contentTypeCommit
314 }
315
316 func (authData *framedContentAuthData) unmarshal(r *Reader, ct contentType) (err error) {
317 *authData = framedContentAuthData{}
318
319 var ok bool
320 authData.signature, ok = r.readOpaqueVec()
321 if !ok {
322 return errUnexpectedEOF
323 }
324 if ct == contentTypeCommit {
325 authData.confirmationTag, ok = r.readOpaqueVec()
326 if !ok {
327 return errUnexpectedEOF
328 }
329 }
330 return nil
331 }
332
333 func (authData *framedContentAuthData) marshal(w *Writer, ct contentType) {
334 w.writeOpaqueVec(authData.signature)
335 if ct == contentTypeCommit {
336 w.writeOpaqueVec(authData.confirmationTag)
337 }
338 }
339
340 // --- AuthenticatedContent ---
341
342 type authenticatedContent struct {
343 wireFormat wireFormat
344 content framedContent
345 auth framedContentAuthData
346 }
347
348 func (authContent *authenticatedContent) unmarshal(r *Reader) (err error) {
349 if e := authContent.wireFormat.unmarshal(r); e != nil {
350 return e
351 }
352 if e := authContent.content.unmarshal(r); e != nil {
353 return e
354 }
355 return authContent.auth.unmarshal(r, authContent.content.contentType)
356 }
357
358 func (authContent *authenticatedContent) marshal(w *Writer) {
359 authContent.wireFormat.marshal(w)
360 authContent.content.marshal(w)
361 authContent.auth.marshal(w, authContent.content.contentType)
362 }
363
364 func (authContent *authenticatedContent) confirmedTranscriptHashInput() (p *confirmedTranscriptHashInput) {
365 return &confirmedTranscriptHashInput{
366 wireFormat: authContent.wireFormat,
367 content: authContent.content,
368 signature: authContent.auth.signature,
369 }
370 }
371
372 func (authContent *authenticatedContent) framedContentTBS(ctx *groupContext) (p *framedContentTBS) {
373 return &framedContentTBS{
374 version: protocolVersionMLS10,
375 wireFormat: authContent.wireFormat,
376 content: authContent.content,
377 context: ctx,
378 }
379 }
380
381 // --- FramedContentTBS (to-be-signed) ---
382
383 type framedContentTBS struct {
384 version protocolVersion
385 wireFormat wireFormat
386 content framedContent
387 context *groupContext // for senderTypeMember and senderTypeNewMemberCommit
388 }
389
390 func (content *framedContentTBS) marshal(w *Writer) {
391 w.addUint16(uint16(content.version))
392 content.wireFormat.marshal(w)
393 content.content.marshal(w)
394 switch content.content.sender.senderType {
395 case senderTypeMember, senderTypeNewMemberCommit:
396 content.context.marshal(w)
397 }
398 }
399
400 // --- PublicMessage ---
401
402 type publicMessage struct {
403 content framedContent
404 auth framedContentAuthData
405 membershipTag []byte // for senderTypeMember
406 }
407
408 func (msg *publicMessage) unmarshal(r *Reader) (err error) {
409 *msg = publicMessage{}
410
411 if e := msg.content.unmarshal(r); e != nil {
412 return e
413 }
414 if e := msg.auth.unmarshal(r, msg.content.contentType); e != nil {
415 return e
416 }
417
418 if msg.content.sender.senderType == senderTypeMember {
419 var ok bool
420 msg.membershipTag, ok = r.readOpaqueVec()
421 if !ok {
422 return errUnexpectedEOF
423 }
424 }
425 return nil
426 }
427
428 func (msg *publicMessage) marshal(w *Writer) {
429 msg.content.marshal(w)
430 msg.auth.marshal(w, msg.content.contentType)
431 if msg.content.sender.senderType == senderTypeMember {
432 w.writeOpaqueVec(msg.membershipTag)
433 }
434 }
435
436 func (msg *publicMessage) authenticatedContent() (p *authenticatedContent) {
437 return &authenticatedContent{
438 wireFormat: wireFormatMLSPublicMessage,
439 content: msg.content,
440 auth: msg.auth,
441 }
442 }
443
444 func (msg *publicMessage) authenticatedContentTBM(ctx *groupContext) (p *authenticatedContentTBM) {
445 return &authenticatedContentTBM{
446 contentTBS: *msg.authenticatedContent().framedContentTBS(ctx),
447 auth: msg.auth,
448 }
449 }
450
451 // --- AuthenticatedContentTBM (to-be-MACed) ---
452
453 type authenticatedContentTBM struct {
454 contentTBS framedContentTBS
455 auth framedContentAuthData
456 }
457
458 func (tbm *authenticatedContentTBM) marshal(w *Writer) {
459 tbm.contentTBS.marshal(w)
460 tbm.auth.marshal(w, tbm.contentTBS.content.contentType)
461 }
462
463 // --- PrivateMessage ---
464
465 type privateMessage struct {
466 groupID []byte
467 epoch uint64
468 contentType contentType
469 authenticatedData []byte
470 encryptedSenderData []byte
471 ciphertext []byte
472 }
473
474 func (msg *privateMessage) unmarshal(r *Reader) (err error) {
475 *msg = privateMessage{}
476
477 var ok bool
478 msg.groupID, ok = r.readOpaqueVec()
479 if !ok {
480 return errUnexpectedEOF
481 }
482 msg.epoch, ok = r.readUint64()
483 if !ok {
484 return errUnexpectedEOF
485 }
486 if e := msg.contentType.unmarshal(r); e != nil {
487 return e
488 }
489 msg.authenticatedData, ok = r.readOpaqueVec()
490 if !ok {
491 return errUnexpectedEOF
492 }
493 msg.encryptedSenderData, ok = r.readOpaqueVec()
494 if !ok {
495 return errUnexpectedEOF
496 }
497 msg.ciphertext, ok = r.readOpaqueVec()
498 if !ok {
499 return errUnexpectedEOF
500 }
501 return nil
502 }
503
504 func (msg *privateMessage) marshal(w *Writer) {
505 w.writeOpaqueVec([]byte(msg.groupID))
506 w.addUint64(msg.epoch)
507 msg.contentType.marshal(w)
508 w.writeOpaqueVec(msg.authenticatedData)
509 w.writeOpaqueVec(msg.encryptedSenderData)
510 w.writeOpaqueVec(msg.ciphertext)
511 }
512
513 func (msg *privateMessage) authenticatedContent(sd *senderData, content *privateMessageContent) (p *authenticatedContent) {
514 return content.authenticatedContent(&framedContent{
515 groupID: msg.groupID,
516 epoch: msg.epoch,
517 sender: sender{
518 senderType: senderTypeMember,
519 leafIndex: sd.leafIndex,
520 },
521 authenticatedData: msg.authenticatedData,
522 contentType: msg.contentType,
523 applicationData: content.applicationData,
524 proposal: content.proposal,
525 commit: content.commit,
526 })
527 }
528
529 // --- Sender data AAD ---
530
531 type senderDataAAD struct {
532 groupID []byte
533 epoch uint64
534 contentType contentType
535 }
536
537 func (aad *senderDataAAD) marshal(w *Writer) {
538 w.writeOpaqueVec([]byte(aad.groupID))
539 w.addUint64(aad.epoch)
540 aad.contentType.marshal(w)
541 }
542
543 // --- Private content AAD ---
544
545 type privateContentAAD struct {
546 groupID []byte
547 epoch uint64
548 contentType contentType
549 authenticatedData []byte
550 }
551
552 func (aad *privateContentAAD) marshal(w *Writer) {
553 w.writeOpaqueVec([]byte(aad.groupID))
554 w.addUint64(aad.epoch)
555 aad.contentType.marshal(w)
556 w.writeOpaqueVec(aad.authenticatedData)
557 }
558
559 // --- PrivateMessageContent ---
560
561 type privateMessageContent struct {
562 applicationData []byte // for contentTypeApplication
563 proposal *proposal // for contentTypeProposal
564 commit *commit // for contentTypeCommit
565
566 auth framedContentAuthData
567 }
568
569 func (content *privateMessageContent) unmarshal(r *Reader, ct contentType) (err error) {
570 *content = privateMessageContent{}
571
572 var err error
573 switch ct {
574 case contentTypeApplication:
575 var ok bool
576 content.applicationData, ok = r.readOpaqueVec()
577 if !ok {
578 err = errUnexpectedEOF
579 }
580 case contentTypeProposal:
581 content.proposal = &proposal{}
582 err = content.proposal.unmarshal(r)
583 case contentTypeCommit:
584 content.commit = &commit{}
585 err = content.commit.unmarshal(r)
586 default:
587 panic("unreachable")
588 }
589 if err != nil {
590 return err
591 }
592 return content.auth.unmarshal(r, ct)
593 }
594
595 func (content *privateMessageContent) marshal(w *Writer, ct contentType) {
596 switch ct {
597 case contentTypeApplication:
598 w.writeOpaqueVec(content.applicationData)
599 case contentTypeProposal:
600 content.proposal.marshal(w)
601 case contentTypeCommit:
602 content.commit.marshal(w)
603 default:
604 panic("unreachable")
605 }
606 content.auth.marshal(w, ct)
607 }
608
609 func (content *privateMessageContent) authenticatedContent(fc *framedContent) (p *authenticatedContent) {
610 return &authenticatedContent{
611 wireFormat: wireFormatMLSPrivateMessage,
612 content: *fc,
613 auth: content.auth,
614 }
615 }
616
617 // --- SenderData ---
618
619 type senderData struct {
620 leafIndex leafIndex
621 generation uint32
622 reuseGuard [4]byte
623 }
624
625 func (data *senderData) unmarshal(r *Reader) (err error) {
626 v, ok := r.readUint32()
627 if !ok {
628 return errUnexpectedEOF
629 }
630 data.leafIndex = leafIndex(v)
631 data.generation, ok = r.readUint32()
632 if !ok {
633 return errUnexpectedEOF
634 }
635 guard, ok := r.readN(4)
636 if !ok {
637 return errUnexpectedEOF
638 }
639 data.reuseGuard[0] = guard[0]
640 data.reuseGuard[1] = guard[1]
641 data.reuseGuard[2] = guard[2]
642 data.reuseGuard[3] = guard[3]
643 return nil
644 }
645
646 func (data *senderData) marshal(w *Writer) {
647 w.addUint32(uint32(data.leafIndex))
648 w.addUint32(data.generation)
649 w.addBytes(data.reuseGuard[:])
650 }
651