selfsdk 0.0.126 → 0.0.131
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 +13 -4
- data/lib/messages/attestation.rb +4 -3
- data/lib/messages/base.rb +5 -4
- data/lib/messaging.rb +1 -1
- data/lib/selfsdk.rb +1 -0
- data/lib/services/auth.rb +2 -4
- data/lib/services/identity.rb +3 -2
- data/lib/signature_graph.rb +283 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de6c98236b7c43869701b0d8d538e309c432104a7ff793dcff59f34b01615adf
|
|
4
|
+
data.tar.gz: da9ba6706eaf3ca113dd4c38a3b633a13850947c0cbfefe70ca1ce05b012e539
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fffb2a10a98443d6396d2c0a108522c403e1b625c51b088e470996632749fc2368ec74238d82418c15fbe6bf1cdae3d8e3e454767e314f4dfc1b9e25fe3a6b7
|
|
7
|
+
data.tar.gz: 64ff7184a0235b16164476259987df92bbfb60f25cfeb9751ff9a838cb7aaf83d7fe7fdc06e8ade67a6c70ed3eaf19b4b04c33e2bd548e95d2eb66b074b9f4ad
|
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
|
|
@@ -64,7 +71,9 @@ module SelfSDK
|
|
|
64
71
|
if verify_key.verify(decode(payload[:signature]), "#{payload[:protected]}.#{payload[:payload]}")
|
|
65
72
|
return true
|
|
66
73
|
end
|
|
67
|
-
|
|
74
|
+
false
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
SelfSDK.logger.info e
|
|
68
77
|
false
|
|
69
78
|
end
|
|
70
79
|
|
|
@@ -85,7 +94,7 @@ module SelfSDK
|
|
|
85
94
|
private
|
|
86
95
|
|
|
87
96
|
def header
|
|
88
|
-
encode({ alg: "EdDSA", typ: "JWT" }.to_json)
|
|
97
|
+
encode({ alg: "EdDSA", typ: "JWT", kid: "#{@key_id}" }.to_json)
|
|
89
98
|
end
|
|
90
99
|
end
|
|
91
100
|
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,14 +72,15 @@ 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!(
|
|
81
|
-
k = @client.
|
|
82
|
-
return if @jwt.verify(
|
|
81
|
+
def verify!(input, kid)
|
|
82
|
+
k = @client.public_key(@from, kid).raw_public_key
|
|
83
|
+
return if @jwt.verify(input, k)
|
|
83
84
|
|
|
84
85
|
SelfSDK.logger.info "skipping message, invalid signature"
|
|
85
86
|
raise ::StandardError.new("invalid signature on incoming message")
|
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
data/lib/services/auth.rb
CHANGED
|
@@ -115,6 +115,7 @@ module SelfSDK
|
|
|
115
115
|
def valid_payload(response)
|
|
116
116
|
parse_payload(response)
|
|
117
117
|
rescue StandardError => e
|
|
118
|
+
SelfSDK.logger.error e
|
|
118
119
|
uuid = ""
|
|
119
120
|
uuid = response[:cid] unless response.nil?
|
|
120
121
|
SelfSDK.logger.error "error checking authentication for #{uuid} : #{e.message}"
|
|
@@ -153,10 +154,7 @@ module SelfSDK
|
|
|
153
154
|
identity = @client.entity(payload[:sub])
|
|
154
155
|
return if identity.nil?
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
return payload if @client.jwt.verify(jws, key[:key])
|
|
158
|
-
end
|
|
159
|
-
nil
|
|
157
|
+
return payload
|
|
160
158
|
end
|
|
161
159
|
end
|
|
162
160
|
end
|
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 = action[:key]
|
|
68
|
+
@public_key = Ed25519::VerifyKey.new(Base64.urlsafe_decode64(@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.131
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aldgate Ventures
|
|
@@ -325,9 +325,11 @@ 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
|
-
licenses:
|
|
331
|
+
licenses:
|
|
332
|
+
- MIT
|
|
331
333
|
metadata: {}
|
|
332
334
|
post_install_message:
|
|
333
335
|
rdoc_options: []
|