melos 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85db7b48fd128cb7bb205689c66ae391964b16228d21d3cd7b1e2f941d332a31
4
- data.tar.gz: d324b915daf2f12f9226f690e9e2a577ab9a34e7235d9088e181e46ffb7d563c
3
+ metadata.gz: 2e29fdc55c828d8bdd2bfd2c2375835dd9e3bf04020bf04291915cd0de2af83b
4
+ data.tar.gz: 467c07c1eaff33a00fb9261a579e52a6fed0fd0d15c9cc1f83b15c99b7a98aa4
5
5
  SHA512:
6
- metadata.gz: b0c1e2b24240f32c119d10c09f16ccebe2c3442ec6422df9d85d8669dba4b058d331bc22d8eff36aafda228bd86c0d9d75a8f83d4fce8e2ad9727537ee54593d
7
- data.tar.gz: 3fe93f19e872cb70e313714008065026e41ddc0374029a5d0f50b9f843d24f4b394cae6974205098fbfdde36cbdcd979ddd8c4bf73ce721181b1c602877e1a22
6
+ metadata.gz: 9ace207ff77ecfa0e220fcc94366e2e07cc41b68424bc6840254c4f5c8c3c8ec1d2e1c94bebde387454a2f12c0054ec00031e41bd77b015c0f5a74c085f4ca46
7
+ data.tar.gz: 3741b2507119fa79de960ab719f37e447b15521b6387a144a7a70ebeeb5e4a57e604e226847b159854e56a796fa2f3468f548c16d64932fcdd6468ef5b4731b3
data/README.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Melos
2
2
 
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/melos.svg)](https://badge.fury.io/rb/melos)
5
+
3
6
  a [Messaging Layer Security Protocol](https://www.rfc-editor.org/rfc/rfc9420.html) implementation in Ruby
4
7
 
5
8
  (yes, [an mls gem](https://rubygems.org/gems/mls) happened to exist since 2014, so...)
9
+
10
+ ## Note on implementation status
11
+
12
+ As of version 0.0.1, this implements:
13
+
14
+ - Serialization/deserialization of messages
15
+ - Key Schedule
16
+ - Encryption Secret Tree
17
+ - Applying Add/Update/Remove/PreSharedKey/GroupContextExtensions Proposal types
18
+ - (thus passes all test vectors in [mls-implementations/test-vectors.md](https://github.com/mlswg/mls-implementations/blob/main/test-vectors.md))
19
+
20
+ but lacks the following (not a complete list):
21
+
22
+ - Creating messages
23
+ - Applying ReInit/ExternalInit proposals
24
+ - Validation (rejecting error cases)
@@ -1,19 +1,13 @@
1
1
  module Melos::Struct; end
2
2
 
3
3
  class Melos::Struct::Base
4
- def initialize(buf)
5
- context, _ = deserialize(buf)
4
+ def initialize(stream)
5
+ stream = StringIO.new(stream) if stream.is_a?(String)
6
+ context = deserialize(stream)
6
7
  set_instance_vars(context)
7
8
  self
8
9
  end
9
10
 
10
- def self.new_and_rest(buf)
11
- instance = self.allocate
12
- context, buf = instance.send(:deserialize, buf)
13
- instance.send(:set_instance_vars, context)
14
- [instance, buf]
15
- end
16
-
17
11
  def raw
18
12
  buf = ''
19
13
  self.class::STRUCT.each do |elem|
@@ -29,45 +23,35 @@ class Melos::Struct::Base
29
23
  buf
30
24
  end
31
25
 
32
- def self.vecs(buf)
33
- value, buf = Melos::Vec.parse_vec(buf)
34
- array = []
35
- while (value.bytesize > 0)
36
- current_instance, value = Melos::Vec.parse_vec(value)
37
- array << current_instance
38
- end
39
- [array, buf]
40
- end
41
-
42
26
  # context here takes a hash
43
- # returns [value, rest_of_buffer]
27
+ # returns the deserialized value
44
28
  # value could return nil, which means predicate was not applicable
45
29
  # predicate takes the context and returns true or false
46
- def deserialize_select_elem_with_context(buf, context, predicate, type, type_param)
30
+ def deserialize_select_elem_with_context(stream, context, predicate, type, type_param)
47
31
  if predicate.(context)
48
- deserialize_elem(buf, type, type_param)
32
+ deserialize_elem(stream, type, type_param)
49
33
  else
50
- [nil, buf]
34
+ nil
51
35
  end
52
36
  end
53
37
 
54
38
  private
55
- def deserialize(buf)
39
+ def deserialize(stream)
56
40
  context = []
57
41
  self.class::STRUCT.each do |elem|
58
42
  case elem[1]
59
43
  when :select
60
- value, buf = deserialize_select_elem_with_context(buf, context.to_h, elem[2], elem[3], elem[4])
44
+ value = deserialize_select_elem_with_context(stream, context.to_h, elem[2], elem[3], elem[4])
61
45
  context << [elem[0], value]
62
46
  when :framed_content_auth_data
63
- value, buf = Melos::Struct::FramedContentAuthData.new_and_rest_with_content_type(buf, context.to_h[:content].content_type)
47
+ value = Melos::Struct::FramedContentAuthData.new_with_content_type(stream, context.to_h[:content].content_type)
64
48
  context << [elem[0], value]
65
49
  else
66
- value, buf = deserialize_elem(buf, elem[1], elem[2])
50
+ value = deserialize_elem(stream, elem[1], elem[2])
67
51
  context << [elem[0], value]
68
52
  end
69
53
  end
70
- [context, buf]
54
+ context
71
55
  end
72
56
 
73
57
  def set_instance_vars(context)
@@ -76,57 +60,51 @@ class Melos::Struct::Base
76
60
  end
77
61
  end
78
62
 
79
- def deserialize_elem(buf, type, type_param)
63
+ def deserialize_elem(stream, type, type_param)
80
64
  case type
81
65
  when :uint8
82
- value = buf.byteslice(0, 1).unpack1('C')
83
- buf = buf.byteslice(1..)
66
+ value = stream.read(1).unpack1('C')
84
67
  when :uint16
85
- value = buf.byteslice(0, 2).unpack1('S>')
86
- buf = buf.byteslice(2..)
68
+ value = stream.read(2).unpack1('S>')
87
69
  when :uint32
88
- value = buf.byteslice(0, 4).unpack1('L>')
89
- buf = buf.byteslice(4..)
70
+ value = stream.read(4).unpack1('L>')
90
71
  when :uint64
91
- value = buf.byteslice(0, 8).unpack1('Q>')
92
- buf = buf.byteslice(8..)
72
+ value = stream.read(8).unpack1('Q>')
93
73
  when :vec
94
- value, buf = Melos::Vec.parse_vec(buf)
74
+ value = Melos::Vec.parse_stringio(stream)
95
75
  when :vec_of_type
96
- vec, buf = Melos::Vec.parse_vec(buf)
76
+ data = Melos::Vec.parse_stringio(stream)
97
77
  value = []
98
- while (vec.bytesize > 0)
99
- current_instance, vec = deserialize_elem(vec, type_param, nil)
78
+ data_stream = StringIO.new(data)
79
+ while (!data_stream.eof?)
80
+ current_instance = deserialize_elem(data_stream, type_param, nil)
100
81
  value << current_instance
101
82
  end
102
83
  when :class
103
- value, buf = type_param.send(:new_and_rest, buf)
84
+ value = type_param.send(:new, stream)
104
85
  when :classes
105
- # prefix, length = buf.get_prefix_and_length
106
- # puts "#{prefix}, #{length}"
107
- vec, buf = Melos::Vec.parse_vec(buf)
86
+ data = Melos::Vec.parse_stringio(stream)
108
87
  value = []
109
- while (vec.bytesize > 0)
110
- current_instance, vec = type_param.send(:new_and_rest, vec)
88
+ data_stream = StringIO.new(data)
89
+ while (!data_stream.eof?)
90
+ current_instance = type_param.send(:new, data_stream)
111
91
  value << current_instance
112
92
  end
113
93
  when :optional
114
- presence = buf.byteslice(0, 1).unpack1('C')
115
- buf = buf.byteslice(1..)
94
+ presence = stream.read(1).unpack1('C')
116
95
  case presence
117
96
  when 0
118
97
  value = nil
119
98
  when 1
120
99
  # as of RFC 9420, optional always takes a class
121
- value, buf = type_param.send(:new_and_rest, buf)
100
+ value = type_param.send(:new, stream)
122
101
  end
123
102
  when :opaque
124
- value = buf.byteslice(0, type_param.to_i)
125
- buf = buf.byteslice((type_param.to_i)..)
103
+ value = stream.read(type_param.to_i)
126
104
  when :padding
127
- value = buf
105
+ value = stream.read
128
106
  end
129
- [value, buf]
107
+ value
130
108
  end
131
109
 
132
110
  # take a name and type
@@ -6,26 +6,26 @@ require_relative '../tree'
6
6
  require_relative '../crypto'
7
7
 
8
8
  module Melos::Struct::RatchetTree
9
- def self.parse(vec)
10
- array, _ = new_and_rest(vec)
11
- array
9
+ def self.parse(stream)
10
+ stream = StringIO.new(stream) if stream.is_a?(String)
11
+ new(stream)
12
12
  end
13
13
 
14
- def self.new_and_rest(vec)
14
+ def self.new(stream)
15
15
  array = []
16
- buf, rest = Melos::Vec.parse_vec(vec)
17
- while buf.bytesize > 0
18
- presence = buf.byteslice(0, 1).unpack1('C')
19
- buf = buf.byteslice(1..)
16
+ data = Melos::Vec.parse_stringio(stream)
17
+ data_stream = StringIO.new(data)
18
+ while !data_stream.eof?
19
+ presence = data_stream.read(1).unpack1('C')
20
20
  case presence
21
21
  when 0
22
22
  array << nil
23
23
  when 1
24
- node, buf = Melos::Struct::Node.new_and_rest(buf)
24
+ node = Melos::Struct::Node.new(data_stream)
25
25
  array << node
26
26
  end
27
27
  end
28
- [array, rest]
28
+ array
29
29
  end
30
30
 
31
31
  def self.raw(array)
@@ -638,18 +638,18 @@ class Melos::Struct::FramedContentAuthData < Melos::Struct::Base
638
638
  ]
639
639
 
640
640
  # initialize from stream
641
- def self.new_and_rest_with_content_type(buf, content_type)
641
+ def self.new_with_content_type(stream, content_type)
642
642
  instance = self.allocate
643
- context, buf = instance.send(:deserialize, buf)
643
+ context = instance.send(:deserialize, stream)
644
644
  # custom part based on instance variable
645
645
  if content_type == Melos::Constants::ContentType::COMMIT # commit
646
646
  # read MAC(opaque <V>) confirmation_tag
647
- value, buf = Melos::Vec.parse_vec(buf)
647
+ value = Melos::Vec.parse_stringio(stream)
648
648
  context << [:confirmation_tag, value]
649
649
  end
650
650
  context << [:content_type, content_type]
651
651
  instance.send(:set_instance_vars, context)
652
- [instance, buf]
652
+ instance
653
653
  end
654
654
 
655
655
  def raw
@@ -887,7 +887,8 @@ class Melos::Struct::PrivateMessage < Melos::Struct::Base
887
887
  sender_data = decrypt_sender_data(suite, sender_data_secret)
888
888
  key, nonce, _ = Melos::SecretTree.ratchet_until_and_get(suite, content_type, secret_tree, sender_data.leaf_index, sender_data.generation)
889
889
  new_nonce = self.class.apply_nonce_reuse_guard(nonce, sender_data.reuse_guard)
890
- pmc, _ = Melos::Struct::PrivateMessageContent.new_and_rest_with_content_type(Melos::Crypto.aead_decrypt(suite, key, new_nonce, private_content_aad, ciphertext), content_type)
890
+ stream = StringIO.new(Melos::Crypto.aead_decrypt(suite, key, new_nonce, private_content_aad, ciphertext))
891
+ pmc = Melos::Struct::PrivateMessageContent.new_with_content_type(stream, content_type)
891
892
 
892
893
  fc = Melos::Struct::FramedContent.create(
893
894
  group_id: group_id,
@@ -945,27 +946,27 @@ class Melos::Struct::PrivateMessageContent < Melos::Struct::Base
945
946
  # bytes -> struct: decode the content and auth field, rest is padding
946
947
  # struct -> bytes: encode content and auth field, add set amount of padding (zero bytes)
947
948
 
948
- def self.new_and_rest_with_content_type(buf, content_type)
949
+ def self.new_with_content_type(stream, content_type)
949
950
  instance = self.allocate
950
951
  context = []
951
952
  # deserialize application_data/proposal/commit
952
953
  case content_type
953
954
  when Melos::Constants::ContentType::APPLICATION
954
- value, buf = Melos::Vec.parse_vec(buf)
955
+ value = Melos::Vec.parse_stringio(stream)
955
956
  context << [:application_data, value]
956
957
  when Melos::Constants::ContentType::PROPOSAL
957
- value, buf = Melos::Struct::Proposal.new_and_rest(buf)
958
+ value = Melos::Struct::Proposal.new(stream)
958
959
  context << [:proposal, value]
959
960
  when Melos::Constants::ContentType::COMMIT
960
- value, buf = Melos::Struct::Commit.new_and_rest(buf)
961
+ value = Melos::Struct::Commit.new(stream)
961
962
  context << [:commit, value]
962
963
  end
963
- fcad, buf = Melos::Struct::FramedContentAuthData.new_and_rest_with_content_type(buf, content_type)
964
+ fcad = Melos::Struct::FramedContentAuthData.new_with_content_type(stream, content_type)
964
965
  context << [:auth, fcad]
965
966
  # assume rest is padding
966
- context << [:padding, buf]
967
+ context << [:padding, stream.read()]
967
968
  instance.send(:set_instance_vars, context)
968
- [instance, '']
969
+ instance
969
970
  end
970
971
 
971
972
  def content
data/lib/melos/vec.rb CHANGED
@@ -40,13 +40,6 @@ module Melos::Vec
40
40
  write_varint(str.bytesize) + str
41
41
  end
42
42
 
43
- def get_prefix_and_length(str)
44
- prefix = str[0].ord >> 6
45
- length = read_varint(str)
46
-
47
- [prefix, length]
48
- end
49
-
50
43
  def parse_vec(vec_as_string)
51
44
  prefix = vec_as_string[0].ord >> 6
52
45
  length = read_varint(vec_as_string)
@@ -67,23 +60,33 @@ module Melos::Vec
67
60
  [str, rest]
68
61
  end
69
62
 
70
- def get_first_vec(vec_as_string)
71
- prefix = vec[0].ord >> 6
72
- length = read_varint(vec_as_string)
63
+ def parse_stringio(vec_as_stringio)
64
+ prefix = prefix_from_stringio(vec_as_stringio)
65
+ length = length_from_stringio(vec_as_stringio)
73
66
  case prefix
74
- when 0
75
- first_vec = vec_as_string[0, 1 + length]
76
- rest = vec_as_string[(1 + length)..]
77
- when 1
78
- first_vec = vec_as_string[0, 2 + length]
79
- rest = vec_as_string[(2 + length)..]
80
- when 2
81
- first_vec = vec_as_string[0, 4 + length]
82
- rest = vec_as_string[(4 + length)..]
67
+ when 0..2
68
+ vec_as_stringio.pos = (vec_as_stringio.pos + (2 ** prefix))
69
+ str = vec_as_stringio.read(length)
83
70
  else
84
71
  raise ArgumentError.new('invalid header')
85
72
  end
86
73
 
87
- [first_vec, rest]
74
+ str
75
+ end
76
+
77
+ private
78
+
79
+ def prefix_from_stringio(stringio)
80
+ pos = stringio.pos
81
+ prefix_ = stringio.getbyte >> 6
82
+ stringio.seek(pos)
83
+ prefix_
84
+ end
85
+
86
+ def length_from_stringio(stringio)
87
+ pos = stringio.pos
88
+ buf = stringio.read(4)
89
+ stringio.seek(pos)
90
+ read_varint(buf)
88
91
  end
89
92
  end
data/lib/melos/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Melos
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: melos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Kajiwara
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: hpke
@@ -50,7 +50,6 @@ executables: []
50
50
  extensions: []
51
51
  extra_rdoc_files: []
52
52
  files:
53
- - ".rspec"
54
53
  - LICENSE.txt
55
54
  - README.md
56
55
  - Rakefile
@@ -104,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
103
  - !ruby/object:Gem::Version
105
104
  version: '0'
106
105
  requirements: []
107
- rubygems_version: 3.6.5
106
+ rubygems_version: 3.7.1
108
107
  specification_version: 4
109
108
  summary: Messaging Layer Security Protocol (RFC 9420) on Ruby
110
109
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper