1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe User do
describe '#authenticatable_salt' do
let(:user) { build(:user, encrypted_password: encrypted_password) }
subject(:authenticatable_salt) { user.authenticatable_salt }
context 'when password is stored in BCrypt format' do
let(:encrypted_password) { '$2a$10$AvwDCyF/8HnlAv./UkAZx.vAlKRS89yNElP38FzdgOmVaSaiDL7xm' }
it 'returns the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).to eq(user.encrypted_password[0, 29])
end
end
context 'when password is stored in PBKDF2 format' do
let(:encrypted_password) { '$pbkdf2-sha512$20000$rKbYsScsDdk$iwWBewXmrkD2fFfaG1SDcMIvl9gvEo3fBWUAfiqyVceTlw/DYgKBByHzf45pF5Qn59R4R.NQHsFpvZB4qlsYmw' } # rubocop:disable Layout/LineLength
it 'uses the decoded password salt' do
expect(authenticatable_salt).to eq('aca6d8b1272c0dd9')
end
it 'does not use the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).not_to eq(encrypted_password[0, 29])
end
end
context 'when the encrypted_password is an unknown type' do
let(:encrypted_password) { '$argon2i$v=19$m=512,t=4,p=2$eM+ZMyYkpDRGaI3xXmuNcQ$c5DeJg3eb5dskVt1mDdxfw' }
it 'returns the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).to eq(encrypted_password[0, 29])
end
end
end
describe '#valid_password?' do
subject(:validate_password) { user.valid_password?(password) }
let(:user) { build(:user, encrypted_password: encrypted_password) }
let(:password) { described_class.random_password }
shared_examples 'password validation fails when the password is encrypted using an unsupported method' do
let(:encrypted_password) { '$argon2i$v=19$m=512,t=4,p=2$eM+ZMyYkpDRGaI3xXmuNcQ$c5DeJg3eb5dskVt1mDdxfw' }
it { is_expected.to eq(false) }
end
context 'when the default encryption method is BCrypt' do
it_behaves_like 'password validation fails when the password is encrypted using an unsupported method'
context 'when the user password PBKDF2+SHA512' do
let(:encrypted_password) do
Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.digest(
password, 20_000, Devise.friendly_token[0, 16])
end
it { is_expected.to eq(true) }
it 're-encrypts the password as BCrypt' do
expect(user.encrypted_password).to start_with('$pbkdf2-sha512$')
validate_password
expect(user.encrypted_password).to start_with('$2a$')
end
end
end
context 'when the default encryption method is PBKDF2+SHA512 and the user password is BCrypt', :fips_mode do
it_behaves_like 'password validation fails when the password is encrypted using an unsupported method'
context 'when the user password BCrypt' do
let(:encrypted_password) { Devise::Encryptor.digest(described_class, password) }
it { is_expected.to eq(true) }
it 're-encrypts the password as PBKDF2+SHA512' do
expect(user.encrypted_password).to start_with('$2a$')
validate_password
expect(user.reload.encrypted_password).to start_with('$pbkdf2-sha512$')
end
end
end
end
describe '#password=' do
let(:user) { build(:user) }
let(:password) { described_class.random_password }
def compare_bcrypt_password(user, password)
Devise::Encryptor.compare(described_class, user.encrypted_password, password)
end
def compare_pbkdf2_password(user, password)
Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.compare(user.encrypted_password, password)
end
context 'when FIPS mode is enabled', :fips_mode do
it 'calls PBKDF2 digest and not the default Devise encryptor' do
expect(Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512)
.to receive(:digest).at_least(:once).and_call_original
expect(Devise::Encryptor).not_to receive(:digest)
user.password = password
end
it 'saves the password in PBKDF2 format' do
user.password = password
user.save!
expect(compare_pbkdf2_password(user, password)).to eq(true)
expect { compare_bcrypt_password(user, password) }.to raise_error(::BCrypt::Errors::InvalidHash)
end
end
it 'calls default Devise encryptor and not the PBKDF2 encryptor' do
expect(Devise::Encryptor).to receive(:digest).at_least(:once).and_call_original
expect(Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512).not_to receive(:digest)
user.password = password
end
it 'saves the password in BCrypt format' do
user.password = password
user.save!
expect { compare_pbkdf2_password(user, password) }
.to raise_error Devise::Pbkdf2Encryptable::Encryptors::InvalidHash
expect(compare_bcrypt_password(user, password)).to eq(true)
end
end
end
|