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 +4 -4
- data/lib/client.rb +10 -0
- data/lib/jwt_service.rb +10 -3
- data/lib/messages/attestation.rb +4 -3
- data/lib/messages/base.rb +4 -3
- data/lib/messaging.rb +1 -1
- data/lib/selfsdk.rb +5 -4
- data/lib/services/identity.rb +3 -2
- data/lib/signature_graph.rb +283 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ba1c83bef5db464131c2343e7f81500c1e5175fd9f740b7563dad90613dd73c
|
4
|
+
data.tar.gz: 12c89161d2837b4f6dba855b43bd554fcb6380dbd207fe7482e9f9fcef4d8493
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd1a8234d5d4ba9ef9589ed41e2a60e9313aa2ba45d3de46f428db95e716125dc9213dff852025643e2c1bafc9342cb3e823eb435e3ed903947161d08c9252bb
|
7
|
+
data.tar.gz: 52c50572cf2587106fb160b3042faeb8c129d2bc35d8df9ee6a29a1b7617af7723be54a16b43190c774ed93a4d694a0cfacc85e0882b5867229f8fdd0ce0c390
|
data/lib/client.rb
CHANGED
@@ -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)
|
data/lib/jwt_service.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/messages/attestation.rb
CHANGED
@@ -15,7 +15,8 @@ module SelfSDK
|
|
15
15
|
@to = payload[:sub]
|
16
16
|
@audience = payload[:aud]
|
17
17
|
@source = payload[:source]
|
18
|
-
|
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.
|
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
|
data/lib/messages/base.rb
CHANGED
@@ -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.
|
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"
|
data/lib/messaging.rb
CHANGED
@@ -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.
|
47
|
+
FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
|
48
48
|
|
49
49
|
if options.include? :ws
|
50
50
|
@ws = options[:ws]
|
data/lib/selfsdk.rb
CHANGED
@@ -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.
|
30
|
-
MESSAGING_URL = "wss://messaging.
|
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}.
|
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}.
|
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
|
|
data/lib/services/identity.rb
CHANGED
@@ -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
|
32
|
-
@client.
|
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.
|
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:
|
354
|
+
summary: joinself sdk
|
354
355
|
test_files: []
|