selfsdk 0.0.127 → 0.0.128

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d9d4b995fb2ea36f3e01c5910bf15755797c1afb3ba13c2ab58c8c89ff73e3a
4
- data.tar.gz: 1c22b973f561e4c47f5883d91a15409989fa34b192712a7195f391a6d7854b41
3
+ metadata.gz: 125110bf24194be23beb2ef32eaffc67cdd4fb0f0124c8b6bc2da283fc7cf999
4
+ data.tar.gz: f058e20f529c0c71480b673cb78d511bee67056a2c8aa215663552443311be4d
5
5
  SHA512:
6
- metadata.gz: 3ae863e903d8ee9f59c74473a44e7f9312f76d2f5a54e14b8959f3ff6e5b36593c5f04eaf0b6718a5868f91ab8e8f3cda6edaaeb3a46787f761362bedcb3691a
7
- data.tar.gz: 71fbc1f03e5c7df88bee3d991b3d7fb8ba37797fea605c60af83a9168be89f7aa76df42b9b2680a0082054fe5bd5f0c0cfaf24ca3427a5423d4961147deaff0b
6
+ metadata.gz: 1748a529d80ef342b18d4a8a8e9b2ab14191d478ce7d431c5ce192ac6052e05a20b62cc27851cc3f890eacbef65fe2f4850a934d99a50f3879f90698b6abbd86
7
+ data.tar.gz: 1648159549fa5474b98a141a7df7fe2901a50f0472e9e0cc03da0e65ab923ed52c8589344b34aff25b9dd9c32e1ad9e9abe65ed6b52c11364520139dde1aae61
@@ -64,6 +64,15 @@ module SelfSDK
64
64
  i[:public_keys]
65
65
  end
66
66
 
67
+ # Lists all public keys stored on self for the given ID
68
+ #
69
+ # @param id [string] identity id
70
+ def public_key(id, kid)
71
+ i = entity(id)
72
+ sg = SelfSDK::SignatureGraph.new(i[:history])
73
+ sg.key_by_id(kid)
74
+ end
75
+
67
76
  private
68
77
 
69
78
  def get_identity(endpoint)
@@ -7,6 +7,7 @@ require 'net/http'
7
7
  require 'rqrcode'
8
8
  require_relative 'log'
9
9
  require_relative 'jwt_service'
10
+ require_relative 'signature_graph'
10
11
  require_relative 'client'
11
12
  require_relative 'messaging'
12
13
  require_relative 'ntptime'
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+
6
+ module SelfSDK
7
+
8
+ ACTION_ADD = "key.add"
9
+ ACTION_REVOKE = "key.revoke"
10
+ KEY_TYPE_DEVICE = "device.key"
11
+ KEY_TYPE_RECOVERY = "recovery.key"
12
+
13
+ class Operation
14
+
15
+ attr_reader :sequence, :previous, :timestamp, :actions, :signing_key, :jws
16
+
17
+ def initialize(operation)
18
+ @jws = operation
19
+
20
+ payload = Base64.urlsafe_decode64(@jws[:payload])
21
+ header = Base64.urlsafe_decode64(@jws[:protected])
22
+
23
+ op = JSON.parse(payload, symbolize_names: true)
24
+ hdr = JSON.parse(header, symbolize_names: true)
25
+
26
+ @sequence = op[:sequence]
27
+ @previous = op[:previous]
28
+ @timestamp = op[:timestamp]
29
+ @version = op[:version]
30
+ @actions = op[:actions]
31
+ @signing_key = hdr[:kid]
32
+
33
+ validate!
34
+ end
35
+
36
+ def validate!
37
+ raise "unknown operation version" if @version != "1.0.0"
38
+ raise "invalid operation sequence" if @sequence < 0
39
+ raise "operation does not specify a previous signature" if @previous.nil?
40
+ raise "invalid operation timestamp" if @timestamp < 1
41
+ raise "operation does not specify any actions" if @actions.nil?
42
+ raise "operation does not specify any actions" if @actions.length < 1
43
+ raise "operation does not specify an identifier for the signing key" if @signing_key.nil?
44
+ end
45
+
46
+ def revokes(kid)
47
+ @actions.each do |action|
48
+ if action[:kid] == kid && action[:action] == ACTION_REVOKE
49
+ return true
50
+ end
51
+ end
52
+ return false
53
+ end
54
+ end
55
+
56
+ class Key
57
+
58
+ attr_reader :kid, :did, :type, :created, :revoked, :public_key, :raw_public_key, :incoming, :outgoing
59
+
60
+ def initialize(action)
61
+ @kid = action[:kid]
62
+ @did = action[:did]
63
+ @type = action[:type]
64
+ @created = action[:from]
65
+ @revoked = 0
66
+
67
+ @raw_public_key = Base64.urlsafe_decode64(action[:key])
68
+ @public_key = Ed25519::VerifyKey.new(@raw_public_key)
69
+
70
+ @incoming = Array.new
71
+ @outgoing = Array.new
72
+ end
73
+
74
+ def valid_at(at)
75
+ created <= at && revoked == 0 || created <= at && revoked > at
76
+ end
77
+
78
+ def revoke(at)
79
+ @revoked = at
80
+ end
81
+
82
+ def revoked?
83
+ @revoked > 0
84
+ end
85
+
86
+ def child_keys
87
+ keys = @outgoing.dup
88
+
89
+ @outgoing.each do |k|
90
+ keys.concat k.child_keys
91
+ end
92
+
93
+ keys
94
+ end
95
+ end
96
+
97
+ class SignatureGraph
98
+ def initialize(history)
99
+ @root = nil
100
+ @keys = Hash.new
101
+ @devices = Hash.new
102
+ @signatures = Hash.new
103
+ @operations = Array.new
104
+ @recovery_key = nil
105
+
106
+ history.each do |operation|
107
+ execute(operation)
108
+ end
109
+ end
110
+
111
+ def key_by_id(kid)
112
+ k = @keys[kid]
113
+ raise "key not found" if k.nil?
114
+ k
115
+ end
116
+
117
+ def key_by_device(did)
118
+ k = @devices[did]
119
+ raise "key not found" if k.nil?
120
+ k
121
+ end
122
+
123
+ def execute(operation)
124
+ op = Operation.new(operation)
125
+
126
+ raise "operation sequence is out of order" if op.sequence != @operations.length
127
+
128
+ if op.sequence > 0
129
+ if @signatures[op.previous] != op.sequence - 1
130
+ raise "operation previous signature does not match"
131
+ end
132
+
133
+ if @operations[op.sequence - 1].timestamp >= op.timestamp
134
+ raise "operation timestamp occurs before previous operation"
135
+ end
136
+
137
+ sk = @keys[op.signing_key]
138
+
139
+ raise "operation specifies a signing key that does not exist" if sk.nil?
140
+
141
+ if sk.revoked? && op.timestamp > sk.revoked
142
+ raise "operation was signed by a key that was revoked at the time of signing"
143
+ end
144
+
145
+ if sk.type == KEY_TYPE_RECOVERY && op.revokes(op.signing_key) != true
146
+ raise "account recovery operation does not revoke the current active recovery key"
147
+ end
148
+ end
149
+
150
+ execute_actions(op)
151
+
152
+ sk = @keys[op.signing_key]
153
+
154
+ raise "operation specifies a signing key that does not exist" if sk.nil?
155
+
156
+ if op.timestamp < sk.created || sk.revoked? && op.timestamp > sk.revoked
157
+ raise "operation was signed with a key that was revoked"
158
+ end
159
+
160
+ sig = Base64.urlsafe_decode64(op.jws[:signature])
161
+
162
+ sk.public_key.verify(sig, "#{op.jws[:protected]}.#{op.jws[:payload]}")
163
+
164
+ has_valid_key = false
165
+
166
+ @keys.each do |kid, k|
167
+ has_valid_key = true unless k.revoked?
168
+ end
169
+
170
+ raise "signature graph does not contain any active or valid keys" unless has_valid_key
171
+ raise "signature graph does not contain a valid recovery key" if @recovery_key.nil?
172
+ raise "signature graph does not contain a valid recovery key" if @recovery_key.revoked?
173
+
174
+ @operations.push(op)
175
+ @signatures[op.jws[:signature]] = op.sequence
176
+ end
177
+
178
+ private
179
+
180
+ def execute_actions(op)
181
+ op.actions.each do |action|
182
+ raise "operation action does not provide a key identifier" if action[:kid].nil?
183
+
184
+ if action[:type] != KEY_TYPE_DEVICE && action[:type] != KEY_TYPE_RECOVERY
185
+ raise "operation action does not provide a valid type"
186
+ end
187
+
188
+ if action[:action] != ACTION_ADD && action[:action] != ACTION_REVOKE
189
+ raise "operation action does not provide a valid action"
190
+ end
191
+
192
+ if action[:action] == ACTION_ADD && action[:key].nil?
193
+ raise "operation action does not provide a valid public key"
194
+ end
195
+
196
+ if action[:action] == ACTION_ADD && action[:type] == KEY_TYPE_DEVICE && action[:did].nil?
197
+ raise "operation action does not provide a valid device id"
198
+ end
199
+
200
+ if action[:from] < 0
201
+ raise "operation action does not provide a valid timestamp for the action to take effect from"
202
+ end
203
+
204
+ case action[:action]
205
+ when ACTION_ADD
206
+ action[:from] = op.timestamp
207
+ add(op, action)
208
+ when ACTION_REVOKE
209
+ revoke(op, action)
210
+ end
211
+ end
212
+ end
213
+
214
+ def add(operation, action)
215
+ if @keys[action[:kid]].nil? != true
216
+ raise "operation contains a key with a duplicate identifier"
217
+ end
218
+
219
+ k = Key.new(action)
220
+
221
+ case action[:type]
222
+ when KEY_TYPE_DEVICE
223
+ dk = @devices[action[:did]]
224
+ unless dk.nil?
225
+ raise "operation contains more than one active key for a device" unless dk.revoked?
226
+ end
227
+ when KEY_TYPE_RECOVERY
228
+ unless @recovery_key.nil?
229
+ raise "operation contains more than one active recovery key" unless @recovery_key.revoked?
230
+ end
231
+
232
+ @recovery_key = k
233
+ end
234
+
235
+ @keys[k.kid] = k
236
+ @devices[k.did] = k
237
+
238
+ if operation.sequence == 0 && operation.signing_key == action[:kid]
239
+ @root = k
240
+ return
241
+ end
242
+
243
+ parent = @keys[operation.signing_key]
244
+
245
+ raise "operation specifies a signing key that does not exist" if parent.nil?
246
+
247
+ k.incoming.push(parent)
248
+ parent.outgoing.push(k)
249
+ end
250
+
251
+ def revoke(operation, action)
252
+ k = @keys[action[:kid]]
253
+
254
+ raise "operation tries to revoke a key that does not exist" if k.nil?
255
+ raise "root operation cannot revoke keys" if operation.sequence < 1
256
+ raise "operation tries to revoke a key that has already been revoked" if k.revoked?
257
+
258
+ k.revoke(action[:from])
259
+
260
+ sk = @keys[operation.signing_key]
261
+
262
+ raise "operation specifies a signing key that does not exist" if k.nil?
263
+
264
+ # if this is an account recovery, nuke all existing keys
265
+ if sk.type == KEY_TYPE_RECOVERY
266
+ @root.revoke(action[:from])
267
+
268
+ @root.child_keys.each do |ck|
269
+ ck.revoke(action[:from]) unless ck.revoked?
270
+ end
271
+
272
+ return
273
+ end
274
+
275
+ k.child_keys.each do |ck|
276
+ ck.revoke(action[:from]) unless ck.created < action[:from]
277
+ end
278
+ end
279
+
280
+ end
281
+
282
+ end
283
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selfsdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.127
4
+ version: 0.0.128
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aldgate Ventures
@@ -325,6 +325,7 @@ files:
325
325
  - lib/services/facts.rb
326
326
  - lib/services/identity.rb
327
327
  - lib/services/messaging.rb
328
+ - lib/signature_graph.rb
328
329
  - lib/sources.rb
329
330
  homepage: https://www.joinself.com/
330
331
  licenses: []