selfsdk 0.0.124

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module SelfSDK
6
+ class << self
7
+ attr_writer :logger
8
+
9
+ def logger
10
+ @logger ||= ::Logger.new($stdout).tap do |log|
11
+ log.progname = name
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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