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