tanker-identity 2.16.0.alpha.1 → 3.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88d5a8e184f0271d9b431a9440eb7a358d9a3d7f4e0e88f5774b9c8601a74cb3
4
- data.tar.gz: 501f29acdb04b8777fcff7fedabeebcb97d6be545b825958d31d985720c00d2c
3
+ metadata.gz: 538f757e41dcface55b1025098af3e75bb52303547e5665ae8ad118f80d447eb
4
+ data.tar.gz: ab2746f68ae1ecc15e8c0900ec382b49a01508270dfc5e7217503221c19cb168
5
5
  SHA512:
6
- metadata.gz: 62fe18c7feea78adbbb5d52aae664d3a4a806bd34dd04130e9ba20a49bf8acab96741d8525a8be50d213ee86d0b9e8d8532374df33a101db1c5cb05910a7ab8f
7
- data.tar.gz: 227bcb9e1e49db803afa686eb511048929a04ad5cac2da25ce54a800cf4ca3b295b0c6fe149826c92a5c544f29ed5c2ea5e1a16db9162eb74a48039da9662c15
6
+ metadata.gz: 9b1cbeae3ffc99ab33c8f65602a1d40c72d31108a273e98e403652f95e5596cce27f778c33154372f77e764fbc879008c497969ab52f3cde19ece5139e97c57a
7
+ data.tar.gz: 76881380c77649b73eb2d9f3d68f28e21ecc6055da620155c20ad67e9181de55380be53b5f44b9eb60c22a6d4469cd123fa5faf53e92803f1639618544f2f010
data/README.md CHANGED
@@ -10,7 +10,7 @@ Identity generation in Ruby for the [Tanker SDK](https://docs.tanker.io/latest/)
10
10
 
11
11
  This gem requires Ruby v2.5 or greater (transitive requirement from [rbnacl](https://github.com/crypto-rb/rbnacl)).
12
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).
13
+ Older Ruby versions are not supported.
14
14
 
15
15
  ## Installation
16
16
 
@@ -21,7 +21,7 @@ Before going further, please follow [instructions to install libsodium](https://
21
21
  Then, add this line to your application's Gemfile:
22
22
 
23
23
  ```ruby
24
- gem 'tanker-identity', git: 'https://github.com/TankerHQ/identity-ruby' #, ref: '<commit>'
24
+ gem 'tanker-identity', 'X.Y.Z'
25
25
  ```
26
26
 
27
27
  Finally, execute:
@@ -48,7 +48,7 @@ The unique ID of a user in your application.
48
48
  <br><br>
49
49
 
50
50
  ```ruby
51
- Tanker::Identity.create_provisional_identity(app_id, email)
51
+ Tanker::Identity.create_provisional_identity(app_id, 'email', email)
52
52
  ```
53
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
54
 
data/lib/tanker/crypto.rb CHANGED
@@ -7,12 +7,27 @@ module Tanker
7
7
  class InvalidSignature < StandardError; end
8
8
 
9
9
  HASH_MIN_SIZE = RbNaCl::Hash::Blake2b::BYTES_MIN # 16
10
+ BLOCK_HASH_SIZE = 32
10
11
 
11
12
  def self.generichash(input, size)
12
13
  binary_input = input.dup.force_encoding(Encoding::ASCII_8BIT)
13
14
  RbNaCl::Hash.blake2b(binary_input, digest_size: size)
14
15
  end
15
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.hashed_provisional_email(email)
23
+ Base64.strict_encode64(Crypto.generichash(email, BLOCK_HASH_SIZE))
24
+ end
25
+
26
+ def self.hashed_provisional_value(value, private_signature_key)
27
+ secret_salt = Crypto.generichash(Base64.strict_decode64(private_signature_key), BLOCK_HASH_SIZE)
28
+ Base64.strict_encode64(Crypto.generichash(secret_salt + value, BLOCK_HASH_SIZE))
29
+ end
30
+
16
31
  # We need this static method since a RbNaCl::SigningKey instance can't be
17
32
  # directly initialized with a given private_signature_key
18
33
  def self.sign_detached(message, private_signature_key)
@@ -9,16 +9,10 @@ module Tanker
9
9
  APP_SECRET_SIZE = 64
10
10
  APP_PUBLIC_KEY_SIZE = 32
11
11
  AUTHOR_SIZE = 32
12
- BLOCK_HASH_SIZE = 32
13
12
  USER_SECRET_SIZE = 32
14
13
 
15
14
  private
16
15
 
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
16
  def self.user_secret(hashed_user_id)
23
17
  random_bytes = Crypto.random_bytes(USER_SECRET_SIZE - 1)
24
18
  check = Crypto.generichash(random_bytes + hashed_user_id, Crypto::HASH_MIN_SIZE)
@@ -27,9 +21,7 @@ module Tanker
27
21
 
28
22
  def self.assert_string_values(hash)
29
23
  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
24
+ raise TypeError, "expected #{key} to be a String but was a #{value.class}" unless value.is_a?(String)
33
25
  end
34
26
  end
35
27
 
@@ -37,7 +29,7 @@ module Tanker
37
29
  block_nature = APP_CREATION_NATURE.chr(Encoding::ASCII_8BIT)
38
30
  none_author = 0.chr(Encoding::ASCII_8BIT) * AUTHOR_SIZE
39
31
  app_public_key = app_secret[-APP_PUBLIC_KEY_SIZE..-1]
40
- Crypto.generichash(block_nature + none_author + app_public_key, BLOCK_HASH_SIZE)
32
+ Crypto.generichash(block_nature + none_author + app_public_key, Crypto::BLOCK_HASH_SIZE)
41
33
  end
42
34
 
43
35
  public
@@ -52,55 +44,61 @@ module Tanker
52
44
 
53
45
  def self.create_identity(b64_app_id, b64_app_secret, user_id)
54
46
  assert_string_values({
55
- app_id: b64_app_id,
56
- app_secret: b64_app_secret,
57
- user_id: user_id
58
- })
47
+ app_id: b64_app_id,
48
+ app_secret: b64_app_secret,
49
+ user_id: user_id
50
+ })
59
51
 
60
52
  app_id = Base64.strict_decode64(b64_app_id)
61
53
  app_secret = Base64.strict_decode64(b64_app_secret)
62
54
 
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)
55
+ raise ArgumentError, "Invalid app_id" if app_id.bytesize != Crypto::BLOCK_HASH_SIZE
56
+ raise ArgumentError, "Invalid app_secret" if app_secret.bytesize != APP_SECRET_SIZE
57
+ raise ArgumentError, "Invalid (app_id, app_secret) combination" if app_id != generate_app_id(app_secret)
66
58
 
67
- hashed_user_id = hash_user_id(app_id, user_id)
59
+ hashed_user_id = Crypto.hash_user_id(app_id, user_id)
68
60
  signature_keypair = Crypto.generate_signature_keypair
69
61
  message = signature_keypair[:public_key] + hashed_user_id
70
62
  signature = Crypto.sign_detached(message, app_secret)
71
63
 
72
64
  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
- })
65
+ trustchain_id: Base64.strict_encode64(app_id),
66
+ target: 'user',
67
+ value: Base64.strict_encode64(hashed_user_id),
68
+ delegation_signature: Base64.strict_encode64(signature),
69
+ ephemeral_public_signature_key: Base64.strict_encode64(signature_keypair[:public_key]),
70
+ ephemeral_private_signature_key: Base64.strict_encode64(signature_keypair[:private_key]),
71
+ user_secret: Base64.strict_encode64(user_secret(hashed_user_id))
72
+ })
81
73
  end
82
74
 
83
- def self.create_provisional_identity(b64_app_id, email)
75
+ def self.create_provisional_identity(b64_app_id, target, value)
84
76
  assert_string_values({
85
- app_id: b64_app_id,
86
- email: email
87
- })
77
+ app_id: b64_app_id,
78
+ target: target,
79
+ value: value
80
+ })
81
+
82
+ valid_targets = %w[email phone_number]
83
+ unless valid_targets.include? target
84
+ raise ArgumentError, "expected #{target} to be one of #{valid_targets.join(', ')}"
85
+ end
88
86
 
89
87
  app_id = Base64.strict_decode64(b64_app_id)
90
- raise ArgumentError.new("Invalid app_id") if app_id.bytesize != BLOCK_HASH_SIZE
88
+ raise ArgumentError, "Invalid app_id" if app_id.bytesize != Crypto::BLOCK_HASH_SIZE
91
89
 
92
90
  encryption_keypair = Crypto.generate_encryption_keypair
93
91
  signature_keypair = Crypto.generate_signature_keypair
94
92
 
95
93
  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
- })
94
+ trustchain_id: b64_app_id,
95
+ target: target,
96
+ value: value,
97
+ public_encryption_key: Base64.strict_encode64(encryption_keypair[:public_key]),
98
+ private_encryption_key: Base64.strict_encode64(encryption_keypair[:private_key]),
99
+ public_signature_key: Base64.strict_encode64(signature_keypair[:public_key]),
100
+ private_signature_key: Base64.strict_encode64(signature_keypair[:private_key])
101
+ })
104
102
  end
105
103
 
106
104
  def self.get_public_identity(serialized_identity)
@@ -117,9 +115,29 @@ module Tanker
117
115
  public_identity = {}
118
116
  public_keys.each { |key| public_identity[key] = identity.fetch(key) }
119
117
 
118
+ if identity['target'] == 'email'
119
+ public_identity['target'] = 'hashed_email'
120
+ public_identity['value'] = Crypto.hashed_provisional_email(public_identity['value'])
121
+ elsif identity['target'] != 'user'
122
+ public_identity['target'] = 'hashed_' + public_identity['target']
123
+ public_identity['value'] = Crypto.hashed_provisional_value(public_identity['value'],
124
+ identity['private_signature_key'])
125
+ end
126
+
120
127
  serialize(public_identity)
121
128
  rescue KeyError # failed fetch
122
- raise ArgumentError.new('Not a valid Tanker identity')
129
+ raise ArgumentError, 'Not a valid Tanker identity'
130
+ end
131
+
132
+ def self.upgrade_identity(serialized_identity)
133
+ assert_string_values({ identity: serialized_identity })
134
+
135
+ identity = deserialize(serialized_identity)
136
+ if identity['target'] == 'email' && !identity.key?('private_encryption_key')
137
+ identity['target'] = 'hashed_email'
138
+ identity['value'] = Crypto.hashed_provisional_email(identity['value'])
139
+ end
140
+ serialize(identity)
123
141
  end
124
142
  end
125
143
  end
@@ -1,6 +1,6 @@
1
1
  module Tanker
2
2
  module Identity
3
- VERSION = '2.16.0.alpha.1'
3
+ VERSION = '3.0.0'
4
4
  end
5
5
 
6
6
  def self.const_missing(name)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tanker-identity
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.16.0.alpha.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanker Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-21 00:00:00.000000000 Z
11
+ date: 2021-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbnacl
@@ -154,11 +154,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
154
154
  version: 2.5.0
155
155
  required_rubygems_version: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - ">"
157
+ - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: 1.3.1
159
+ version: '0'
160
160
  requirements: []
161
- rubygems_version: 3.0.3
161
+ rubygems_version: 3.1.6
162
162
  signing_key:
163
163
  specification_version: 4
164
164
  summary: Tanker identity management library packaged as a gem