melos 0.0.1
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 +7 -0
- data/.rspec +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +5 -0
- data/Rakefile +10 -0
- data/lib/melos/constants.rb +84 -0
- data/lib/melos/crypto.rb +307 -0
- data/lib/melos/key_schedule.rb +62 -0
- data/lib/melos/psk.rb +35 -0
- data/lib/melos/secret_tree.rb +109 -0
- data/lib/melos/struct/base.rb +172 -0
- data/lib/melos/struct/ratchet_tree.rb +324 -0
- data/lib/melos/struct/structs.rb +1019 -0
- data/lib/melos/tree.rb +265 -0
- data/lib/melos/util.rb +11 -0
- data/lib/melos/vec.rb +89 -0
- data/lib/melos/version.rb +5 -0
- data/lib/melos.rb +15 -0
- data/sig/melos.rbs +4 -0
- data/test_vectors/crypto-basics.json +303 -0
- data/test_vectors/deserialization.json +58 -0
- data/test_vectors/key-schedule.json +926 -0
- data/test_vectors/message-protection.json +142 -0
- data/test_vectors/messages.json +5702 -0
- data/test_vectors/passive-client-handling-commit.json +2683 -0
- data/test_vectors/passive-client-random.json +2657 -0
- data/test_vectors/passive-client-welcome.json +814 -0
- data/test_vectors/psk_secret.json +2382 -0
- data/test_vectors/secret-tree.json +4846 -0
- data/test_vectors/transcript-hashes.json +58 -0
- data/test_vectors/tree-math.json +8276 -0
- data/test_vectors/tree-operations.json +47 -0
- data/test_vectors/tree-validation.json +11839 -0
- data/test_vectors/treekem.json +14877 -0
- data/test_vectors/welcome.json +51 -0
- metadata +110 -0
@@ -0,0 +1,1019 @@
|
|
1
|
+
require_relative '../vec'
|
2
|
+
require_relative 'base'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
class Melos::Struct::EncryptContext < Melos::Struct::Base
|
6
|
+
attr_reader :label, :context
|
7
|
+
STRUCT = [
|
8
|
+
[:label, :vec],
|
9
|
+
[:context, :vec]
|
10
|
+
]
|
11
|
+
end
|
12
|
+
|
13
|
+
class Melos::Struct::RefHashInput < Melos::Struct::Base
|
14
|
+
attr_reader :label, :value
|
15
|
+
STRUCT = [
|
16
|
+
[:label, :vec],
|
17
|
+
[:value, :vec]
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Section 5.3 Credentials
|
22
|
+
class Melos::Struct::Certificate < Melos::Struct::Base
|
23
|
+
attr_reader :cert_data
|
24
|
+
STRUCT = [
|
25
|
+
[:cert_data, :vec]
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
class Melos::Struct::Credential < Melos::Struct::Base
|
30
|
+
attr_reader :credential_type, :identity, :certificates
|
31
|
+
STRUCT = [
|
32
|
+
[:credential_type, :uint16],
|
33
|
+
[:identity, :select, ->(ctx){ctx[:credential_type] == Melos::Constants::CredentialType::BASIC}, :vec],
|
34
|
+
[:certificates, :select, ->(ctx){ctx[:credential_type] == Melos::Constants::CredentialType::X509}, :classes, Melos::Struct::Certificate]
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
## 6.3.2
|
39
|
+
|
40
|
+
class Melos::Struct::SenderData < Melos::Struct::Base
|
41
|
+
attr_reader :leaf_index, :generation, :reuse_guard
|
42
|
+
STRUCT = [
|
43
|
+
[:leaf_index, :uint32],
|
44
|
+
[:generation, :uint32],
|
45
|
+
[:reuse_guard, :opaque, 4]
|
46
|
+
]
|
47
|
+
|
48
|
+
def self.create(leaf_index:, generation:, reuse_guard:)
|
49
|
+
new_instance = self.allocate
|
50
|
+
new_instance.instance_variable_set(:@leaf_index, leaf_index)
|
51
|
+
new_instance.instance_variable_set(:@generation, generation)
|
52
|
+
new_instance.instance_variable_set(:@reuse_guard, reuse_guard)
|
53
|
+
new_instance
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
## 7.1
|
58
|
+
|
59
|
+
class Melos::Struct::ParentNode < Melos::Struct::Base
|
60
|
+
attr_reader :encryption_key, :parent_hash, :unmerged_leaves
|
61
|
+
STRUCT = [
|
62
|
+
[:encryption_key, :vec], # HPKEPublicKey = opaque <V>
|
63
|
+
[:parent_hash, :vec],
|
64
|
+
[:unmerged_leaves, :vec_of_type, :uint32] # becomes a vec of uint32
|
65
|
+
]
|
66
|
+
|
67
|
+
def self.create(encryption_key:, parent_hash:, unmerged_leaves:)
|
68
|
+
new_instance = self.allocate
|
69
|
+
new_instance.instance_variable_set(:@encryption_key, encryption_key)
|
70
|
+
new_instance.instance_variable_set(:@parent_hash, parent_hash)
|
71
|
+
new_instance.instance_variable_set(:@unmerged_leaves, unmerged_leaves)
|
72
|
+
new_instance
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
## 7.2
|
77
|
+
|
78
|
+
class Melos::Struct::Capabilities < Melos::Struct::Base
|
79
|
+
attr_reader :versions, :cipher_suites, :extensions, :proposals, :credentials
|
80
|
+
STRUCT = [
|
81
|
+
[:versions, :vec], # vec of ProtocolVersion (uint16)
|
82
|
+
[:cipher_suites, :vec], # vec of CipherSuite (uint16)
|
83
|
+
[:extensions, :vec], # vec of ExtensionType (uint16)
|
84
|
+
[:proposals, :vec], # vec of ProposalTypes (uint16)
|
85
|
+
[:credentials, :vec] # vec of CredentialTypes (uint16)
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
class Melos::Struct::Lifetime < Melos::Struct::Base
|
90
|
+
attr_reader :not_before, :not_after
|
91
|
+
STRUCT = [
|
92
|
+
[:not_before, :uint64],
|
93
|
+
[:not_after, :uint64]
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
class Melos::Struct::Extension < Melos::Struct::Base
|
98
|
+
attr_reader :extension_type, :extension_data
|
99
|
+
STRUCT = [
|
100
|
+
[:extension_type, :uint16], # ExtensionType = uint16
|
101
|
+
[:extension_data, :vec]
|
102
|
+
]
|
103
|
+
end
|
104
|
+
|
105
|
+
class Melos::Struct::LeafNode < Melos::Struct::Base
|
106
|
+
attr_reader :encryption_key, :signature_key, :credential, :capabilities, :leaf_node_source, :lifetime, :parent_hash, :extensions, :signature
|
107
|
+
STRUCT = [
|
108
|
+
[:encryption_key, :vec], # HPKEPublicKey = opaque <V>
|
109
|
+
[:signature_key, :vec], # SignaturePublicKey = opaque <V>
|
110
|
+
[:credential, :class, Melos::Struct::Credential],
|
111
|
+
[:capabilities, :class, Melos::Struct::Capabilities],
|
112
|
+
[:leaf_node_source, :uint8], # LeafNodeSource = enum of uint8,
|
113
|
+
[:lifetime, :select, ->(ctx){ctx[:leaf_node_source] == Melos::Constants::LeafNodeSource::KEY_PACKAGE}, :class, Melos::Struct::Lifetime],
|
114
|
+
[:parent_hash, :select, ->(ctx){ctx[:leaf_node_source] == Melos::Constants::LeafNodeSource::COMMIT}, :vec],
|
115
|
+
[:extensions, :classes, Melos::Struct::Extension],
|
116
|
+
[:signature, :vec]
|
117
|
+
]
|
118
|
+
|
119
|
+
def leaf_node_tbs(group_id, leaf_index)
|
120
|
+
buf = ''
|
121
|
+
buf += Melos::Vec.from_string(encryption_key)
|
122
|
+
buf += Melos::Vec.from_string(signature_key)
|
123
|
+
buf += credential.raw
|
124
|
+
buf += capabilities.raw
|
125
|
+
buf += [leaf_node_source].pack('C')
|
126
|
+
if leaf_node_source == Melos::Constants::LeafNodeSource::KEY_PACKAGE
|
127
|
+
buf += lifetime.raw
|
128
|
+
elsif leaf_node_source == Melos::Constants::LeafNodeSource::COMMIT
|
129
|
+
buf += Melos::Vec.from_string(parent_hash)
|
130
|
+
end
|
131
|
+
buf += Melos::Vec.from_string(extensions.map(&:raw).join)
|
132
|
+
if leaf_node_source == Melos::Constants::LeafNodeSource::UPDATE || leaf_node_source == Melos::Constants::LeafNodeSource::COMMIT
|
133
|
+
buf += Melos::Vec.from_string(group_id)
|
134
|
+
buf += [leaf_index].pack('L>') # uint32
|
135
|
+
end
|
136
|
+
buf
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.create(
|
140
|
+
encryption_key:, signature_key:, credential:, capabilities:,
|
141
|
+
leaf_node_source:, lifetime:, parent_hash:, extensions:, signature:
|
142
|
+
)
|
143
|
+
new_instance = self.allocate
|
144
|
+
new_instance.instance_variable_set(:@encryption_key, encryption_key)
|
145
|
+
new_instance.instance_variable_set(:@signature_key, signature_key)
|
146
|
+
new_instance.instance_variable_set(:@credential, credential)
|
147
|
+
new_instance.instance_variable_set(:@capabilities, capabilities)
|
148
|
+
new_instance.instance_variable_set(:@leaf_node_source, leaf_node_source)
|
149
|
+
new_instance.instance_variable_set(:@lifetime, lifetime)
|
150
|
+
new_instance.instance_variable_set(:@parent_hash, parent_hash)
|
151
|
+
new_instance.instance_variable_set(:@extensions, extensions)
|
152
|
+
new_instance.instance_variable_set(:@signature, signature)
|
153
|
+
new_instance
|
154
|
+
end
|
155
|
+
|
156
|
+
def sign(suite, signature_private_key, group_id, leaf_index)
|
157
|
+
@signature = Melos::Crypto.sign_with_label(suite, signature_private_key, "LeafNodeTBS", leaf_node_tbs(group_id, leaf_index))
|
158
|
+
end
|
159
|
+
|
160
|
+
def verify(suite, group_id, leaf_index)
|
161
|
+
Melos::Crypto.verify_with_label(suite, signature_key, "LeafNodeTBS", leaf_node_tbs(group_id, leaf_index), signature)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
## 7.6
|
166
|
+
|
167
|
+
class Melos::Struct::HPKECipherText < Melos::Struct::Base
|
168
|
+
attr_reader :kem_output, :ciphertext
|
169
|
+
STRUCT = [
|
170
|
+
[:kem_output, :vec],
|
171
|
+
[:ciphertext, :vec]
|
172
|
+
]
|
173
|
+
|
174
|
+
def self.create(kem_output:, ciphertext:)
|
175
|
+
new_instance = self.allocate
|
176
|
+
new_instance.instance_variable_set(:@kem_output, kem_output)
|
177
|
+
new_instance.instance_variable_set(:@ciphertext, ciphertext)
|
178
|
+
new_instance
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class Melos::Struct::UpdatePathNode < Melos::Struct::Base
|
183
|
+
attr_reader :encryption_key, :encrypted_path_secret
|
184
|
+
STRUCT = [
|
185
|
+
[:encryption_key, :vec], # HPKEPublicKey = opaque <V>
|
186
|
+
[:encrypted_path_secret, :classes, Melos::Struct::HPKECipherText]
|
187
|
+
]
|
188
|
+
|
189
|
+
def self.create(encryption_key:, encrypted_path_secret:)
|
190
|
+
new_instance = self.allocate
|
191
|
+
new_instance.instance_variable_set(:@encryption_key, encryption_key)
|
192
|
+
new_instance.instance_variable_set(:@encrypted_path_secret, encrypted_path_secret)
|
193
|
+
new_instance
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class Melos::Struct::UpdatePath < Melos::Struct::Base
|
198
|
+
attr_reader :leaf_node, :nodes
|
199
|
+
STRUCT = [
|
200
|
+
[:leaf_node, :class, Melos::Struct::LeafNode],
|
201
|
+
[:nodes, :classes, Melos::Struct::UpdatePathNode]
|
202
|
+
]
|
203
|
+
|
204
|
+
def self.create(leaf_node:, nodes:)
|
205
|
+
new_instance = self.allocate
|
206
|
+
new_instance.instance_variable_set(:@leaf_node, leaf_node)
|
207
|
+
new_instance.instance_variable_set(:@nodes, nodes)
|
208
|
+
new_instance
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
## 7.8
|
213
|
+
|
214
|
+
## NodeType: uint8 enum
|
215
|
+
|
216
|
+
class Melos::Struct::LeafNodeHashInput < Melos::Struct::Base
|
217
|
+
attr_reader :leaf_index, :leaf_node
|
218
|
+
STRUCT = [
|
219
|
+
[:leaf_index, :uint32],
|
220
|
+
[:leaf_node, :optional, Melos::Struct::LeafNode]
|
221
|
+
]
|
222
|
+
end
|
223
|
+
|
224
|
+
class Melos::Struct::ParentNodeHashInput < Melos::Struct::Base
|
225
|
+
attr_reader :parent_node, :left_hash, :right_hash
|
226
|
+
STRUCT = [
|
227
|
+
[:parent_node, :optional, Melos::Struct::ParentNode],
|
228
|
+
[:left_hash, :vec],
|
229
|
+
[:right_hash, :vec]
|
230
|
+
]
|
231
|
+
end
|
232
|
+
|
233
|
+
## 7.9
|
234
|
+
|
235
|
+
class Melos::Struct::ParentHashInput < Melos::Struct::Base
|
236
|
+
attr_reader :encryption_key, :parent_hash, :original_sibling_tree_hash
|
237
|
+
STRUCT = [
|
238
|
+
[:encryption_key, :vec], # HPKEPublicKey
|
239
|
+
[:parent_hash, :vec],
|
240
|
+
[:original_sibling_tree_hash, :vec]
|
241
|
+
]
|
242
|
+
end
|
243
|
+
|
244
|
+
class Melos::Struct::TreeHashInput < Melos::Struct::Base
|
245
|
+
attr_reader :node_type, :leaf_node, :parent_node
|
246
|
+
STRUCT = [
|
247
|
+
[:node_type, :uint8],
|
248
|
+
[:leaf_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::LEAF}, :class, Melos::Struct::LeafNodeHashInput],
|
249
|
+
[:parent_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::PARENT}, :class, Melos::Struct::ParentNodeHashInput],
|
250
|
+
]
|
251
|
+
end
|
252
|
+
|
253
|
+
## 8
|
254
|
+
|
255
|
+
class Melos::Struct::KDFLabel < Melos::Struct::Base
|
256
|
+
attr_reader :length, :label, :context
|
257
|
+
STRUCT = [
|
258
|
+
[:length, :uint16],
|
259
|
+
[:label, :vec],
|
260
|
+
[:context, :vec]
|
261
|
+
]
|
262
|
+
end
|
263
|
+
|
264
|
+
## 8.1
|
265
|
+
|
266
|
+
class Melos::Struct::GroupContext < Melos::Struct::Base
|
267
|
+
attr_reader :version, :cipher_suite, :group_id, :epoch, :tree_hash, :confirmed_transcript_hash
|
268
|
+
attr_accessor :extensions
|
269
|
+
STRUCT = [
|
270
|
+
[:version, :uint16],
|
271
|
+
[:cipher_suite, :uint16],
|
272
|
+
[:group_id, :vec],
|
273
|
+
[:epoch, :uint64],
|
274
|
+
[:tree_hash, :vec],
|
275
|
+
[:confirmed_transcript_hash, :vec],
|
276
|
+
[:extensions, :classes, Melos::Struct::Extension]
|
277
|
+
]
|
278
|
+
|
279
|
+
def self.create(cipher_suite:, group_id:, epoch:, tree_hash:, confirmed_transcript_hash:, extensions:)
|
280
|
+
new_instance = self.allocate
|
281
|
+
new_instance.instance_variable_set(:@version, 1)
|
282
|
+
new_instance.instance_variable_set(:@cipher_suite, cipher_suite)
|
283
|
+
new_instance.instance_variable_set(:@group_id, group_id)
|
284
|
+
new_instance.instance_variable_set(:@epoch, epoch)
|
285
|
+
new_instance.instance_variable_set(:@tree_hash, tree_hash)
|
286
|
+
new_instance.instance_variable_set(:@confirmed_transcript_hash, confirmed_transcript_hash)
|
287
|
+
new_instance.instance_variable_set(:@extensions, extensions)
|
288
|
+
new_instance
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
## 8.4
|
293
|
+
|
294
|
+
class Melos::Struct::PreSharedKeyID < Melos::Struct::Base
|
295
|
+
attr_reader :psktype, :psk_id, :usage, :psk_group_id, :psk_epoch, :psk_nonce
|
296
|
+
STRUCT = [
|
297
|
+
[:psktype, :uint8],
|
298
|
+
[:psk_id, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::EXTERNAL}, :vec], # external
|
299
|
+
[:usage, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :uint8], # resumption
|
300
|
+
[:psk_group_id, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :vec], # resumption
|
301
|
+
[:psk_epoch, :select, ->(ctx){ctx[:psktype] == Melos::Constants::PSKType::RESUMPTION}, :uint64], # resumption
|
302
|
+
[:psk_nonce, :vec]
|
303
|
+
]
|
304
|
+
|
305
|
+
def self.create_external(psk_id:, psk_nonce:)
|
306
|
+
new_instance = self.allocate
|
307
|
+
new_instance.instance_variable_set(:@psktype, Melos::Constants::PSKType::EXTERNAL)
|
308
|
+
new_instance.instance_variable_set(:@psk_id, psk_id)
|
309
|
+
new_instance.instance_variable_set(:@psk_nonce, psk_nonce)
|
310
|
+
new_instance
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
class Melos::Struct::PSKLabel < Melos::Struct::Base
|
315
|
+
attr_reader :id, :index, :count
|
316
|
+
STRUCT = [
|
317
|
+
[:id, :class, Melos::Struct::PreSharedKeyID],
|
318
|
+
[:index, :uint16],
|
319
|
+
[:count, :uint16]
|
320
|
+
]
|
321
|
+
|
322
|
+
def self.create(id:, index:, count:)
|
323
|
+
new_instance = self.allocate
|
324
|
+
new_instance.instance_variable_set(:@id, id)
|
325
|
+
new_instance.instance_variable_set(:@index, index)
|
326
|
+
new_instance.instance_variable_set(:@count, count)
|
327
|
+
new_instance
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
## 10
|
332
|
+
|
333
|
+
class Melos::Struct::KeyPackage < Melos::Struct::Base
|
334
|
+
attr_reader :version, :cipher_suite, :init_key, :leaf_node, :extensions, :signature
|
335
|
+
STRUCT = [
|
336
|
+
[:version, :uint16],
|
337
|
+
[:cipher_suite, :uint16],
|
338
|
+
[:init_key, :vec], # HPKEPublicKey
|
339
|
+
[:leaf_node, :class, Melos::Struct::LeafNode],
|
340
|
+
[:extensions, :classes, Melos::Struct::Extension],
|
341
|
+
[:signature, :vec] # SignWithLabel(., "KeyPackageTBS", KeyPackageTBS)
|
342
|
+
]
|
343
|
+
|
344
|
+
def ref(suite)
|
345
|
+
Melos::Crypto.make_keypackage_ref(suite, self.raw)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
class Melos::Struct::KeyPackageTBS < Melos::Struct::Base
|
350
|
+
attr_reader :version, :cipher_suite, :init_key, :leaf_node, :extensions
|
351
|
+
STRUCT = [
|
352
|
+
[:version, :uint16],
|
353
|
+
[:cipher_suite, :uint16],
|
354
|
+
[:init_key, :vec], # HPKEPublicKey
|
355
|
+
[:leaf_node, :class, Melos::Struct::LeafNode],
|
356
|
+
[:extensions, :classes, Melos::Struct::Extension]
|
357
|
+
]
|
358
|
+
end
|
359
|
+
|
360
|
+
## 11
|
361
|
+
|
362
|
+
class Melos::Struct::RequiredCapabilities < Melos::Struct::Base
|
363
|
+
attr_reader :extension_types, :proposal_types, :credential_types
|
364
|
+
STRUCT = [
|
365
|
+
[:extension_types, :vec], # vec of uint16
|
366
|
+
[:proposal_types, :vec], # vec of uint16
|
367
|
+
[:credential_types, :vec] # vec of uint16
|
368
|
+
]
|
369
|
+
end
|
370
|
+
|
371
|
+
## 12
|
372
|
+
|
373
|
+
## 12.1.1 - 12.1.7
|
374
|
+
|
375
|
+
class Melos::Struct::Add < Melos::Struct::Base
|
376
|
+
attr_reader :key_package
|
377
|
+
STRUCT = [
|
378
|
+
[:key_package, :class, Melos::Struct::KeyPackage]
|
379
|
+
]
|
380
|
+
end
|
381
|
+
|
382
|
+
class Melos::Struct::Update < Melos::Struct::Base
|
383
|
+
attr_reader :leaf_node
|
384
|
+
STRUCT = [
|
385
|
+
[:leaf_node, :class, Melos::Struct::LeafNode]
|
386
|
+
]
|
387
|
+
end
|
388
|
+
|
389
|
+
class Melos::Struct::Remove < Melos::Struct::Base
|
390
|
+
attr_reader :removed
|
391
|
+
STRUCT = [
|
392
|
+
[:removed, :uint32]
|
393
|
+
]
|
394
|
+
end
|
395
|
+
|
396
|
+
class Melos::Struct::PreSharedKey < Melos::Struct::Base
|
397
|
+
attr_reader :psk
|
398
|
+
STRUCT = [
|
399
|
+
[:psk, :class, Melos::Struct::PreSharedKeyID]
|
400
|
+
]
|
401
|
+
end
|
402
|
+
|
403
|
+
class Melos::Struct::ReInit < Melos::Struct::Base
|
404
|
+
attr_reader :group_id, :version, :cipher_suite, :extensions
|
405
|
+
STRUCT = [
|
406
|
+
[:group_id, :vec],
|
407
|
+
[:version, :uint16],
|
408
|
+
[:cipher_suite, :uint16],
|
409
|
+
[:extensions, :classes, Melos::Struct::Extension]
|
410
|
+
]
|
411
|
+
end
|
412
|
+
|
413
|
+
class Melos::Struct::ExternalInit < Melos::Struct::Base
|
414
|
+
attr_reader :kem_output
|
415
|
+
STRUCT = [
|
416
|
+
[:kem_output, :vec]
|
417
|
+
]
|
418
|
+
end
|
419
|
+
|
420
|
+
class Melos::Struct::GroupContextExtensions < Melos::Struct::Base
|
421
|
+
attr_reader :extensions
|
422
|
+
STRUCT = [
|
423
|
+
[:extensions, :classes, Melos::Struct::Extension]
|
424
|
+
]
|
425
|
+
end
|
426
|
+
|
427
|
+
## 12.1.8.1
|
428
|
+
|
429
|
+
class Melos::Struct::ExternalSender < Melos::Struct::Base
|
430
|
+
attr_reader :signature_key, :credential
|
431
|
+
STRUCT = [
|
432
|
+
[:signature_key, :vec],
|
433
|
+
[:credential, :class, Melos::Struct::Credential]
|
434
|
+
]
|
435
|
+
end
|
436
|
+
|
437
|
+
## 12.1
|
438
|
+
|
439
|
+
class Melos::Struct::Proposal < Melos::Struct::Base
|
440
|
+
attr_reader :proposal_type, :add, :update, :remove, :psk, :reinit, :external_init, :group_context_extensions
|
441
|
+
STRUCT = [
|
442
|
+
[:proposal_type, :uint16],
|
443
|
+
[:add, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::ADD}, :class, Melos::Struct::Add],
|
444
|
+
[:update, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::UPDATE}, :class, Melos::Struct::Update],
|
445
|
+
[:remove, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::REMOVE}, :class, Melos::Struct::Remove],
|
446
|
+
[:psk, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::PSK}, :class, Melos::Struct::PreSharedKey],
|
447
|
+
[:reinit, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::REINIT}, :class, Melos::Struct::ReInit],
|
448
|
+
[:external_init, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::EXTERNAL_INIT}, :class, Melos::Struct::ExternalInit],
|
449
|
+
[:group_context_extensions, :select, ->(ctx){ctx[:proposal_type] == Melos::Constants::ProposalType::GROUP_CONTEXT_EXTENSIONS}, :class, Melos::Struct::GroupContextExtensions],
|
450
|
+
]
|
451
|
+
|
452
|
+
def proposal_content
|
453
|
+
@add || @update || @remove || @psk || @reinit || @external_init || @group_context_extensions
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
## 12.4
|
458
|
+
|
459
|
+
class Melos::Struct::ProposalOrRef < Melos::Struct::Base
|
460
|
+
attr_reader :type, :proposal, :reference
|
461
|
+
STRUCT = [
|
462
|
+
[:type, :uint8],
|
463
|
+
[:proposal, :select, ->(ctx){ctx[:type] == Melos::Constants::ProposalOrRefType::PROPOSAL}, :class, Melos::Struct::Proposal],
|
464
|
+
[:reference, :select, ->(ctx){ctx[:type] == Melos::Constants::ProposalOrRefType::REFERENCE}, :vec] # ProposalRef is a HashReference, which is a :vec
|
465
|
+
]
|
466
|
+
end
|
467
|
+
|
468
|
+
class Melos::Struct::Commit < Melos::Struct::Base
|
469
|
+
attr_reader :proposals, :path
|
470
|
+
STRUCT = [
|
471
|
+
[:proposals, :classes, Melos::Struct::ProposalOrRef],
|
472
|
+
[:path, :optional, Melos::Struct::UpdatePath]
|
473
|
+
]
|
474
|
+
end
|
475
|
+
|
476
|
+
## 12.4.3
|
477
|
+
|
478
|
+
class Melos::Struct::GroupInfo < Melos::Struct::Base
|
479
|
+
attr_reader :group_context, :extensions, :confirmation_tag, :signer, :signature
|
480
|
+
STRUCT = [
|
481
|
+
[:group_context, :class, Melos::Struct::GroupContext],
|
482
|
+
[:extensions, :classes, Melos::Struct::Extension],
|
483
|
+
[:confirmation_tag, :vec], # MAC = opaque <V>
|
484
|
+
[:signer, :uint32],
|
485
|
+
[:signature, :vec]
|
486
|
+
]
|
487
|
+
|
488
|
+
def group_info_tbs
|
489
|
+
Melos::Struct::GroupInfoTBS.create(
|
490
|
+
group_context:,
|
491
|
+
extensions:,
|
492
|
+
confirmation_tag:,
|
493
|
+
signer:
|
494
|
+
)
|
495
|
+
end
|
496
|
+
|
497
|
+
def sign(suite, signer_private)
|
498
|
+
Melos::Crypto.sign_with_label(suite, signer_private_key, "GroupInfoTBS", group_info_tbs.raw)
|
499
|
+
end
|
500
|
+
|
501
|
+
def verify(suite, signer_public_key)
|
502
|
+
Melos::Crypto.verify_with_label(suite, signer_public_key, "GroupInfoTBS", group_info_tbs.raw, signature)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
class Melos::Struct::GroupInfoTBS < Melos::Struct::Base
|
508
|
+
attr_reader :group_context, :extensions, :confirmation_tag, :signer, :signature
|
509
|
+
STRUCT = [
|
510
|
+
[:group_context, :class, Melos::Struct::GroupContext],
|
511
|
+
[:extensions, :classes, Melos::Struct::Extension],
|
512
|
+
[:confirmation_tag, :vec], # MAC = opaque <V>
|
513
|
+
[:signer, :uint32]
|
514
|
+
]
|
515
|
+
|
516
|
+
def self.create(group_context:, extensions:, confirmation_tag:, signer:)
|
517
|
+
new_instance = self.allocate
|
518
|
+
new_instance.instance_variable_set(:@group_context, group_context)
|
519
|
+
new_instance.instance_variable_set(:@extensions, extensions)
|
520
|
+
new_instance.instance_variable_set(:@confirmation_tag, confirmation_tag)
|
521
|
+
new_instance.instance_variable_set(:@signer, signer)
|
522
|
+
new_instance
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# 12.4.3.1
|
527
|
+
|
528
|
+
class Melos::Struct::PathSecret < Melos::Struct::Base
|
529
|
+
attr_reader :path_secret
|
530
|
+
STRUCT = [
|
531
|
+
[:path_secret, :vec]
|
532
|
+
]
|
533
|
+
end
|
534
|
+
|
535
|
+
class Melos::Struct::GroupSecrets < Melos::Struct::Base
|
536
|
+
attr_reader :joiner_secret, :path_secret, :psks
|
537
|
+
STRUCT = [
|
538
|
+
[:joiner_secret, :vec],
|
539
|
+
[:path_secret, :optional, Melos::Struct::PathSecret],
|
540
|
+
[:psks, :classes, Melos::Struct::PreSharedKeyID]
|
541
|
+
]
|
542
|
+
end
|
543
|
+
|
544
|
+
class Melos::Struct::EncryptedGroupSecrets < Melos::Struct::Base
|
545
|
+
attr_reader :new_member, :encrypted_group_secrets
|
546
|
+
STRUCT = [
|
547
|
+
[:new_member, :vec], # KeyPackageRef = opaque <V>
|
548
|
+
[:encrypted_group_secrets, :class, Melos::Struct::HPKECipherText]
|
549
|
+
]
|
550
|
+
end
|
551
|
+
|
552
|
+
class Melos::Struct::Welcome < Melos::Struct::Base
|
553
|
+
attr_reader :cipher_suite, :secrets, :encrypted_group_info
|
554
|
+
STRUCT = [
|
555
|
+
[:cipher_suite, :uint16],
|
556
|
+
[:secrets, :classes, Melos::Struct::EncryptedGroupSecrets],
|
557
|
+
[:encrypted_group_info, :vec]
|
558
|
+
]
|
559
|
+
end
|
560
|
+
|
561
|
+
## 12.4.3.2
|
562
|
+
|
563
|
+
class Melos::Struct::ExternalPub < Melos::Struct::Base
|
564
|
+
attr_reader :external_pub
|
565
|
+
STRUCT = [
|
566
|
+
[:external_pub, :vec] # HPKEPublicKey
|
567
|
+
]
|
568
|
+
end
|
569
|
+
|
570
|
+
class Melos::Struct::Node < Melos::Struct::Base
|
571
|
+
attr_reader :node_type, :leaf_node, :parent_node
|
572
|
+
STRUCT = [
|
573
|
+
[:node_type, :uint8],
|
574
|
+
[:leaf_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::LEAF}, :class, Melos::Struct::LeafNode], # leaf
|
575
|
+
[:parent_node, :select, ->(ctx){ctx[:node_type] == Melos::Constants::NodeType::PARENT}, :class, Melos::Struct::ParentNode] # parent
|
576
|
+
]
|
577
|
+
|
578
|
+
def parent_hash_in_node
|
579
|
+
@leaf_node&.parent_hash || @parent_node.parent_hash
|
580
|
+
end
|
581
|
+
|
582
|
+
def public_encryption_key
|
583
|
+
@leaf_node&.encryption_key || @parent_node&.encryption_key
|
584
|
+
end
|
585
|
+
|
586
|
+
def self.new_leaf_node(leaf_node)
|
587
|
+
instance = self.allocate
|
588
|
+
instance.new_leaf_node_impl(leaf_node)
|
589
|
+
instance
|
590
|
+
end
|
591
|
+
|
592
|
+
def self.new_parent_node(parent_node)
|
593
|
+
instance = self.allocate
|
594
|
+
instance.new_parent_node_impl(parent_node)
|
595
|
+
instance
|
596
|
+
end
|
597
|
+
|
598
|
+
def new_leaf_node_impl(leaf_node)
|
599
|
+
@node_type = Melos::Constants::NodeType::LEAF
|
600
|
+
@leaf_node = leaf_node
|
601
|
+
end
|
602
|
+
|
603
|
+
def new_parent_node_impl(parent_node)
|
604
|
+
@node_type = Melos::Constants::NodeType::PARENT
|
605
|
+
@parent_node = parent_node
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# Section 6.1
|
610
|
+
class Melos::Struct::Sender < Melos::Struct::Base
|
611
|
+
attr_reader :sender_type, :leaf_index, :sender_index
|
612
|
+
STRUCT = [
|
613
|
+
[:sender_type, :uint8],
|
614
|
+
[:leaf_index, :select, ->(ctx){ctx[:sender_type] == Melos::Constants::SenderType::MEMBER}, :uint32],
|
615
|
+
[:sender_index, :select, ->(ctx){ctx[:sender_type] == Melos::Constants::SenderType::EXTERNAL}, :uint32],
|
616
|
+
]
|
617
|
+
|
618
|
+
def self.create_member(leaf_index)
|
619
|
+
instance = self.allocate
|
620
|
+
instance.instance_variable_set(:@sender_type, Melos::Constants::SenderType::MEMBER)
|
621
|
+
instance.instance_variable_set(:@leaf_index, leaf_index)
|
622
|
+
instance
|
623
|
+
end
|
624
|
+
|
625
|
+
def self.create_external(sender_index)
|
626
|
+
instance = self.allocate
|
627
|
+
instance.instance_variable_set(:@sender_type, Melos::Constants::SenderType::EXTERNAL)
|
628
|
+
instance.instance_variable_set(:@sender_index, sender_index)
|
629
|
+
instance
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
class Melos::Struct::FramedContentAuthData < Melos::Struct::Base
|
634
|
+
attr_reader :signature, :confirmation_tag, :content_type
|
635
|
+
|
636
|
+
STRUCT = [
|
637
|
+
[:signature, :vec]
|
638
|
+
]
|
639
|
+
|
640
|
+
# initialize from stream
|
641
|
+
def self.new_and_rest_with_content_type(buf, content_type)
|
642
|
+
instance = self.allocate
|
643
|
+
context, buf = instance.send(:deserialize, buf)
|
644
|
+
# custom part based on instance variable
|
645
|
+
if content_type == Melos::Constants::ContentType::COMMIT # commit
|
646
|
+
# read MAC(opaque <V>) confirmation_tag
|
647
|
+
value, buf = Melos::Vec.parse_vec(buf)
|
648
|
+
context << [:confirmation_tag, value]
|
649
|
+
end
|
650
|
+
context << [:content_type, content_type]
|
651
|
+
instance.send(:set_instance_vars, context)
|
652
|
+
[instance, buf]
|
653
|
+
end
|
654
|
+
|
655
|
+
def raw
|
656
|
+
if @content_type == Melos::Constants::ContentType::COMMIT
|
657
|
+
Melos::Vec.from_string(@signature) + Melos::Vec.from_string(@confirmation_tag)
|
658
|
+
else
|
659
|
+
Melos::Vec.from_string(@signature)
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
def self.create(signature:, content_type:, confirmation_tag:)
|
664
|
+
instance = self.allocate
|
665
|
+
instance.instance_variable_set(:@signature, signature)
|
666
|
+
instance.instance_variable_set(:@content_type, content_type)
|
667
|
+
if content_type == Melos::Constants::ContentType::COMMIT
|
668
|
+
instance.instance_variable_set(:@confirmation_tag, confirmation_tag)
|
669
|
+
end
|
670
|
+
instance
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
class Melos::Struct::FramedContent < Melos::Struct::Base
|
675
|
+
attr_reader :group_id, :epoch, :sender, :authenticated_data, :content_type, :application_data, :proposal, :commit
|
676
|
+
STRUCT = [
|
677
|
+
[:group_id, :vec],
|
678
|
+
[:epoch, :uint64],
|
679
|
+
[:sender, :class, Melos::Struct::Sender],
|
680
|
+
[:authenticated_data, :vec],
|
681
|
+
[:content_type, :uint8],
|
682
|
+
[:application_data, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::APPLICATION}, :vec],
|
683
|
+
[:proposal, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::PROPOSAL}, :class, Melos::Struct::Proposal],
|
684
|
+
[:commit, :select, ->(context){context[:content_type] == Melos::Constants::ContentType::COMMIT}, :class, Melos::Struct::Commit]
|
685
|
+
]
|
686
|
+
|
687
|
+
def content_tbs(version, wire_format, group_context)
|
688
|
+
buf = [version].pack('S>') + [wire_format].pack('S>') + self.raw
|
689
|
+
case sender.sender_type
|
690
|
+
when Melos::Constants::SenderType::MEMBER, Melos::Constants::SenderType::NEW_MEMBER_COMMIT
|
691
|
+
buf += group_context.raw
|
692
|
+
when Melos::Constants::SenderType::EXTERNAL, Melos::Constants::SenderType::NEW_MEMBER_PROPOSAL
|
693
|
+
# do nothing
|
694
|
+
end
|
695
|
+
buf
|
696
|
+
end
|
697
|
+
|
698
|
+
def self.create(group_id:, epoch:, sender:, authenticated_data:, content_type:, content:)
|
699
|
+
new_instance = self.allocate
|
700
|
+
new_instance.instance_variable_set(:@group_id, group_id)
|
701
|
+
new_instance.instance_variable_set(:@epoch, epoch)
|
702
|
+
new_instance.instance_variable_set(:@sender, sender)
|
703
|
+
new_instance.instance_variable_set(:@authenticated_data, authenticated_data)
|
704
|
+
new_instance.instance_variable_set(:@content_type, content_type)
|
705
|
+
case content_type
|
706
|
+
when Melos::Constants::ContentType::APPLICATION
|
707
|
+
new_instance.instance_variable_set(:@application_data, content)
|
708
|
+
when Melos::Constants::ContentType::PROPOSAL
|
709
|
+
new_instance.instance_variable_set(:@proposal, content)
|
710
|
+
when Melos::Constants::ContentType::COMMIT
|
711
|
+
new_instance.instance_variable_set(:@commit, content)
|
712
|
+
end
|
713
|
+
new_instance
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
class Melos::Struct::AuthenticatedContent < Melos::Struct::Base
|
718
|
+
attr_reader :wire_format, :content, :auth
|
719
|
+
STRUCT = [
|
720
|
+
[:wire_format, :uint16],
|
721
|
+
[:content, :class, Melos::Struct::FramedContent],
|
722
|
+
[:auth, :framed_content_auth_data]
|
723
|
+
]
|
724
|
+
|
725
|
+
def content_tbm
|
726
|
+
content.content_tbs(Melos::Constants::Version::MLS10, wire_format) + auth.raw
|
727
|
+
end
|
728
|
+
|
729
|
+
def confirmed_transcript_hash_input
|
730
|
+
Melos::Struct::ConfirmedTranscriptHashInput.create(
|
731
|
+
wire_format: wire_format,
|
732
|
+
content: content,
|
733
|
+
signature: auth.signature
|
734
|
+
)
|
735
|
+
end
|
736
|
+
|
737
|
+
def verify(suite, signature_public_key, context)
|
738
|
+
return false if (wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE && content.content_type == Melos::Constants::ContentType::APPLICATION)
|
739
|
+
|
740
|
+
content_tbs = content.content_tbs(Melos::Constants::Version::MLS10, wire_format, context)
|
741
|
+
|
742
|
+
return Melos::Crypto.verify_with_label(suite, signature_public_key, "FramedContentTBS", content_tbs, auth.signature)
|
743
|
+
end
|
744
|
+
|
745
|
+
def self.create(wire_format:, content:, auth:)
|
746
|
+
new_instance = self.allocate
|
747
|
+
new_instance.instance_variable_set(:@wire_format, wire_format)
|
748
|
+
new_instance.instance_variable_set(:@content, content)
|
749
|
+
new_instance.instance_variable_set(:@auth, auth)
|
750
|
+
new_instance
|
751
|
+
end
|
752
|
+
|
753
|
+
# populate auth with values
|
754
|
+
def sign(suite, signature_private_key, group_context)
|
755
|
+
raise ArgumentError.new('Application data cannot be sent as a PublicMessage') if wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE && content.content_type == Melos::Constants::ContentType::APPLICATION
|
756
|
+
content_tbs = content.content_tbs(Melos::Constants::Version::MLS10, wire_format, group_context)
|
757
|
+
signature = Melos::Crypto.sign_with_label(suite, signature_private_key, "FramedContentTBS", content_tbs)
|
758
|
+
@auth = Melos::Struct::FramedContentAuthData.create(
|
759
|
+
signature: signature,
|
760
|
+
content_type: content.content_type,
|
761
|
+
confirmation_tag: nil
|
762
|
+
)
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
## 6.2
|
767
|
+
|
768
|
+
class Melos::Struct::PublicMessage < Melos::Struct::Base
|
769
|
+
attr_reader :content, :auth, :membership_tag
|
770
|
+
STRUCT = [
|
771
|
+
[:content, :class, Melos::Struct::FramedContent],
|
772
|
+
[:auth, :framed_content_auth_data],
|
773
|
+
[:membership_tag, :select, ->(ctx){ctx[:content].sender.sender_type == Melos::Constants::SenderType::MEMBER}, :vec] # MAC is opaque <V>
|
774
|
+
]
|
775
|
+
|
776
|
+
def self.protect(authenticated_content, suite, membership_key, group_context)
|
777
|
+
message = self.allocate
|
778
|
+
message.instance_variable_set(:@content, authenticated_content.content)
|
779
|
+
message.instance_variable_set(:@auth, authenticated_content.auth)
|
780
|
+
if message.content.sender.sender_type == Melos::Constants::SenderType::MEMBER # member
|
781
|
+
message.instance_variable_set(:@membership_tag, message.membership_mac(suite, membership_key, group_context))
|
782
|
+
end
|
783
|
+
message
|
784
|
+
end
|
785
|
+
|
786
|
+
def unprotect(suite, membership_key, group_context)
|
787
|
+
## if sender type is member then membershipMac(suite, membership_key, group_context)
|
788
|
+
if (content.sender.sender_type == Melos::Constants::SenderType::MEMBER)
|
789
|
+
return nil if membership_tag != membership_mac(suite, membership_key, group_context)
|
790
|
+
end
|
791
|
+
Melos::Struct::AuthenticatedContent.create(
|
792
|
+
wire_format: Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE, # public_message
|
793
|
+
content: content,
|
794
|
+
auth: auth
|
795
|
+
)
|
796
|
+
end
|
797
|
+
|
798
|
+
def membership_mac(suite, membership_key, group_context)
|
799
|
+
authenticated_content_tbm = content.content_tbs(
|
800
|
+
Melos::Constants::Version::MLS10,
|
801
|
+
Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE,
|
802
|
+
group_context
|
803
|
+
) + auth.raw
|
804
|
+
Melos::Crypto.mac(suite, membership_key, authenticated_content_tbm)
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
## 6.3
|
809
|
+
|
810
|
+
class Melos::Struct::PrivateMessage < Melos::Struct::Base
|
811
|
+
attr_reader :group_id, :epoch, :content_type, :authenticated_data, :encrypted_sender_data, :ciphertext
|
812
|
+
STRUCT = [
|
813
|
+
[:group_id, :vec],
|
814
|
+
[:epoch, :uint64],
|
815
|
+
[:content_type, :uint8],
|
816
|
+
[:authenticated_data, :vec],
|
817
|
+
[:encrypted_sender_data, :vec],
|
818
|
+
[:ciphertext, :vec]
|
819
|
+
]
|
820
|
+
|
821
|
+
def self.create(group_id:, epoch:, content_type:, authenticated_data:, encrypted_sender_data:, ciphertext:)
|
822
|
+
new_instance = self.allocate
|
823
|
+
new_instance.instance_variable_set(:@group_id, group_id)
|
824
|
+
new_instance.instance_variable_set(:@epoch, epoch)
|
825
|
+
new_instance.instance_variable_set(:@content_type, content_type)
|
826
|
+
new_instance.instance_variable_set(:@authenticated_data, authenticated_data)
|
827
|
+
new_instance.instance_variable_set(:@encrypted_sender_data, encrypted_sender_data)
|
828
|
+
new_instance.instance_variable_set(:@ciphertext, ciphertext)
|
829
|
+
new_instance
|
830
|
+
end
|
831
|
+
|
832
|
+
def sender_data_aad
|
833
|
+
self.class.sender_data_aad_impl(group_id, epoch, content_type)
|
834
|
+
end
|
835
|
+
|
836
|
+
def self.sender_data_aad_impl(gid, ep, ct)
|
837
|
+
Melos::Vec.from_string(gid) + [ep].pack('Q>') + [ct].pack('C')
|
838
|
+
end
|
839
|
+
|
840
|
+
def private_content_aad
|
841
|
+
self.class.private_content_aad_impl(group_id, epoch, content_type, authenticated_data)
|
842
|
+
end
|
843
|
+
|
844
|
+
def self.private_content_aad_impl(gid, ep, ct, ad)
|
845
|
+
Melos::Vec.from_string(gid) + [ep].pack('Q>') + [ct].pack('C') + Melos::Vec.from_string(ad)
|
846
|
+
end
|
847
|
+
|
848
|
+
def self.protect(authenticated_content, suite, secret_tree, sender_data_secret, padding_size)
|
849
|
+
leaf_index = authenticated_content.content.sender.leaf_index
|
850
|
+
content_type = authenticated_content.content.content_type
|
851
|
+
reuse_guard = SecureRandom.random_bytes(4)
|
852
|
+
key, nonce, generation = Melos::SecretTree::ratchet_and_get(suite, content_type, secret_tree, leaf_index)
|
853
|
+
new_nonce = apply_nonce_reuse_guard(nonce, reuse_guard)
|
854
|
+
|
855
|
+
private_message_content_plain = serialize_private_message_content(authenticated_content.content, authenticated_content.auth, padding_size)
|
856
|
+
aad = private_content_aad_impl(
|
857
|
+
authenticated_content.content.group_id,
|
858
|
+
authenticated_content.content.epoch,
|
859
|
+
authenticated_content.content.content_type,
|
860
|
+
authenticated_content.content.authenticated_data)
|
861
|
+
private_message_content_ciphertext = Melos::Crypto.aead_encrypt(suite, key, new_nonce, aad, private_message_content_plain)
|
862
|
+
|
863
|
+
sender_data_plain = Melos::Struct::SenderData.create(
|
864
|
+
leaf_index: leaf_index,
|
865
|
+
generation: generation,
|
866
|
+
reuse_guard: reuse_guard
|
867
|
+
)
|
868
|
+
sd_aad = sender_data_aad_impl(
|
869
|
+
authenticated_content.content.group_id,
|
870
|
+
authenticated_content.content.epoch,
|
871
|
+
authenticated_content.content.content_type)
|
872
|
+
sender_data_key = Melos::Crypto.sender_data_key(suite, sender_data_secret, private_message_content_ciphertext)
|
873
|
+
sender_data_nonce = Melos::Crypto.sender_data_nonce(suite, sender_data_secret, private_message_content_ciphertext)
|
874
|
+
sender_data_ciphertext = Melos::Crypto.aead_encrypt(suite, sender_data_key, sender_data_nonce, sd_aad, sender_data_plain.raw)
|
875
|
+
|
876
|
+
create(
|
877
|
+
group_id: authenticated_content.content.group_id,
|
878
|
+
epoch: authenticated_content.content.epoch,
|
879
|
+
content_type: authenticated_content.content.content_type,
|
880
|
+
authenticated_data: authenticated_content.content.authenticated_data,
|
881
|
+
encrypted_sender_data: sender_data_ciphertext,
|
882
|
+
ciphertext: private_message_content_ciphertext
|
883
|
+
)
|
884
|
+
end
|
885
|
+
|
886
|
+
def unprotect(suite, secret_tree, sender_data_secret)
|
887
|
+
sender_data = decrypt_sender_data(suite, sender_data_secret)
|
888
|
+
key, nonce, _ = Melos::SecretTree.ratchet_until_and_get(suite, content_type, secret_tree, sender_data.leaf_index, sender_data.generation)
|
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)
|
891
|
+
|
892
|
+
fc = Melos::Struct::FramedContent.create(
|
893
|
+
group_id: group_id,
|
894
|
+
epoch: epoch,
|
895
|
+
sender: Melos::Struct::Sender.create_member(sender_data.leaf_index),
|
896
|
+
authenticated_data: authenticated_data,
|
897
|
+
content_type: content_type,
|
898
|
+
content: pmc.content
|
899
|
+
)
|
900
|
+
|
901
|
+
Melos::Struct::AuthenticatedContent.create(
|
902
|
+
wire_format: Melos::Constants::WireFormat::MLS_PRIVATE_MESSAGE, # private_message
|
903
|
+
content: fc,
|
904
|
+
auth: pmc.auth
|
905
|
+
)
|
906
|
+
end
|
907
|
+
|
908
|
+
private
|
909
|
+
def decrypt_sender_data(suite, sender_data_secret)
|
910
|
+
sender_data_key = Melos::Crypto.sender_data_key(suite, sender_data_secret, ciphertext)
|
911
|
+
sender_data_nonce = Melos::Crypto.sender_data_nonce(suite, sender_data_secret, ciphertext)
|
912
|
+
Melos::Struct::SenderData.new(Melos::Crypto.aead_decrypt(suite, sender_data_key, sender_data_nonce, sender_data_aad, encrypted_sender_data))
|
913
|
+
end
|
914
|
+
|
915
|
+
def self.apply_nonce_reuse_guard(nonce, guard)
|
916
|
+
guard_arr = guard.unpack('c*')
|
917
|
+
nonce_arr = nonce.unpack('c*')
|
918
|
+
guard_arr.each_with_index do |char, index|
|
919
|
+
nonce_arr[index] = nonce_arr[index] ^ char
|
920
|
+
end
|
921
|
+
nonce_arr.pack('C*')
|
922
|
+
end
|
923
|
+
|
924
|
+
def self.serialize_private_message_content(framed_content, framed_content_auth_data, padding_size)
|
925
|
+
buf = ''
|
926
|
+
case framed_content.content_type
|
927
|
+
when Melos::Constants::ContentType::APPLICATION
|
928
|
+
buf += Melos::Vec.from_string(framed_content.application_data)
|
929
|
+
when Melos::Constants::ContentType::PROPOSAL
|
930
|
+
buf += framed_content.proposal.raw
|
931
|
+
when Melos::Constants::ContentType::COMMIT
|
932
|
+
buf += framed_content.commit.raw
|
933
|
+
end
|
934
|
+
buf += Melos::Vec.from_string(framed_content_auth_data.signature)
|
935
|
+
if framed_content.content_type == Melos::Constants::ContentType::COMMIT
|
936
|
+
buf += Melos::Vec.from_string(framed_content_auth_data.confirmation_tag)
|
937
|
+
end
|
938
|
+
buf += Melos::Crypto::Util.zero_vector(padding_size)
|
939
|
+
buf
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
class Melos::Struct::PrivateMessageContent < Melos::Struct::Base
|
944
|
+
attr_accessor :application_data, :proposal, :commit, :auth, :padding
|
945
|
+
# bytes -> struct: decode the content and auth field, rest is padding
|
946
|
+
# struct -> bytes: encode content and auth field, add set amount of padding (zero bytes)
|
947
|
+
|
948
|
+
def self.new_and_rest_with_content_type(buf, content_type)
|
949
|
+
instance = self.allocate
|
950
|
+
context = []
|
951
|
+
# deserialize application_data/proposal/commit
|
952
|
+
case content_type
|
953
|
+
when Melos::Constants::ContentType::APPLICATION
|
954
|
+
value, buf = Melos::Vec.parse_vec(buf)
|
955
|
+
context << [:application_data, value]
|
956
|
+
when Melos::Constants::ContentType::PROPOSAL
|
957
|
+
value, buf = Melos::Struct::Proposal.new_and_rest(buf)
|
958
|
+
context << [:proposal, value]
|
959
|
+
when Melos::Constants::ContentType::COMMIT
|
960
|
+
value, buf = Melos::Struct::Commit.new_and_rest(buf)
|
961
|
+
context << [:commit, value]
|
962
|
+
end
|
963
|
+
fcad, buf = Melos::Struct::FramedContentAuthData.new_and_rest_with_content_type(buf, content_type)
|
964
|
+
context << [:auth, fcad]
|
965
|
+
# assume rest is padding
|
966
|
+
context << [:padding, buf]
|
967
|
+
instance.send(:set_instance_vars, context)
|
968
|
+
[instance, '']
|
969
|
+
end
|
970
|
+
|
971
|
+
def content
|
972
|
+
@application_data || @proposal || @commit
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
## 8.2
|
977
|
+
|
978
|
+
class Melos::Struct::ConfirmedTranscriptHashInput < Melos::Struct::Base
|
979
|
+
attr_accessor :wire_format, :content, :signature
|
980
|
+
STRUCT = [
|
981
|
+
[:wire_format, :uint16],
|
982
|
+
[:content, :class, Melos::Struct::FramedContent], # with content_type == commit
|
983
|
+
[:signature, :vec]
|
984
|
+
]
|
985
|
+
|
986
|
+
def self.create(wire_format:, content:, signature:)
|
987
|
+
new_instance = self.allocate
|
988
|
+
new_instance.instance_variable_set(:@wire_format, wire_format)
|
989
|
+
new_instance.instance_variable_set(:@content, content)
|
990
|
+
new_instance.instance_variable_set(:@signature, signature)
|
991
|
+
new_instance
|
992
|
+
end
|
993
|
+
end
|
994
|
+
|
995
|
+
class Melos::Struct::InterimTranscriptHashInput < Melos::Struct::Base
|
996
|
+
attr_reader :confirmation_tag
|
997
|
+
STRUCT = [
|
998
|
+
[:confirmation_tag, :vec]
|
999
|
+
]
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
class Melos::Struct::MLSMessage < Melos::Struct::Base
|
1003
|
+
attr_accessor :version, :wire_format, :public_message, :private_message, :welcome, :group_info, :key_package
|
1004
|
+
STRUCT = [
|
1005
|
+
[:version, :uint16], #mls10 = 1
|
1006
|
+
[:wire_format, :uint16],
|
1007
|
+
[:public_message, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE}, :class, Melos::Struct::PublicMessage],
|
1008
|
+
[:private_message, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_PRIVATE_MESSAGE}, :class, Melos::Struct::PrivateMessage],
|
1009
|
+
[:welcome, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_WELCOME}, :class, Melos::Struct::Welcome],
|
1010
|
+
[:group_info, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_GROUP_INFO}, :class, Melos::Struct::GroupInfo],
|
1011
|
+
[:key_package, :select, ->(ctx){ctx[:wire_format] == Melos::Constants::WireFormat::MLS_KEY_PACKAGE}, :class, Melos::Struct::KeyPackage]
|
1012
|
+
]
|
1013
|
+
|
1014
|
+
def verify(suite, signer_public_key, group_context)
|
1015
|
+
if wire_format == Melos::Constants::WireFormat::MLS_PUBLIC_MESSAGE
|
1016
|
+
public_message.verify(suite, signer_public_key, version, wire_format, group_context)
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
end
|