selfsdk 0.0.124
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 +7 -0
- data/lib/acl.rb +52 -0
- data/lib/authenticated.rb +26 -0
- data/lib/client.rb +95 -0
- data/lib/jwt_service.rb +91 -0
- data/lib/log.rb +15 -0
- data/lib/messages/attestation.rb +53 -0
- data/lib/messages/authentication_message.rb +25 -0
- data/lib/messages/authentication_req.rb +52 -0
- data/lib/messages/authentication_resp.rb +23 -0
- data/lib/messages/base.rb +89 -0
- data/lib/messages/fact.rb +55 -0
- data/lib/messages/fact_request.rb +112 -0
- data/lib/messages/fact_response.rb +91 -0
- data/lib/messages/message.rb +39 -0
- data/lib/messaging.rb +441 -0
- data/lib/ntptime.rb +51 -0
- data/lib/proto/acl_pb.rb +19 -0
- data/lib/proto/aclcommand_pb.rb +16 -0
- data/lib/proto/auth_pb.rb +19 -0
- data/lib/proto/errtype_pb.rb +19 -0
- data/lib/proto/header_pb.rb +16 -0
- data/lib/proto/message_pb.rb +22 -0
- data/lib/proto/msgtype_pb.rb +18 -0
- data/lib/proto/notification_pb.rb +19 -0
- data/lib/selfsdk.rb +109 -0
- data/lib/services/auth.rb +163 -0
- data/lib/services/facts.rb +138 -0
- data/lib/services/identity.rb +44 -0
- data/lib/services/messaging.rb +100 -0
- data/lib/sources.rb +78 -0
- metadata +354 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: df6729e3320be7cc2abd4ea22a94ff757b8e8d72ee5e1f2b2ddbafed509483c4
|
4
|
+
data.tar.gz: c7202790ad7ac8c0fcb984a3c494983b08549e9e516b238cc247d869a630d2a2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 860606a05e95e553e6c1e36c63b15a1e3b93922d56e0a58a773d911da0ea29d95f5395406b13124b005e05c468d1c2740308e55908decffc77eb79d6c569ac14
|
7
|
+
data.tar.gz: 7a3ba36d29276730966cc01ce4d2730c0613142ce6d844deb61380a3b3055596e724bfe78160061436cf43f9f510b49b7407a3a9eb107e0e057c094f9d8e7f32
|
data/lib/acl.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
# Namespace for classes and modules that handle Self interactions.
|
6
|
+
module SelfSDK
|
7
|
+
# Access control list
|
8
|
+
class ACL
|
9
|
+
def initialize(messaging)
|
10
|
+
@messaging = messaging
|
11
|
+
@jwt = @messaging.jwt
|
12
|
+
end
|
13
|
+
|
14
|
+
# Lists allowed connections.
|
15
|
+
def list
|
16
|
+
SelfSDK.logger.info "Listing allowed connections"
|
17
|
+
rules = {}
|
18
|
+
@messaging.list_acl_rules.each do |c|
|
19
|
+
rules[c['acl_source']] = DateTime.parse(c['acl_exp'])
|
20
|
+
end
|
21
|
+
rules
|
22
|
+
end
|
23
|
+
|
24
|
+
# Allows incomming messages from the given identity.
|
25
|
+
def allow(id)
|
26
|
+
SelfSDK.logger.info "Allowing connections from #{id}"
|
27
|
+
@messaging.add_acl_rule(@jwt.prepare(jti: SecureRandom.uuid,
|
28
|
+
cid: SecureRandom.uuid,
|
29
|
+
typ: 'acl.permit',
|
30
|
+
iss: @jwt.id,
|
31
|
+
sub: @jwt.id,
|
32
|
+
iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
|
33
|
+
exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
|
34
|
+
acl_source: id,
|
35
|
+
acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Deny incomming messages from the given identity.
|
39
|
+
def deny(id)
|
40
|
+
SelfSDK.logger.info "Denying connections from #{id}"
|
41
|
+
@messaging.remove_acl_rule(@jwt.prepare(jti: SecureRandom.uuid,
|
42
|
+
cid: SecureRandom.uuid,
|
43
|
+
typ: 'acl.revoke',
|
44
|
+
iss: @jwt.id,
|
45
|
+
sub: @jwt.id,
|
46
|
+
iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
|
47
|
+
exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
|
48
|
+
acl_source: id,
|
49
|
+
acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SelfSDK
|
2
|
+
class Authenticated
|
3
|
+
attr_accessor :payload, :uuid, :selfsdk, :status
|
4
|
+
|
5
|
+
def initialize(payload)
|
6
|
+
return if payload.nil?
|
7
|
+
|
8
|
+
@payload = payload
|
9
|
+
@uuid = payload[:cid]
|
10
|
+
@selfsdk = payload[:sub]
|
11
|
+
end
|
12
|
+
|
13
|
+
def accepted?
|
14
|
+
return false if @payload.nil?
|
15
|
+
|
16
|
+
@payload[:status] == "accepted"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{ uuid: @uuid,
|
21
|
+
selfsdk: @selfsdk,
|
22
|
+
accepted: accepted? }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/client.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
|
5
|
+
module SelfSDK
|
6
|
+
class RestClient
|
7
|
+
attr_reader :self_url, :jwt, :env
|
8
|
+
|
9
|
+
# RestClient initializer
|
10
|
+
#
|
11
|
+
# @param url [string] self-messaging url
|
12
|
+
# @param token [string] jwt token identifying the authenticated user
|
13
|
+
def initialize(url, app_id, app_key, env)
|
14
|
+
SelfSDK.logger.info "client setup with #{url}"
|
15
|
+
@self_url = url
|
16
|
+
@env = env
|
17
|
+
@jwt = SelfSDK::JwtService.new(app_id, app_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get identity details
|
21
|
+
#
|
22
|
+
# @param id [string] identity self_id.
|
23
|
+
def identity(id)
|
24
|
+
get_identity "/v1/identities/#{id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get app details
|
28
|
+
#
|
29
|
+
# @param id [string] app self_id.
|
30
|
+
def app(id)
|
31
|
+
get_identity "/v1/apps/#{id}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get app/identity details
|
35
|
+
#
|
36
|
+
# @param id [string] app/identity self_id.
|
37
|
+
def entity(id)
|
38
|
+
#TODO : Consider a better check for this conditional
|
39
|
+
if id.length == 11
|
40
|
+
return identity(id)
|
41
|
+
else
|
42
|
+
return app(id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Lists all devices assigned to the given identity
|
47
|
+
#
|
48
|
+
# @param id [string] identity id
|
49
|
+
def devices(id)
|
50
|
+
res = get "/v1/identities/#{id}/devices"
|
51
|
+
body = JSON.parse(res.body, symbolize_names: true)
|
52
|
+
if res.code != 200
|
53
|
+
SelfSDK.logger.error "identity response : #{body[:message]}"
|
54
|
+
raise "you need connection permissions"
|
55
|
+
end
|
56
|
+
body
|
57
|
+
end
|
58
|
+
|
59
|
+
# Lists all public keys stored on self for the given ID
|
60
|
+
#
|
61
|
+
# @param id [string] identity id
|
62
|
+
def public_keys(id)
|
63
|
+
i = entity(id)
|
64
|
+
i[:public_keys]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def get_identity(endpoint)
|
70
|
+
res = get endpoint
|
71
|
+
body = JSON.parse(res.body, symbolize_names: true)
|
72
|
+
if res.code != 200
|
73
|
+
SelfSDK.logger.error "app response : #{body[:message]}"
|
74
|
+
raise body[:message]
|
75
|
+
end
|
76
|
+
body
|
77
|
+
end
|
78
|
+
|
79
|
+
def get(endpoint)
|
80
|
+
HTTParty.get("#{@self_url}#{endpoint}", headers: {
|
81
|
+
'Content-Type' => 'application/json',
|
82
|
+
'Authorization' => "Bearer #{@jwt.auth_token}"
|
83
|
+
})
|
84
|
+
end
|
85
|
+
|
86
|
+
def post(endpoint, body)
|
87
|
+
HTTParty.post("#{@self_url}#{endpoint}",
|
88
|
+
headers: {
|
89
|
+
'Content-Type' => 'application/json',
|
90
|
+
'Authorization' => "Bearer #{@jwt.auth_token}"
|
91
|
+
},
|
92
|
+
body: body)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/jwt_service.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module SelfSDK
|
7
|
+
class JwtService
|
8
|
+
attr_reader :id, :key
|
9
|
+
|
10
|
+
# Jwt initializer
|
11
|
+
#
|
12
|
+
# @param app_id [string] the app id.
|
13
|
+
# @param app_key [string] the app api key provided by developer portal.
|
14
|
+
def initialize(app_id, app_key)
|
15
|
+
@id = app_id
|
16
|
+
@key = app_key
|
17
|
+
end
|
18
|
+
|
19
|
+
# Prepares a jwt object based on an input
|
20
|
+
#
|
21
|
+
# @param input [string] input to be prepared
|
22
|
+
def prepare(input)
|
23
|
+
signed(input).to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def signed(input)
|
27
|
+
payload = encode(input.to_json)
|
28
|
+
{
|
29
|
+
payload: payload,
|
30
|
+
protected: header,
|
31
|
+
signature: sign("#{header}.#{payload}")
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(input)
|
36
|
+
JSON.parse(input, symbolize_names: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Encodes the input with base64
|
40
|
+
#
|
41
|
+
# @param input [string] the string to be encoded.
|
42
|
+
def encode(input)
|
43
|
+
Base64.urlsafe_encode64(input, padding: false)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Base64 decodes the input string
|
47
|
+
#
|
48
|
+
# @param input [string] the string to be decoded.
|
49
|
+
def decode(input)
|
50
|
+
Base64.urlsafe_decode64(input)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Signs the given input with the configured Ed25519 key.
|
54
|
+
#
|
55
|
+
# @param input [string] the string to be signed.
|
56
|
+
def sign(input)
|
57
|
+
signing_key = Ed25519::SigningKey.new(decode(@key))
|
58
|
+
signature = signing_key.sign(input)
|
59
|
+
encode(signature)
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify(payload, key)
|
63
|
+
verify_key = Ed25519::VerifyKey.new(decode(key))
|
64
|
+
if verify_key.verify(decode(payload[:signature]), "#{payload[:protected]}.#{payload[:payload]}")
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
rescue StandardError
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Generates the auth_token based on the app's private key.
|
72
|
+
def auth_token
|
73
|
+
payload = header + "." + encode({
|
74
|
+
jti: SecureRandom.uuid,
|
75
|
+
cid: SecureRandom.uuid,
|
76
|
+
typ: 'auth.token',
|
77
|
+
iat: (SelfSDK::Time.now - 5).to_i,
|
78
|
+
exp: (SelfSDK::Time.now + 60).to_i,
|
79
|
+
iss: @id,
|
80
|
+
sub: @id}.to_json)
|
81
|
+
signature = sign(payload)
|
82
|
+
"#{payload}.#{signature}"
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def header
|
88
|
+
encode({ alg: "EdDSA", typ: "JWT" }.to_json)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/log.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SelfSDK
|
4
|
+
module Messages
|
5
|
+
class Attestation
|
6
|
+
attr_accessor :verified, :origin, :source, :value, :operator, :expected_value, :fact_name, :to, :audience
|
7
|
+
|
8
|
+
def initialize(messaging)
|
9
|
+
@messaging = messaging
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(name, attestation)
|
13
|
+
payload = JSON.parse(@messaging.jwt.decode(attestation[:payload]), symbolize_names: true)
|
14
|
+
@origin = payload[:iss]
|
15
|
+
@to = payload[:sub]
|
16
|
+
@audience = payload[:aud]
|
17
|
+
@source = payload[:source]
|
18
|
+
@verified = valid_signature?(attestation)
|
19
|
+
@expected_value = payload[:expected_value]
|
20
|
+
@operator = payload[:operator]
|
21
|
+
@fact_name = name.to_s
|
22
|
+
unless payload[name].nil?
|
23
|
+
@value = payload[name]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_signature?(body)
|
28
|
+
k = @messaging.client.public_keys(@origin).first[:key]
|
29
|
+
raise ::StandardError.new("invalid signature") unless @messaging.jwt.verify(body, k)
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate!(original)
|
35
|
+
raise ::StandardError.new("invalid origin") if @to != original.to
|
36
|
+
end
|
37
|
+
|
38
|
+
def signed
|
39
|
+
o = {
|
40
|
+
sub: @to,
|
41
|
+
iss: @origin,
|
42
|
+
source: @source,
|
43
|
+
fact: @fact_name,
|
44
|
+
expected_value: @expected_value,
|
45
|
+
operator: @operator,
|
46
|
+
}
|
47
|
+
o[:aud] = @audience unless @audience.nil?
|
48
|
+
o[@fact_name.to_sym] = @value
|
49
|
+
@messaging.jwt.signed(o)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative '../ntptime'
|
5
|
+
|
6
|
+
module SelfSDK
|
7
|
+
module Messages
|
8
|
+
class AuthenticationMessage < Base
|
9
|
+
|
10
|
+
def parse(input, original=nil)
|
11
|
+
@input = input
|
12
|
+
@typ = @typ
|
13
|
+
@payload = get_payload input
|
14
|
+
@id = payload[:cid]
|
15
|
+
@from = payload[:iss]
|
16
|
+
@to = payload[:sub]
|
17
|
+
@audience = payload[:aud]
|
18
|
+
@from_device = payload[:device_id]
|
19
|
+
@expires = ::Time.parse(payload[:exp])
|
20
|
+
@issued = ::Time.parse(payload[:iat])
|
21
|
+
@status = payload[:status]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative '../ntptime'
|
5
|
+
require_relative 'authentication_message'
|
6
|
+
|
7
|
+
module SelfSDK
|
8
|
+
module Messages
|
9
|
+
class AuthenticationReq < AuthenticationMessage
|
10
|
+
MSG_TYPE = "identities.authenticate.req"
|
11
|
+
DEFAULT_EXP_TIMEOUT = 300
|
12
|
+
|
13
|
+
def initialize(messaging)
|
14
|
+
@typ = MSG_TYPE
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def populate(selfid, opts)
|
19
|
+
@id = SecureRandom.uuid
|
20
|
+
@from = @client.jwt.id
|
21
|
+
@to = selfid
|
22
|
+
|
23
|
+
@id = opts[:cid] if opts.include?(:cid)
|
24
|
+
@description = opts.include?(:description) ? opts[:description] : nil
|
25
|
+
@exp_timeout = opts.fetch(:exp_timeout, DEFAULT_EXP_TIMEOUT)
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
{ typ: MSG_TYPE,
|
30
|
+
iss: @jwt.id,
|
31
|
+
sub: @to,
|
32
|
+
aud: @to,
|
33
|
+
iat: SelfSDK::Time.now.strftime('%FT%TZ'),
|
34
|
+
exp: (SelfSDK::Time.now + @exp_timeout).strftime('%FT%TZ'),
|
35
|
+
cid: @id,
|
36
|
+
jti: SecureRandom.uuid }
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def proto
|
42
|
+
@to_device = @client.devices(@to).first
|
43
|
+
Msgproto::Message.new(type: Msgproto::MsgType::MSG,
|
44
|
+
sender: "#{@jwt.id}:#{@messaging.device_id}",
|
45
|
+
id: @id,
|
46
|
+
recipient: "#{@to}:#{@to_device}",
|
47
|
+
ciphertext: @jwt.prepare(body))
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative '../ntptime'
|
5
|
+
require_relative 'authentication_message'
|
6
|
+
|
7
|
+
module SelfSDK
|
8
|
+
module Messages
|
9
|
+
class AuthenticationResp < AuthenticationMessage
|
10
|
+
MSG_TYPE = "identities.authenticate.resp"
|
11
|
+
def initialize(messaging)
|
12
|
+
@typ = MSG_TYPE
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def proto
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|