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 +4 -4
- data/README.md +19 -0
- data/lib/melos/struct/base.rb +32 -54
- data/lib/melos/struct/ratchet_tree.rb +10 -10
- data/lib/melos/struct/structs.rb +13 -12
- data/lib/melos/vec.rb +23 -20
- data/lib/melos/version.rb +1 -1
- metadata +3 -4
- data/.rspec +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e29fdc55c828d8bdd2bfd2c2375835dd9e3bf04020bf04291915cd0de2af83b
|
4
|
+
data.tar.gz: 467c07c1eaff33a00fb9261a579e52a6fed0fd0d15c9cc1f83b15c99b7a98aa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ace207ff77ecfa0e220fcc94366e2e07cc41b68424bc6840254c4f5c8c3c8ec1d2e1c94bebde387454a2f12c0054ec00031e41bd77b015c0f5a74c085f4ca46
|
7
|
+
data.tar.gz: 3741b2507119fa79de960ab719f37e447b15521b6387a144a7a70ebeeb5e4a57e604e226847b159854e56a796fa2f3468f548c16d64932fcdd6468ef5b4731b3
|
data/README.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Melos
|
2
2
|
|
3
|
+
|
4
|
+
[](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)
|
data/lib/melos/struct/base.rb
CHANGED
@@ -1,19 +1,13 @@
|
|
1
1
|
module Melos::Struct; end
|
2
2
|
|
3
3
|
class Melos::Struct::Base
|
4
|
-
def initialize(
|
5
|
-
|
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
|
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(
|
30
|
+
def deserialize_select_elem_with_context(stream, context, predicate, type, type_param)
|
47
31
|
if predicate.(context)
|
48
|
-
deserialize_elem(
|
32
|
+
deserialize_elem(stream, type, type_param)
|
49
33
|
else
|
50
|
-
|
34
|
+
nil
|
51
35
|
end
|
52
36
|
end
|
53
37
|
|
54
38
|
private
|
55
|
-
def deserialize(
|
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
|
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
|
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
|
50
|
+
value = deserialize_elem(stream, elem[1], elem[2])
|
67
51
|
context << [elem[0], value]
|
68
52
|
end
|
69
53
|
end
|
70
|
-
|
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(
|
63
|
+
def deserialize_elem(stream, type, type_param)
|
80
64
|
case type
|
81
65
|
when :uint8
|
82
|
-
value =
|
83
|
-
buf = buf.byteslice(1..)
|
66
|
+
value = stream.read(1).unpack1('C')
|
84
67
|
when :uint16
|
85
|
-
value =
|
86
|
-
buf = buf.byteslice(2..)
|
68
|
+
value = stream.read(2).unpack1('S>')
|
87
69
|
when :uint32
|
88
|
-
value =
|
89
|
-
buf = buf.byteslice(4..)
|
70
|
+
value = stream.read(4).unpack1('L>')
|
90
71
|
when :uint64
|
91
|
-
value =
|
92
|
-
buf = buf.byteslice(8..)
|
72
|
+
value = stream.read(8).unpack1('Q>')
|
93
73
|
when :vec
|
94
|
-
value
|
74
|
+
value = Melos::Vec.parse_stringio(stream)
|
95
75
|
when :vec_of_type
|
96
|
-
|
76
|
+
data = Melos::Vec.parse_stringio(stream)
|
97
77
|
value = []
|
98
|
-
|
99
|
-
|
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
|
84
|
+
value = type_param.send(:new, stream)
|
104
85
|
when :classes
|
105
|
-
|
106
|
-
# puts "#{prefix}, #{length}"
|
107
|
-
vec, buf = Melos::Vec.parse_vec(buf)
|
86
|
+
data = Melos::Vec.parse_stringio(stream)
|
108
87
|
value = []
|
109
|
-
|
110
|
-
|
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 =
|
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
|
100
|
+
value = type_param.send(:new, stream)
|
122
101
|
end
|
123
102
|
when :opaque
|
124
|
-
value =
|
125
|
-
buf = buf.byteslice((type_param.to_i)..)
|
103
|
+
value = stream.read(type_param.to_i)
|
126
104
|
when :padding
|
127
|
-
value =
|
105
|
+
value = stream.read
|
128
106
|
end
|
129
|
-
|
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(
|
10
|
-
|
11
|
-
|
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.
|
14
|
+
def self.new(stream)
|
15
15
|
array = []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
24
|
+
node = Melos::Struct::Node.new(data_stream)
|
25
25
|
array << node
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
28
|
+
array
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.raw(array)
|
data/lib/melos/struct/structs.rb
CHANGED
@@ -638,18 +638,18 @@ class Melos::Struct::FramedContentAuthData < Melos::Struct::Base
|
|
638
638
|
]
|
639
639
|
|
640
640
|
# initialize from stream
|
641
|
-
def self.
|
641
|
+
def self.new_with_content_type(stream, content_type)
|
642
642
|
instance = self.allocate
|
643
|
-
context
|
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
|
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
|
-
|
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
|
-
|
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.
|
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
|
955
|
+
value = Melos::Vec.parse_stringio(stream)
|
955
956
|
context << [:application_data, value]
|
956
957
|
when Melos::Constants::ContentType::PROPOSAL
|
957
|
-
value
|
958
|
+
value = Melos::Struct::Proposal.new(stream)
|
958
959
|
context << [:proposal, value]
|
959
960
|
when Melos::Constants::ContentType::COMMIT
|
960
|
-
value
|
961
|
+
value = Melos::Struct::Commit.new(stream)
|
961
962
|
context << [:commit, value]
|
962
963
|
end
|
963
|
-
fcad
|
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,
|
967
|
+
context << [:padding, stream.read()]
|
967
968
|
instance.send(:set_instance_vars, context)
|
968
|
-
|
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
|
71
|
-
prefix =
|
72
|
-
length =
|
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
|
-
|
76
|
-
|
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
|
-
|
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
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.
|
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:
|
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.
|
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