selfsdk 0.0.124 → 0.0.129

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: df6729e3320be7cc2abd4ea22a94ff757b8e8d72ee5e1f2b2ddbafed509483c4
4
- data.tar.gz: c7202790ad7ac8c0fcb984a3c494983b08549e9e516b238cc247d869a630d2a2
3
+ metadata.gz: 7ba1c83bef5db464131c2343e7f81500c1e5175fd9f740b7563dad90613dd73c
4
+ data.tar.gz: 12c89161d2837b4f6dba855b43bd554fcb6380dbd207fe7482e9f9fcef4d8493
5
5
  SHA512:
6
- metadata.gz: 860606a05e95e553e6c1e36c63b15a1e3b93922d56e0a58a773d911da0ea29d95f5395406b13124b005e05c468d1c2740308e55908decffc77eb79d6c569ac14
7
- data.tar.gz: 7a3ba36d29276730966cc01ce4d2730c0613142ce6d844deb61380a3b3055596e724bfe78160061436cf43f9f510b49b7407a3a9eb107e0e057c094f9d8e7f32
6
+ metadata.gz: bd1a8234d5d4ba9ef9589ed41e2a60e9313aa2ba45d3de46f428db95e716125dc9213dff852025643e2c1bafc9342cb3e823eb435e3ed903947161d08c9252bb
7
+ data.tar.gz: 52c50572cf2587106fb160b3042faeb8c129d2bc35d8df9ee6a29a1b7617af7723be54a16b43190c774ed93a4d694a0cfacc85e0882b5867229f8fdd0ce0c390
@@ -59,11 +59,21 @@ module SelfSDK
59
59
  # Lists all public keys stored on self for the given ID
60
60
  #
61
61
  # @param id [string] identity id
62
+ # DEPRECATED
62
63
  def public_keys(id)
63
64
  i = entity(id)
64
65
  i[:public_keys]
65
66
  end
66
67
 
68
+ # Lists all public keys stored on self for the given ID
69
+ #
70
+ # @param id [string] identity id
71
+ def public_key(id, kid)
72
+ i = entity(id)
73
+ sg = SelfSDK::SignatureGraph.new(i[:history])
74
+ sg.key_by_id(kid)
75
+ end
76
+
67
77
  private
68
78
 
69
79
  def get_identity(endpoint)
@@ -5,7 +5,7 @@ require 'json'
5
5
 
6
6
  module SelfSDK
7
7
  class JwtService
8
- attr_reader :id, :key
8
+ attr_reader :id, :key, :key_id
9
9
 
10
10
  # Jwt initializer
11
11
  #
@@ -13,7 +13,14 @@ module SelfSDK
13
13
  # @param app_key [string] the app api key provided by developer portal.
14
14
  def initialize(app_id, app_key)
15
15
  @id = app_id
16
- @key = app_key
16
+ parts = app_key.split(':')
17
+ if parts.length > 1
18
+ @key_id = parts[0]
19
+ @key = parts[1]
20
+ else
21
+ @key_id = "1"
22
+ @key = app_key
23
+ end
17
24
  end
18
25
 
19
26
  # Prepares a jwt object based on an input
@@ -85,7 +92,7 @@ module SelfSDK
85
92
  private
86
93
 
87
94
  def header
88
- encode({ alg: "EdDSA", typ: "JWT" }.to_json)
95
+ encode({ alg: "EdDSA", typ: "JWT", kid: "#{@key_id}" }.to_json)
89
96
  end
90
97
  end
91
98
  end
@@ -15,7 +15,8 @@ module SelfSDK
15
15
  @to = payload[:sub]
16
16
  @audience = payload[:aud]
17
17
  @source = payload[:source]
18
- @verified = valid_signature?(attestation)
18
+ header = JSON.parse(@messaging.jwt.decode(attestation[:protected]), symbolize_names: true)
19
+ @verified = valid_signature?(attestation, header[:kid])
19
20
  @expected_value = payload[:expected_value]
20
21
  @operator = payload[:operator]
21
22
  @fact_name = name.to_s
@@ -24,8 +25,8 @@ module SelfSDK
24
25
  end
25
26
  end
26
27
 
27
- def valid_signature?(body)
28
- k = @messaging.client.public_keys(@origin).first[:key]
28
+ def valid_signature?(body, kid)
29
+ k = @messaging.client.public_key(@origin, kid).raw_public_key
29
30
  raise ::StandardError.new("invalid signature") unless @messaging.jwt.verify(body, k)
30
31
 
31
32
  true
@@ -72,13 +72,14 @@ module SelfSDK
72
72
 
73
73
  jwt = JSON.parse(body, symbolize_names: true)
74
74
  payload = JSON.parse(@jwt.decode(jwt[:payload]), symbolize_names: true)
75
+ header = JSON.parse(@jwt.decode(jwt[:protected]), symbolize_names: true)
75
76
  @from = payload[:iss]
76
- verify! jwt
77
+ verify! jwt, header[:kid]
77
78
  payload
78
79
  end
79
80
 
80
- def verify!(jwt)
81
- k = @client.public_keys(@from).first[:key]
81
+ def verify!(jwt, kid)
82
+ k = @client.public_key(@from, kid).raw_public_key
82
83
  return if @jwt.verify(jwt, k)
83
84
 
84
85
  SelfSDK.logger.info "skipping message, invalid signature"
@@ -44,7 +44,7 @@ module SelfSDK
44
44
  @offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
45
45
  @offset = read_offset
46
46
 
47
- FileUtils.mkdir_p @storage_dir unless File.exists? @storage_dir
47
+ FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
48
48
 
49
49
  if options.include? :ws
50
50
  @ws = options[:ws]
@@ -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'
@@ -26,8 +27,8 @@ module SelfSDK
26
27
  # @attr_reader [Types] app_id the identifier of the current app.
27
28
  # @attr_reader [Types] app_key the api key for the current app.
28
29
  class App
29
- BASE_URL = "https://api.selfid.net".freeze
30
- MESSAGING_URL = "wss://messaging.selfid.net/v1/messaging".freeze
30
+ BASE_URL = "https://api.joinself.com".freeze
31
+ MESSAGING_URL = "wss://messaging.joinself.com/v1/messaging".freeze
31
32
 
32
33
  attr_reader :client
33
34
  attr_accessor :messaging_client
@@ -95,13 +96,13 @@ module SelfSDK
95
96
 
96
97
  def base_url(opts)
97
98
  return opts[:base_url] if opts.key? :base_url
98
- return "https://api.#{opts[:env].to_s}.selfid.net" if opts.key? :env
99
+ return "https://api.#{opts[:env].to_s}.joinself.com" if opts.key? :env
99
100
  BASE_URL
100
101
  end
101
102
 
102
103
  def messaging_url(opts)
103
104
  return opts[:messaging_url] if opts.key? :messaging_url
104
- return "wss://messaging.#{opts[:env].to_s}.selfid.net/v1/messaging" if opts.key? :env
105
+ return "wss://messaging.#{opts[:env].to_s}.joinself.com/v1/messaging" if opts.key? :env
105
106
  MESSAGING_URL
106
107
  end
107
108
 
@@ -27,9 +27,10 @@ module SelfSDK
27
27
  # Gets an identity public keys
28
28
  #
29
29
  # @param [String] selfid gets the identity details (app/user)
30
+ # @param [String] kid the public key id.
30
31
  # @return [Array] with the identity public keys
31
- def public_keys(selfid)
32
- @client.public_keys(selfid)
32
+ def public_key(selfid, kid)
33
+ @client.public_key(selfid, kid).public_key
33
34
  end
34
35
 
35
36
  # Gets an app/identity details
@@ -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.124
4
+ version: 0.0.129
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: []
@@ -350,5 +351,5 @@ requirements: []
350
351
  rubygems_version: 3.0.3
351
352
  signing_key:
352
353
  specification_version: 4
353
- summary: self id gem
354
+ summary: joinself sdk
354
355
  test_files: []