selfsdk 0.0.127 → 0.0.128

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: 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: []