tanker-identity 2.16.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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: []