-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Exposing a TRN in the URL opens us up to someone being able to enumerate through the values and capture data on people. We want to prevent this by encoding the values passed in the URL. This ensures that the UI is the only way to access someone's record. I opted to use a symmetric encoding process to allow us to encode/decode using a secret that we control. This is an easy to understand encryption that is as secure as our secret key.
- Loading branch information
1 parent
17d1e0f
commit 0726d08
Showing
4 changed files
with
96 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
class SecureIdentifier | ||
SECRET_KEY = Rails.application.credentials.secret_key_base.byteslice(0..31) | ||
CIPHER = 'aes-256-cbc' | ||
|
||
def self.encode(value) | ||
raise ArgumentError, "value cannot be nil" if value.nil? | ||
return value if value.empty? | ||
|
||
cipher = OpenSSL::Cipher.new(CIPHER) | ||
cipher.encrypt | ||
cipher.key = SECRET_KEY | ||
|
||
encrypted = cipher.update(value) + cipher.final | ||
Base64.urlsafe_encode64(encrypted).strip | ||
end | ||
|
||
def self.decode(value) | ||
raise ArgumentError, "value cannot be nil" if value.nil? | ||
return value if value.empty? | ||
|
||
decipher = OpenSSL::Cipher.new(CIPHER) | ||
decipher.decrypt | ||
decipher.key = SECRET_KEY | ||
|
||
begin | ||
decoded = Base64.urlsafe_decode64(value) | ||
decipher.update(decoded) + decipher.final | ||
rescue ArgumentError => e | ||
return value | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
require 'rails_helper' | ||
|
||
RSpec.describe SecureIdentifier do | ||
let(:plain_text) { 'Hello, World!' } | ||
|
||
describe '.encode' do | ||
subject { described_class.encode(plain_text) } | ||
|
||
it { is_expected.not_to eq(plain_text) } | ||
|
||
it 'returns a Base64 encoded string' do | ||
is_expected.to eq( | ||
Base64.strict_encode64(Base64.strict_decode64(SecureIdentifier.encode(plain_text))) | ||
) | ||
end | ||
|
||
context 'when given an empty string' do | ||
let(:plain_text) { '' } | ||
|
||
it { is_expected.to eq(plain_text) } | ||
end | ||
|
||
context 'when given nil' do | ||
let(:plain_text) { nil } | ||
|
||
it 'raises an ArgumentError' do | ||
expect { subject }.to raise_error(ArgumentError) | ||
end | ||
end | ||
end | ||
|
||
describe '.decode' do | ||
subject { described_class.decode(encoded_text) } | ||
|
||
let(:encoded_text) { described_class.encode(plain_text) } | ||
|
||
it { is_expected.to eq(plain_text) } | ||
|
||
context 'when given an invalid Base64 string' do | ||
let(:encoded_text) { 'invalid' } | ||
|
||
it 'returns the passed value' do | ||
is_expected.to eq(encoded_text) | ||
end | ||
end | ||
|
||
context 'when given an empty string' do | ||
let(:encoded_text) { '' } | ||
|
||
it { is_expected.to eq(encoded_text) } | ||
end | ||
|
||
context 'when given nil' do | ||
let(:encoded_text) { nil } | ||
|
||
it 'raises an ArgumentError' do | ||
expect { subject }.to raise_error(ArgumentError) | ||
end | ||
end | ||
end | ||
end |