tanker-core 2.4.0.alpha.1

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.
@@ -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