tanker-core 2.4.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'tanker/core/verification_method'
5
+ require 'tanker/c_tanker/c_string'
6
+
7
+ module Tanker
8
+ module CTanker
9
+ class CVerificationMethod < FFI::Struct
10
+ layout :version, :uint8,
11
+ :type, :uint8,
12
+ :email, :pointer
13
+
14
+ TYPE_EMAIL = 1
15
+ TYPE_PASSPHRASE = 2
16
+ TYPE_VERIFICATION_KEY = 3
17
+ TYPE_OIDC_ID_TOKEN = 4
18
+
19
+ def to_verification_method
20
+ case self[:type]
21
+ when TYPE_EMAIL
22
+ EmailVerificationMethod.new(self[:email].read_string.force_encoding(Encoding::UTF_8))
23
+ when TYPE_PASSPHRASE
24
+ PassphraseVerificationMethod.new
25
+ when TYPE_VERIFICATION_KEY
26
+ VerificationKeyVerificationMethod.new
27
+ when TYPE_OIDC_ID_TOKEN
28
+ OIDCIDTokenVerificationMethod.new
29
+ else
30
+ raise "Unknown VerificationMethod type #{self[:type]}!"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ # always keep this file first, it defines the module and the class
5
+ require_relative 'core/version'
6
+ require_relative 'c_tanker'
7
+ require_relative 'error'
8
+ require_relative 'core/session'
9
+ require_relative 'core/verification'
10
+ require_relative 'core/encryption'
11
+ require_relative 'core/stream'
12
+ require_relative 'sharing_options'
13
+ require_relative 'core/group'
14
+ require_relative 'core/encryption_session'
15
+ require_relative 'core/log_record'
16
+ require_relative 'core/attach_result'
17
+ require_relative 'core/init'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tanker::Core
4
+ class AttachResult
5
+ attr_reader :status, :verification_method
6
+
7
+ def initialize(status, verification_method)
8
+ @status = status
9
+ @verification_method = verification_method
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tanker/c_tanker'
4
+ require_relative 'encryption_session'
5
+
6
+ module Tanker
7
+ class Core
8
+ def encrypt_data(data, encryption_options = nil)
9
+ unless data.is_a?(String)
10
+ raise TypeError, "expected data to be an ASCII-8BIT binary String, but got a #{data.class}"
11
+ end
12
+ unless data.encoding == Encoding::ASCII_8BIT
13
+ raise ArgumentError, "expected data to be an ASCII-8BIT binary String, but it was #{data.encoding} encoded"
14
+ end
15
+
16
+ encrypt_common data, encryption_options
17
+ end
18
+
19
+ def encrypt_utf8(str, encryption_options = nil)
20
+ ASSERT_UTF8.call(str)
21
+
22
+ encrypt_common str, encryption_options
23
+ end
24
+
25
+ def decrypt_data(data)
26
+ inbuf = FFI::MemoryPointer.from_string(data)
27
+
28
+ decrypted_size = CTanker.tanker_decrypted_size(inbuf, data.bytesize).get.address
29
+ outbuf = FFI::MemoryPointer.new(:char, decrypted_size)
30
+
31
+ CTanker.tanker_decrypt(@ctanker, outbuf, inbuf, data.bytesize).get
32
+
33
+ outbuf.read_string decrypted_size
34
+ end
35
+
36
+ def decrypt_utf8(data)
37
+ decrypted = decrypt_data data
38
+ decrypted.force_encoding(Encoding::UTF_8)
39
+ end
40
+
41
+ def get_resource_id(data)
42
+ unless data.is_a?(String)
43
+ raise TypeError, "expected data to be an ASCII-8BIT binary String, but got a #{data.class}"
44
+ end
45
+ unless data.encoding == Encoding::ASCII_8BIT
46
+ raise ArgumentError, "expected data to be an ASCII-8BIT binary String, but it was #{data.encoding} encoded"
47
+ end
48
+
49
+ inbuf = FFI::MemoryPointer.from_string(data)
50
+ CTanker.tanker_get_resource_id(inbuf, data.bytesize).get_string
51
+ end
52
+
53
+ def share(resource_ids, sharing_options)
54
+ unless resource_ids.is_a?(Array)
55
+ raise TypeError, "expected resource_ids to be an array of strings, but got a #{resource_ids.class}"
56
+ end
57
+ unless sharing_options.is_a?(SharingOptions)
58
+ raise TypeError, "expected sharing_options to be a SharingOptions, but got a #{sharing_options.class}"
59
+ end
60
+
61
+ cresource_ids = CTanker.new_cstring_array resource_ids
62
+ cusers = sharing_options[:recipient_public_identities]
63
+ nb_cusers = sharing_options[:nb_recipient_public_identities]
64
+ cgroups = sharing_options[:recipient_group_ids]
65
+ nb_cgroups = sharing_options[:nb_recipient_group_ids]
66
+
67
+ CTanker.tanker_share(@ctanker, cusers, nb_cusers,
68
+ cgroups, nb_cgroups,
69
+ cresource_ids, resource_ids.length).get
70
+ end
71
+
72
+ def create_encryption_session(sharing_options = nil)
73
+ if sharing_options.nil?
74
+ cusers = nil
75
+ nb_cusers = 0
76
+ cgroups = nil
77
+ nb_cgroups = 0
78
+ else
79
+ cusers = sharing_options[:recipient_public_identities]
80
+ nb_cusers = sharing_options[:nb_recipient_public_identities]
81
+ cgroups = sharing_options[:recipient_group_ids]
82
+ nb_cgroups = sharing_options[:nb_recipient_group_ids]
83
+ end
84
+
85
+ csession = CTanker.tanker_encryption_session_open(@ctanker, cusers, nb_cusers,
86
+ cgroups, nb_cgroups).get
87
+ EncryptionSession.new(csession)
88
+ end
89
+
90
+ def self.prehash_password(str)
91
+ ASSERT_UTF8.call(str)
92
+
93
+ CTanker.tanker_prehash_password(str).get_string
94
+ end
95
+
96
+ private
97
+
98
+ def encrypt_common(data, encryption_options = nil)
99
+ inbuf = FFI::MemoryPointer.from_string(data)
100
+
101
+ encrypted_size = CTanker.tanker_encrypted_size data.bytesize
102
+ outbuf = FFI::MemoryPointer.new(:char, encrypted_size)
103
+
104
+ CTanker.tanker_encrypt(@ctanker, outbuf, inbuf, data.bytesize, encryption_options).get
105
+
106
+ outbuf.read_string encrypted_size
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tanker/c_tanker'
4
+ require 'tanker/core/stream'
5
+
6
+ module Tanker
7
+ class Core::EncryptionSession
8
+ def initialize(csession)
9
+ @csession = csession
10
+ csession_addr = @csession.address
11
+ ObjectSpace.define_finalizer(@csession) do |_|
12
+ CTanker.tanker_encryption_session_close(FFI::Pointer.new(:void, csession_addr)).get
13
+ end
14
+ end
15
+
16
+ def encrypt_data(data)
17
+ unless data.is_a?(String)
18
+ raise TypeError, "expected data to be an ASCII-8BIT binary String, but got a #{data.class}"
19
+ end
20
+ unless data.encoding == Encoding::ASCII_8BIT
21
+ raise ArgumentError, "expected data to be an ASCII-8BIT binary String, but it was #{data.encoding} encoded"
22
+ end
23
+
24
+ encrypt_common(data)
25
+ end
26
+
27
+ def encrypt_utf8(str)
28
+ ASSERT_UTF8.call(str)
29
+
30
+ encrypt_common str
31
+ end
32
+
33
+ def encrypt_common(data)
34
+ inbuf = FFI::MemoryPointer.from_string(data)
35
+
36
+ encrypted_size = CTanker.tanker_encryption_session_encrypted_size data.bytesize
37
+ outbuf = FFI::MemoryPointer.new(:char, encrypted_size)
38
+
39
+ CTanker.tanker_encryption_session_encrypt(@csession, outbuf, inbuf, data.bytesize).get
40
+
41
+ outbuf.read_string encrypted_size
42
+ end
43
+
44
+ def encrypt_stream(stream)
45
+ Stream.do_stream_action(stream) { |cb| CTanker.tanker_encryption_session_stream_encrypt(@csession, cb, nil) }
46
+ end
47
+
48
+ def resource_id
49
+ CTanker.tanker_encryption_session_get_resource_id(@csession).get_string
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tanker/c_tanker'
4
+
5
+ module Tanker
6
+ class Core
7
+ def create_group(member_identities)
8
+ cmember_identities = CTanker.new_cstring_array member_identities
9
+ CTanker.tanker_create_group(@ctanker, cmember_identities, member_identities.length).get_string
10
+ end
11
+
12
+ def update_group_members(group_id, users_to_add:)
13
+ cidentities = CTanker.new_cstring_array users_to_add
14
+ CTanker.tanker_update_group_members(@ctanker, group_id, cidentities, users_to_add.length).get
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tanker
4
+ # Main entry point for the Tanker SDK. Can open a Tanker session.
5
+ class Core
6
+ CTanker.tanker_init
7
+
8
+ def initialize(options)
9
+ @revoke_event_handlers = Set.new
10
+ @ctanker = CTanker.tanker_create(options).get
11
+ ctanker_addr = @ctanker.address
12
+ ObjectSpace.define_finalizer(@ctanker) do |_|
13
+ CTanker.tanker_destroy(FFI::Pointer.new(:void, ctanker_addr)).get
14
+ end
15
+
16
+ @device_revoked_handler = ->(_) { @revoke_event_handlers.each(&:call) }
17
+ CTanker.tanker_event_connect(@ctanker, CTanker::CTankerEvent::DEVICE_REVOKED, @device_revoked_handler, nil).get
18
+ end
19
+
20
+ def self.set_log_handler(&block) # rubocop:disable Naming/AccessorMethodName
21
+ @log_handler = lambda do |clog|
22
+ block.call LogRecord.new clog[:category], clog[:level], clog[:file], clog[:line], clog[:message]
23
+ end
24
+ CTanker.tanker_set_log_handler @log_handler
25
+ end
26
+
27
+ def connect_device_revoked_handler(&block)
28
+ @revoke_event_handlers.add block
29
+ end
30
+
31
+ def disconnect_handler(&block)
32
+ @revoke_event_handlers.delete block
33
+ end
34
+ end
35
+
36
+ ASSERT_UTF8 = lambda do |str|
37
+ raise TypeError, "expected a String, but got a #{str.class}" unless str.is_a?(String)
38
+
39
+ encoding = str.encoding # Workaround rubocop bug
40
+ raise ArgumentError, "expected an UTF-8 String, but it was #{encoding} encoded" unless encoding == Encoding::UTF_8
41
+ end
42
+ private_constant :ASSERT_UTF8
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tanker::Core
4
+ class LogRecord
5
+ attr_reader :category, :level, :file, :line, :message
6
+
7
+ LEVEL_DEBUG = 1
8
+ LEVEL_INFO = 2
9
+ LEVEL_WARNING = 3
10
+ LEVEL_ERROR = 4
11
+
12
+ def initialize(category, level, file, line, message)
13
+ @category = category
14
+ @level = level
15
+ @file = file
16
+ @line = line
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'tanker/c_tanker/c_string'
5
+
6
+ module Tanker
7
+ # Options that can be given when opening a Tanker session
8
+ class Core::Options < FFI::Struct
9
+ layout :version, :uint8,
10
+ :app_id, :pointer,
11
+ :url, :pointer,
12
+ :writable_path, :pointer,
13
+ :sdk_type, :pointer,
14
+ :sdk_version, :pointer
15
+
16
+ SDK_TYPE = CTanker.new_cstring 'client-ruby'
17
+ SDK_VERSION = CTanker.new_cstring Core::VERSION
18
+
19
+ def initialize(app_id:, url: nil, writable_path: nil)
20
+ # Note: Instance variables are required to keep the CStrings alive
21
+ @app_id = CTanker.new_cstring app_id
22
+ @url = CTanker.new_cstring url
23
+ @writable_path = CTanker.new_cstring writable_path
24
+
25
+ self[:version] = 2
26
+ self[:app_id] = @app_id
27
+ self[:url] = @url
28
+ self[:writable_path] = @writable_path
29
+ self[:sdk_type] = SDK_TYPE
30
+ self[:sdk_version] = SDK_VERSION
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tanker/c_tanker'
4
+ require_relative 'status'
5
+
6
+ module Tanker
7
+ class Core
8
+ def start(identity)
9
+ CTanker.tanker_start(@ctanker, identity).get.address
10
+ end
11
+
12
+ def generate_verification_key
13
+ CTanker.tanker_generate_verification_key(@ctanker).get_string
14
+ end
15
+
16
+ def register_identity(verification)
17
+ cverif = CTanker::CVerification.new(verification)
18
+ CTanker.tanker_register_identity(@ctanker, cverif).get
19
+ end
20
+
21
+ def verify_identity(verification)
22
+ cverif = CTanker::CVerification.new(verification)
23
+ CTanker.tanker_verify_identity(@ctanker, cverif).get
24
+ end
25
+
26
+ def set_verification_method(verification) # rubocop:disable Naming/AccessorMethodName (this is not a setter)
27
+ cverif = CTanker::CVerification.new(verification)
28
+ CTanker.tanker_set_verification_method(@ctanker, cverif).get
29
+ end
30
+
31
+ def get_verification_methods # rubocop:disable Naming/AccessorMethodName
32
+ method_list_ptr = CTanker.tanker_get_verification_methods(@ctanker).get
33
+ count = method_list_ptr.get(:uint32, FFI::Pointer.size)
34
+
35
+ method_base_addr = method_list_ptr.read_pointer
36
+ method_list = count.times.map do |i|
37
+ method_ptr = method_base_addr + i * CTanker::CVerificationMethod.size
38
+ CTanker::CVerificationMethod.new(method_ptr).to_verification_method
39
+ end
40
+ CTanker.tanker_free_verification_method_list method_list_ptr
41
+ method_list
42
+ end
43
+
44
+ def device_id
45
+ CTanker.tanker_device_id(@ctanker).get_string
46
+ end
47
+
48
+ def device_list
49
+ device_list_ptr = CTanker.tanker_get_device_list(@ctanker).get
50
+ count = device_list_ptr.get(:uint32, FFI::Pointer.size)
51
+
52
+ method_base_addr = device_list_ptr.read_pointer
53
+ device_info_list = count.times.map do |i|
54
+ method_ptr = method_base_addr + i * CTanker::CDeviceInfo.size
55
+ CTanker::CDeviceInfo.new(method_ptr)
56
+ end
57
+ CTanker.tanker_free_device_list device_list_ptr
58
+ device_info_list
59
+ end
60
+
61
+ def revoke_device(device_id)
62
+ CTanker.tanker_revoke_device(@ctanker, device_id).get
63
+ end
64
+
65
+ def stop
66
+ CTanker.tanker_stop(@ctanker).get
67
+ end
68
+
69
+ def status
70
+ CTanker.tanker_status(@ctanker)
71
+ end
72
+
73
+ def attach_provisional_identity(provisional_identity)
74
+ attach_ptr = CTanker.tanker_attach_provisional_identity(@ctanker, provisional_identity).get
75
+ attach_status = attach_ptr.get(:uint8, 1)
76
+ method_ptr = attach_ptr.get_pointer(FFI::Pointer.size)
77
+ method = CTanker::CVerificationMethod.new(method_ptr).to_verification_method
78
+ AttachResult.new attach_status, method
79
+ end
80
+
81
+ def verify_provisional_identity(verification)
82
+ cverif = CTanker::CVerification.new(verification)
83
+ CTanker.tanker_verify_provisional_identity(@ctanker, cverif).get
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Status of a Tanker session
4
+ module Tanker::Status
5
+ STOPPED = 0
6
+ READY = 1
7
+ IDENTITY_REGISTRATION_NEEDED = 2
8
+ IDENTITY_VERIFICATION_NEEDED = 3
9
+ end