tanker-identity 2.16.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 88d5a8e184f0271d9b431a9440eb7a358d9a3d7f4e0e88f5774b9c8601a74cb3
4
+ data.tar.gz: 501f29acdb04b8777fcff7fedabeebcb97d6be545b825958d31d985720c00d2c
5
+ SHA512:
6
+ metadata.gz: 62fe18c7feea78adbbb5d52aae664d3a4a806bd34dd04130e9ba20a49bf8acab96741d8525a8be50d213ee86d0b9e8d8532374df33a101db1c5cb05910a7ab8f
7
+ data.tar.gz: 227bcb9e1e49db803afa686eb511048929a04ad5cac2da25ce54a800cf4ca3b295b0c6fe149826c92a5c544f29ed5c2ea5e1a16db9162eb74a48039da9662c15
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2019 Kontrol SAS
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ <a href="#readme"><img src="https://tanker.io/images/github-logo.png" alt="Tanker logo" width="180" /></a>
2
+
3
+ # Identity SDK
4
+
5
+ [![Actions Status](https://github.com/TankerHQ/identity-ruby/workflows/Tests/badge.svg)](https://github.com/TankerHQ/identity-ruby/actions) [![codecov](https://codecov.io/gh/TankerHQ/identity-ruby/branch/master/graph/badge.svg)](https://codecov.io/gh/TankerHQ/identity-ruby)
6
+
7
+ Identity generation in Ruby for the [Tanker SDK](https://docs.tanker.io/latest/).
8
+
9
+ ## Requirements
10
+
11
+ This gem requires Ruby v2.5 or greater (transitive requirement from [rbnacl](https://github.com/crypto-rb/rbnacl)).
12
+
13
+ If still on an older version of Ruby, use `tanker-identity` with the [`603b35f8` commit ref](https://github.com/TankerHQ/identity-ruby/tree/603b35f8e1ca889c4862e8f9c1e54632a38b32b6).
14
+
15
+ ## Installation
16
+
17
+ This project depends on the [rbnacl](https://github.com/crypto-rb/rbnacl) gem, which requires the [libsodium](https://download.libsodium.org/doc/) cryptographic library.
18
+
19
+ Before going further, please follow [instructions to install libsodium](https://github.com/crypto-rb/rbnacl/wiki/Installing-libsodium).
20
+
21
+ Then, add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'tanker-identity', git: 'https://github.com/TankerHQ/identity-ruby' #, ref: '<commit>'
25
+ ```
26
+
27
+ Finally, execute:
28
+
29
+ ```shell
30
+ bundle
31
+ ```
32
+
33
+ ## API
34
+
35
+ ```ruby
36
+ Tanker::Identity.create_identity(app_id, app_secret, user_id)
37
+ ```
38
+ Create a new Tanker identity. This identity is secret and must only be given to a user who has been authenticated by your application. This identity is used by the Tanker client SDK to open a Tanker session.
39
+
40
+ **app_id**<br>
41
+ The app ID. You can access it from the [Tanker dashboard](https://dashboard.tanker.io).
42
+
43
+ **app_secret**<br>
44
+ The app secret. A secret that you have saved right after the creation of your app on the [Tanker dashboard](https://dashboard.tanker.io).
45
+
46
+ **user_id**<br>
47
+ The unique ID of a user in your application.
48
+ <br><br>
49
+
50
+ ```ruby
51
+ Tanker::Identity.create_provisional_identity(app_id, email)
52
+ ```
53
+ Create a Tanker provisional identity. It allows you to share a resource with a user who does not have an account in your application yet.
54
+
55
+ **app_id**<br>
56
+ The app ID. You can access it from the [Tanker dashboard](https://dashboard.tanker.io).
57
+
58
+ **email**<br>
59
+ The email of the potential recipient of the resource.
60
+ <br><br>
61
+
62
+ ```ruby
63
+ Tanker::Identity.get_public_identity(identity)
64
+ ```
65
+ Return the public identity from an identity. This public identity can be used by the Tanker client SDK to share encrypted resource.
66
+
67
+ **identity**<br>
68
+ A secret identity.
69
+ <br><br>
70
+
71
+ ## Usage example
72
+
73
+ The server-side pseudo-code below demonstrates a typical flow to safely deliver identities to your users:
74
+
75
+ ```ruby
76
+ require 'tanker-identity'
77
+
78
+ # 1. store these configurations in a safe place
79
+ app_id = '<app-id>'
80
+ app_secret = '<app-secret>'
81
+
82
+ # 2. you will typically have methods to check user authentication
83
+ def authenticated? # check user is authenticated on the server
84
+ def current_user # current authenticated user
85
+
86
+ # 3. you will need to add internal methods to store / load identities
87
+ def db_store_identity(user_id, identity)
88
+ def db_load_identity(user_id)
89
+
90
+ # 4. finally, add user facing functionality
91
+ def tanker_secret_identity(user_id)
92
+ raise 'Not authenticated' unless authenticated?
93
+ raise 'Not authorized' unless current_user.id == user_id
94
+
95
+ identity = db_load_identity(user_id)
96
+
97
+ if identity.nil?
98
+ identity = Tanker::Identity.create_identity(app_id, app_secret, user_id)
99
+ db_store_identity(user_id, identity)
100
+ end
101
+
102
+ identity
103
+ end
104
+
105
+ def tanker_public_identity(user_id)
106
+ raise 'Not authenticated' unless authenticated?
107
+
108
+ identity = db_load_identity(user_id)
109
+
110
+ raise 'User does not exist or has no identity yet' unless identity
111
+
112
+ Tanker::Identity.get_public_identity(identity)
113
+ end
114
+ ```
115
+
116
+ Read more about identities in the [Tanker documentation](https://docs.tanker.io/latest/).
117
+
118
+ Check the [examples](https://github.com/TankerHQ/identity-ruby/tree/master/examples/) folder for usage examples.
119
+
120
+ ## Development
121
+
122
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
+
124
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+
126
+ To audit the Gemfile.lock against the [advisory database](https://rubysec.com/), run `bundle exec bundle-audit check --update`.
127
+
128
+ ## Contributing
129
+
130
+ Bug reports and pull requests are welcome on GitHub at https://github.com/TankerHQ/identity-ruby.
131
+
132
+ [build-badge]: https://travis-ci.org/TankerHQ/identity-ruby.svg?branch=master
133
+ [build]: https://travis-ci.org/TankerHQ/identity-ruby
@@ -0,0 +1 @@
1
+ require 'tanker/identity'
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require 'rbnacl'
3
+ require 'securerandom'
4
+
5
+ module Tanker
6
+ module Crypto
7
+ class InvalidSignature < StandardError; end
8
+
9
+ HASH_MIN_SIZE = RbNaCl::Hash::Blake2b::BYTES_MIN # 16
10
+
11
+ def self.generichash(input, size)
12
+ binary_input = input.dup.force_encoding(Encoding::ASCII_8BIT)
13
+ RbNaCl::Hash.blake2b(binary_input, digest_size: size)
14
+ end
15
+
16
+ # We need this static method since a RbNaCl::SigningKey instance can't be
17
+ # directly initialized with a given private_signature_key
18
+ def self.sign_detached(message, private_signature_key)
19
+ signature_bytes = RbNaCl::SigningKey.signature_bytes
20
+ buffer = RbNaCl::Util.prepend_zeros(signature_bytes, message)
21
+ buffer_len = RbNaCl::Util.zeros(8) # 8 bytes for an int64 (FFI::Type::LONG_LONG.size)
22
+
23
+ RbNaCl::SigningKey.sign_ed25519(buffer, buffer_len, message, message.bytesize, private_signature_key)
24
+
25
+ buffer[0, signature_bytes]
26
+ end
27
+
28
+ def self.verify_sign_detached(message, signature, public_signature_key)
29
+ verify_key = RbNaCl::VerifyKey.new(public_signature_key)
30
+ verify_key.verify(signature, message)
31
+ rescue RbNaCl::BadSignatureError
32
+ raise InvalidSignature.new
33
+ end
34
+
35
+ def self.generate_signature_keypair
36
+ signing_key = RbNaCl::SigningKey.generate
37
+
38
+ {
39
+ private_key: signing_key.keypair_bytes,
40
+ public_key: signing_key.verify_key.to_bytes
41
+ }
42
+ end
43
+
44
+ def self.generate_encryption_keypair
45
+ encryption_key = RbNaCl::PrivateKey.generate
46
+
47
+ {
48
+ private_key: encryption_key.to_bytes,
49
+ public_key: encryption_key.public_key.to_bytes
50
+ }
51
+ end
52
+
53
+ def self.random_bytes(size)
54
+ SecureRandom.bytes(size)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,125 @@
1
+ require 'tanker/crypto'
2
+ require 'tanker/identity/version'
3
+ require 'base64'
4
+ require 'json'
5
+
6
+ module Tanker
7
+ module Identity
8
+ APP_CREATION_NATURE = 1
9
+ APP_SECRET_SIZE = 64
10
+ APP_PUBLIC_KEY_SIZE = 32
11
+ AUTHOR_SIZE = 32
12
+ BLOCK_HASH_SIZE = 32
13
+ USER_SECRET_SIZE = 32
14
+
15
+ private
16
+
17
+ def self.hash_user_id(app_id, user_id)
18
+ binary_user_id = user_id.dup.force_encoding(Encoding::ASCII_8BIT)
19
+ Crypto.generichash(binary_user_id + app_id, BLOCK_HASH_SIZE)
20
+ end
21
+
22
+ def self.user_secret(hashed_user_id)
23
+ random_bytes = Crypto.random_bytes(USER_SECRET_SIZE - 1)
24
+ check = Crypto.generichash(random_bytes + hashed_user_id, Crypto::HASH_MIN_SIZE)
25
+ random_bytes + check[0]
26
+ end
27
+
28
+ def self.assert_string_values(hash)
29
+ hash.each_pair do |key, value|
30
+ unless value.is_a?(String)
31
+ raise TypeError.new("expected #{key} to be a String but was a #{value.class}")
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.generate_app_id(app_secret)
37
+ block_nature = APP_CREATION_NATURE.chr(Encoding::ASCII_8BIT)
38
+ none_author = 0.chr(Encoding::ASCII_8BIT) * AUTHOR_SIZE
39
+ app_public_key = app_secret[-APP_PUBLIC_KEY_SIZE..-1]
40
+ Crypto.generichash(block_nature + none_author + app_public_key, BLOCK_HASH_SIZE)
41
+ end
42
+
43
+ public
44
+
45
+ def self.deserialize(b64_json)
46
+ JSON.parse(Base64.strict_decode64(b64_json))
47
+ end
48
+
49
+ def self.serialize(hash)
50
+ Base64.strict_encode64(JSON.generate(hash))
51
+ end
52
+
53
+ def self.create_identity(b64_app_id, b64_app_secret, user_id)
54
+ assert_string_values({
55
+ app_id: b64_app_id,
56
+ app_secret: b64_app_secret,
57
+ user_id: user_id
58
+ })
59
+
60
+ app_id = Base64.strict_decode64(b64_app_id)
61
+ app_secret = Base64.strict_decode64(b64_app_secret)
62
+
63
+ raise ArgumentError.new("Invalid app_id") if app_id.bytesize != BLOCK_HASH_SIZE
64
+ raise ArgumentError.new("Invalid app_secret") if app_secret.bytesize != APP_SECRET_SIZE
65
+ raise ArgumentError.new("Invalid (app_id, app_secret) combination") if app_id != generate_app_id(app_secret)
66
+
67
+ hashed_user_id = hash_user_id(app_id, user_id)
68
+ signature_keypair = Crypto.generate_signature_keypair
69
+ message = signature_keypair[:public_key] + hashed_user_id
70
+ signature = Crypto.sign_detached(message, app_secret)
71
+
72
+ serialize({
73
+ trustchain_id: Base64.strict_encode64(app_id),
74
+ target: 'user',
75
+ value: Base64.strict_encode64(hashed_user_id),
76
+ delegation_signature: Base64.strict_encode64(signature),
77
+ ephemeral_public_signature_key: Base64.strict_encode64(signature_keypair[:public_key]),
78
+ ephemeral_private_signature_key: Base64.strict_encode64(signature_keypair[:private_key]),
79
+ user_secret: Base64.strict_encode64(user_secret(hashed_user_id))
80
+ })
81
+ end
82
+
83
+ def self.create_provisional_identity(b64_app_id, email)
84
+ assert_string_values({
85
+ app_id: b64_app_id,
86
+ email: email
87
+ })
88
+
89
+ app_id = Base64.strict_decode64(b64_app_id)
90
+ raise ArgumentError.new("Invalid app_id") if app_id.bytesize != BLOCK_HASH_SIZE
91
+
92
+ encryption_keypair = Crypto.generate_encryption_keypair
93
+ signature_keypair = Crypto.generate_signature_keypair
94
+
95
+ serialize({
96
+ trustchain_id: b64_app_id,
97
+ target: 'email',
98
+ value: email,
99
+ public_encryption_key: Base64.strict_encode64(encryption_keypair[:public_key]),
100
+ private_encryption_key: Base64.strict_encode64(encryption_keypair[:private_key]),
101
+ public_signature_key: Base64.strict_encode64(signature_keypair[:public_key]),
102
+ private_signature_key: Base64.strict_encode64(signature_keypair[:private_key])
103
+ })
104
+ end
105
+
106
+ def self.get_public_identity(serialized_identity)
107
+ assert_string_values({ identity: serialized_identity })
108
+
109
+ identity = deserialize(serialized_identity)
110
+
111
+ if identity['target'] == 'user'
112
+ public_keys = ['trustchain_id', 'target', 'value']
113
+ else
114
+ public_keys = ['trustchain_id', 'target', 'value', 'public_encryption_key', 'public_signature_key']
115
+ end
116
+
117
+ public_identity = {}
118
+ public_keys.each { |key| public_identity[key] = identity.fetch(key) }
119
+
120
+ serialize(public_identity)
121
+ rescue KeyError # failed fetch
122
+ raise ArgumentError.new('Not a valid Tanker identity')
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,11 @@
1
+ module Tanker
2
+ module Identity
3
+ VERSION = '2.16.0.alpha.1'
4
+ end
5
+
6
+ def self.const_missing(name)
7
+ super unless name == :VERSION
8
+ warn "DEPRECATION WARNING: Using `Tanker::VERSION` is deprecated in favor of `Tanker::Identity::VERSION`"
9
+ Tanker::Identity::VERSION
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tanker-identity
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.16.0.alpha.1
5
+ platform: ruby
6
+ authors:
7
+ - Tanker Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbnacl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler-audit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubygems-tasks
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.5
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Building blocks to add Tanker identity management to your application
126
+ server
127
+ email:
128
+ - contact@tanker.io
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - LICENSE
134
+ - README.md
135
+ - lib/tanker-identity.rb
136
+ - lib/tanker/crypto.rb
137
+ - lib/tanker/identity.rb
138
+ - lib/tanker/identity/version.rb
139
+ homepage: https://tanker.io
140
+ licenses:
141
+ - Apache-2.0
142
+ metadata:
143
+ homepage_uri: https://tanker.io
144
+ source_code_uri: https://github.com/TankerHQ/identity-ruby
145
+ changelog_uri: https://docs.tanker.io/latest/release-notes/identity/ruby
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: 2.5.0
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">"
158
+ - !ruby/object:Gem::Version
159
+ version: 1.3.1
160
+ requirements: []
161
+ rubygems_version: 3.0.3
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Tanker identity management library packaged as a gem
165
+ test_files: []