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