Skip to content

Commit c53fdbd

Browse files
committed
feat(E2EE-2407): Add support of PrehashedAndEncryptedPassphrase in Enroll API
1 parent 6205469 commit c53fdbd

10 files changed

Lines changed: 157 additions & 3 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ spec/reports/
1616
/vendor
1717
/build
1818
/conan
19+
.tool-versions
20+
TAGS

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"rubyLsp.bypassTypechecker": true
3+
}

lib/tanker/c_tanker.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ module CTanker
9090
blocking_attach_function :tanker_http_handle_response, [CHttpRequest, CHttpResponse], :void
9191

9292
blocking_attach_function :tanker_prehash_password, [:string], CFuture
93+
blocking_attach_function :tanker_prehash_and_encrypt_password, [:string, :string], CFuture
9394

9495
blocking_attach_function :tanker_authenticate_with_idp, [:session_pointer, :string, :string], CFuture
9596
blocking_attach_function :tanker_free_authenticate_with_idp_result, [:pointer], :void

lib/tanker/c_tanker/c_verification.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ class CVerification < FFI::Struct
9393
:preverified_email, :pointer,
9494
:preverified_phone_number, :pointer,
9595
:preverified_oidc, COIDCVerification,
96-
:oidc_authorization_code_verification, COIDCAuthorizationCodeVerification
96+
:oidc_authorization_code_verification, COIDCAuthorizationCodeVerification,
97+
:prehashed_and_encrypted_passphrase, :pointer
9798

9899
TYPE_EMAIL = 1
99100
TYPE_PASSPHRASE = 2
@@ -105,6 +106,7 @@ class CVerification < FFI::Struct
105106
TYPE_E2E_PASSPHRASE = 8
106107
TYPE_PREVERIFIED_OIDC = 9
107108
TYPE_OIDC_AUTHORIZATION_CODE = 10
109+
TYPE_PREHASHED_AND_ENCRYPTED_PASSPHRASE = 11
108110

109111
def initialize(verification) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity Not relevant for a case/when
110112
super()
@@ -157,11 +159,15 @@ def initialize(verification) # rubocop:disable Metrics/MethodLength, Metrics/Cyc
157159
verification.authorization_code,
158160
verification.state
159161
)
162+
when Tanker::PrehashedAndEncryptedPassphraseVerification
163+
@prehashed_and_encrypted_passphrase = CTanker.new_cstring verification.prehashed_and_encrypted_passphrase
164+
self[:type] = TYPE_PREHASHED_AND_ENCRYPTED_PASSPHRASE
165+
self[:prehashed_and_encrypted_passphrase] = @prehashed_and_encrypted_passphrase
160166
else
161167
raise ArgumentError, 'Unknown Tanker::Verification type!'
162168
end
163169

164-
self[:version] = 8
170+
self[:version] = 9
165171
end
166172
end
167173

lib/tanker/core/encryption.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ def self.prehash_password(str)
7979
CTanker.tanker_prehash_password(str).get_string
8080
end
8181

82+
def self.prehash_and_encrypt_password(password, public_key)
83+
ASSERT_UTF8.call(password)
84+
ASSERT_UTF8.call(public_key)
85+
86+
CTanker.tanker_prehash_and_encrypt_password(password, public_key).get_string
87+
end
88+
8289
private
8390

8491
def encrypt_common(data, encryption_options)

lib/tanker/core/verification.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,16 @@ def initialize(e2e_passphrase)
139139
@e2e_passphrase = e2e_passphrase
140140
end
141141
end
142+
143+
class PrehashedAndEncryptedPassphraseVerification < Verification
144+
attr_reader :prehashed_and_encrypted_passphrase
145+
146+
def initialize(prehashed_and_encrypted_passphrase)
147+
super()
148+
149+
ASSERT_UTF8.call(prehashed_and_encrypted_passphrase)
150+
151+
@prehashed_and_encrypted_passphrase = prehashed_and_encrypted_passphrase
152+
end
153+
end
142154
end

lib/tanker/core/verification_method.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,6 @@ def ==(other)
7878
end
7979

8080
class E2ePassphraseVerificationMethod < VerificationMethod; end
81+
82+
class PrehashedAndEncryptedPassphraseVerificationMethod < VerificationMethod; end
8183
end

spec/admin_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class AppConfig
3030
attr_reader :oidc_config
3131
attr_reader :trustchain_url
3232
attr_reader :verification_api_token
33+
attr_reader :enroll_passphrase_public_encryption_key
3334

3435
def self.safe_get_env(var)
3536
val = ENV.fetch(var, nil)
@@ -62,6 +63,7 @@ def initialize
6263
}
6364
}
6465
@oidc_config = OIDCConfig.new client_id, client_secret, issuer, provider_name, fake_oidc_issuer_url, users
66+
@enroll_passphrase_public_encryption_key = AppConfig.safe_get_env('TANKER_ENROLL_PASSPHRASE_PUBLIC_ENCRYPTION_KEY')
6567
end
6668
end
6769

spec/core_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,54 @@
279279
expect(Tanker::Core.prehash_password(input)).to eq expected
280280
end
281281

282+
it 'fails to prehash and encrypt password when password is an empty string' do
283+
password = ''
284+
public_key = 'XJTPOJqdKJLCGwimhANxqrtiC2BOTmJUjJG7l4s5UhY='
285+
expect { Tanker::Core.prehash_and_encrypt_password(password, public_key) }.to(raise_error) do |e|
286+
expect(e).to be_a(Tanker::Error)
287+
expect(e).to be_a(Tanker::Error::InvalidArgument)
288+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
289+
end
290+
end
291+
292+
it 'fails to prehash and encrypt password when public key is an empty string' do
293+
password = 'Happy birthday ! 🥳'
294+
public_key = ''
295+
expect { Tanker::Core.prehash_and_encrypt_password(password, public_key) }.to(raise_error) do |e|
296+
expect(e).to be_a(Tanker::Error)
297+
expect(e).to be_a(Tanker::Error::InvalidArgument)
298+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
299+
end
300+
end
301+
302+
it 'fails to prehash and encrypt password when public key is not a base64-encoded string' do
303+
password = 'Happy birthday ! 🥳'
304+
public_key = '🎂'
305+
expect { Tanker::Core.prehash_and_encrypt_password(password, public_key) }.to(raise_error) do |e|
306+
expect(e).to be_a(Tanker::Error)
307+
expect(e).to be_a(Tanker::Error::InvalidArgument)
308+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
309+
end
310+
end
311+
312+
it 'fails to prehash and encrypt password when public key is invalid' do
313+
password = 'Happy birthday ! 🥳'
314+
public_key = 'fake'
315+
expect { Tanker::Core.prehash_and_encrypt_password(password, public_key) }.to(raise_error) do |e|
316+
expect(e).to be_a(Tanker::Error)
317+
expect(e).to be_a(Tanker::Error::InvalidArgument)
318+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
319+
end
320+
end
321+
322+
it 'can prehash and encrypt password' do
323+
password = 'Happy birthday ! 🥳'
324+
public_key = 'XJTPOJqdKJLCGwimhANxqrtiC2BOTmJUjJG7l4s5UhY='
325+
paep = Tanker::Core.prehash_and_encrypt_password(password, public_key)
326+
expect(paep).to be_kind_of String
327+
expect(paep.length).to be > 0
328+
end
329+
282330
it 'can share with a provisional user' do
283331
message = 'No plugin updates available'
284332
alice = Tanker::Core.new @options

spec/verification_spec.rb

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def extract_subject(id_token)
2828
jbody['sub']
2929
end
3030

31+
def create_paep_verification(passphrase)
32+
public_key = AppConfig.instance.enroll_passphrase_public_encryption_key
33+
paep = Tanker::Core.prehash_and_encrypt_password(passphrase, public_key)
34+
Tanker::PrehashedAndEncryptedPassphraseVerification.new paep
35+
end
36+
3137
RSpec.describe "#{Tanker} Verification" do
3238
before(:all) do
3339
Tanker::App.use_test_log_handler
@@ -320,6 +326,22 @@ def extract_subject(id_token)
320326
tanker.free
321327
end
322328

329+
it 'fails to register with a prehashed and encrypted passphrase' do
330+
tanker = Tanker::Core.new @options
331+
tanker.start @identity
332+
333+
paep_verification = create_paep_verification('p@assword')
334+
335+
expect { tanker.register_identity paep_verification }
336+
.to(raise_error) do |e|
337+
expect(e).to be_a(Tanker::Error)
338+
expect(e).to be_a(Tanker::Error::InvalidArgument)
339+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
340+
end
341+
342+
tanker.free
343+
end
344+
323345
it 'fails to verify with preverified email' do
324346
email = 'mono@chromat.ic'
325347

@@ -387,6 +409,28 @@ def extract_subject(id_token)
387409
tanker2.free
388410
end
389411

412+
it 'fails to verify with a prehashed and encrypted passphrase' do
413+
passphrase = 'p@ssword'
414+
tanker1 = Tanker::Core.new @options
415+
tanker1.start @identity
416+
tanker1.register_identity Tanker::PassphraseVerification.new(passphrase)
417+
tanker1.free
418+
419+
tanker2 = Tanker::Core.new @options
420+
tanker2.start @identity
421+
422+
paep_verification = create_paep_verification(passphrase)
423+
424+
expect { tanker2.verify_identity paep_verification }
425+
.to(raise_error) do |e|
426+
expect(e).to be_a(Tanker::Error)
427+
expect(e).to be_a(Tanker::Error::InvalidArgument)
428+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
429+
end
430+
431+
tanker2.free
432+
end
433+
390434
it 'sets verification method with preverified email' do
391435
email = 'mono@chromat.ic'
392436

@@ -454,6 +498,24 @@ def extract_subject(id_token)
454498
expect(tanker2.status).to eq(Tanker::Status::READY)
455499
tanker2.free
456500
end
501+
502+
it 'fails to set verification method to prehashed and encrypted passphrase' do
503+
passphrase = '42 is the Answer to the Ultimate Question of Life, the Universe, and Everything'
504+
tanker = Tanker::Core.new @options
505+
tanker.start @identity
506+
tanker.register_identity Tanker::PassphraseVerification.new passphrase
507+
508+
paep_verification = create_paep_verification(passphrase)
509+
510+
expect { tanker.set_verification_method paep_verification }
511+
.to(raise_error) do |e|
512+
expect(e).to be_a(Tanker::Error)
513+
expect(e).to be_a(Tanker::Error::InvalidArgument)
514+
expect(e.code).to eq(Tanker::Error::INVALID_ARGUMENT)
515+
end
516+
517+
tanker.free
518+
end
457519
end
458520

459521
describe 'session tokens' do
@@ -564,10 +626,12 @@ def extract_subject(id_token)
564626

565627
phone_number = '+33639982233'
566628
email = 'mono@chromat.ic'
629+
passphrase = 'Penny Lane is in my ears and in my eyes'
567630
@server.enroll_user(@identity, [
568631
Tanker::PreverifiedPhoneNumberVerification.new(phone_number),
569632
Tanker::PreverifiedEmailVerification.new(email),
570-
Tanker::PreverifiedOIDCVerification.new(@martine_subject, provider_id)
633+
Tanker::PreverifiedOIDCVerification.new(@martine_subject, provider_id),
634+
create_paep_verification(passphrase)
571635
])
572636

573637
tanker1 = Tanker::Core.new @options
@@ -576,6 +640,8 @@ def extract_subject(id_token)
576640
tanker2.start @identity
577641
tanker3 = Tanker::Core.new @options
578642
tanker3.start @identity
643+
tanker4 = Tanker::Core.new @options
644+
tanker4.start @identity
579645

580646
expect(tanker1.status).to eq(Tanker::Status::IDENTITY_VERIFICATION_NEEDED)
581647
tanker1.verify_identity Tanker::EmailVerification.new(
@@ -595,9 +661,14 @@ def extract_subject(id_token)
595661
tanker3.verify_identity Tanker::OIDCIDTokenVerification.new(@martine_id_token)
596662
expect(tanker3.status).to eq(Tanker::Status::READY)
597663

664+
expect(tanker4.status).to eq(Tanker::Status::IDENTITY_VERIFICATION_NEEDED)
665+
tanker4.verify_identity Tanker::PassphraseVerification.new(passphrase)
666+
expect(tanker4.status).to eq(Tanker::Status::READY)
667+
598668
tanker1.free
599669
tanker2.free
600670
tanker3.free
671+
tanker4.free
601672
end
602673
end
603674

0 commit comments

Comments
 (0)