selfsdk 0.0.124 → 0.0.129

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