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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74ab84c18470d9115ee847d3c737d70ca987c946db78957b1fb26848840ee67c
4
- data.tar.gz: 36ff94d8ec89e4c02da41b0c53209c72c92aba3455907b0440a1026e5d710a91
3
+ metadata.gz: de6c98236b7c43869701b0d8d538e309c432104a7ff793dcff59f34b01615adf
4
+ data.tar.gz: da9ba6706eaf3ca113dd4c38a3b633a13850947c0cbfefe70ca1ce05b012e539
5
5
  SHA512:
6
- metadata.gz: 8b3abe26489b0043512bf2f3aa97286eca248551dcb5ce43cee54a5f4a6fc01b9361c684119ba18cce7ade6f9d6ca52dcd07abe87e65b88b740e8ffc41fbf262
7
- data.tar.gz: e193c7852ec51457f6f8235eccb42855e6bf37974dbc67c823bf4c53b12ac9d96caf5101d9d1ecc1f97a3cd148d7f64c2f65df12c39896792e1dd19fcbf89dab
6
+ metadata.gz: 9fffb2a10a98443d6396d2c0a108522c403e1b625c51b088e470996632749fc2368ec74238d82418c15fbe6bf1cdae3d8e3e454767e314f4dfc1b9e25fe3a6b7
7
+ data.tar.gz: 64ff7184a0235b16164476259987df92bbfb60f25cfeb9751ff9a838cb7aaf83d7fe7fdc06e8ade67a6c70ed3eaf19b4b04c33e2bd548e95d2eb66b074b9f4ad
@@ -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
@@ -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
- rescue StandardError
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
@@ -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,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!(jwt)
81
- k = @client.public_keys(@from).first[:key]
82
- return if @jwt.verify(jwt, k)
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")
@@ -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'
@@ -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
- identity[:public_keys].each do |key|
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
@@ -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 = 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.126
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: []