-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Don't reuse IVs and add future placeholder for key rotation.
- Loading branch information
Showing
15 changed files
with
317 additions
and
30 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,10 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "encryption/symmetric" | ||
|
||
module AcaEntities | ||
# Manages encryption and encryption primatives for ACA Entities | ||
# and associated payloads. | ||
module Encryption | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# frozen_string_literal: true | ||
|
||
require "base64" | ||
require_relative "symmetric/legacy_keyset" | ||
require_relative "symmetric/key_manager" | ||
require_relative "symmetric/encrypted_payload" | ||
require_relative "symmetric/legacy_encrypted_payload" | ||
require_relative "symmetric/parse_encrypted_payload" | ||
require_relative "symmetric/decrypt_payload" | ||
require_relative "symmetric/encrypt_payload" | ||
|
||
module AcaEntities | ||
module Encryption | ||
# Management and algorithms for symmetric algorithms, supported by | ||
# libsodium. | ||
module Symmetric | ||
# Algorithm implementation versions. | ||
# | ||
# Right now we have only Version 1. | ||
ALGO_VERSIONS = ["S1"].freeze | ||
CURRENT_ALGO_VERSION = "S1" | ||
|
||
class InvalidPayloadHeaderError < StandardError; end | ||
|
||
class KeyNotFoundError < StandardError; end | ||
|
||
# Manages nonces for symmetric encryption | ||
class Nonce | ||
def self.generate(byte_size) | ||
RbNaCl::Random.random_bytes(byte_size) | ||
end | ||
end | ||
|
||
def decrypt(payload) | ||
DecryptPayload.new.call(payload) | ||
end | ||
|
||
def encrypt(payload) | ||
EncryptPayload.new.call(payload) | ||
end | ||
|
||
module_function :decrypt, :encrypt | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'dry/monads' | ||
require 'dry/monads/do' | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# Decrypt a payload. | ||
class DecryptPayload | ||
send(:include, Dry::Monads[:result, :do]) | ||
send(:include, Dry::Monads[:try]) | ||
|
||
def call(payload) | ||
encrypted_payload = yield parse_header_and_payload(payload) | ||
decrypt_payload(encrypted_payload) | ||
end | ||
|
||
def parse_header_and_payload(payload) | ||
ParseEncryptedPayload.new.call(payload) | ||
end | ||
|
||
def decrypt_payload(encrypted_payload) | ||
if encrypted_payload.header? | ||
found_key = yield lookup_key(encrypted_payload) | ||
decryption_result = Try do | ||
decryption_box = RbNaCl::SecretBox.new(found_key) | ||
decryption_box.decrypt(encrypted_payload.nonce, encrypted_payload.content) | ||
end | ||
decryption_result.to_result | ||
else | ||
secret_box = RbNaCl::SecretBox.new(LegacyKeyset.secret_key) | ||
Success(secret_box.decrypt(LegacyKeyset.iv, Base64.decode64(encrypted_payload.content))) | ||
end | ||
end | ||
|
||
def lookup_key(encrypted_payload) | ||
key_result = Try do | ||
KeyManager.resolve_key(encrypted_payload.algo_version, encrypted_payload.key_version) | ||
end | ||
|
||
key_result.to_result | ||
end | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'dry/monads' | ||
require 'dry/monads/do' | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# Encrypte a payload. | ||
class EncryptPayload | ||
send(:include, Dry::Monads[:result, :do]) | ||
send(:include, Dry::Monads[:try]) | ||
|
||
def call(payload) | ||
encrypted = yield construct_encrypted_payload(payload) | ||
encode_payload(encrypted) | ||
end | ||
|
||
def construct_encrypted_payload(payload) | ||
encryption_attempt = Try do | ||
current_algo_v = KeyManager.current_algo_version | ||
current_key_v = KeyManager.current_key_version | ||
key = KeyManager.resolve_key(current_algo_v, current_key_v) | ||
encrypt_box = RbNaCl::SecretBox.new(key) | ||
nonce = Nonce.generate(encrypt_box.nonce_bytes) | ||
content = encrypt_box.encrypt(nonce, payload) | ||
EncryptedPayload.new( | ||
current_algo_v, | ||
current_key_v, | ||
nonce, | ||
content | ||
) | ||
end | ||
encryption_attempt.to_result | ||
end | ||
|
||
def encode_payload(encrypted) | ||
encoded_result = Try do | ||
# rubocop:disable Style/StringConcatenation | ||
encrypted.algo_version + "." + encrypted.key_version + "." + | ||
Base64.strict_encode64(encrypted.nonce) + "." + | ||
Base64.strict_encode64(encrypted.content) | ||
# rubocop:enable Style/StringConcatenation | ||
end | ||
|
||
encoded_result.to_result | ||
end | ||
end | ||
end | ||
end | ||
end |
23 changes: 23 additions & 0 deletions
23
lib/aca_entities/encryption/symmetric/encrypted_payload.rb
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,23 @@ | ||
# frozen_string_literal: true | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# A standard payload with a header for encryption. | ||
class EncryptedPayload | ||
attr_reader :algo_version, :key_version, :nonce, :content | ||
|
||
def initialize(a_version, k_version, nonce_value, content_value) | ||
@algo_version = a_version | ||
@key_version = k_version | ||
@nonce = nonce_value | ||
@content = content_value | ||
end | ||
|
||
def header? | ||
true | ||
end | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# frozen_string_literal: true | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# Manages the selection of a key and algorithm using version headers | ||
class KeyManager | ||
def self.current_algo_version | ||
CURRENT_ALGO_VERSION | ||
end | ||
|
||
# Right now, since we're using the legacy configuration as our source, | ||
# we'll tag our key version as 'L'. | ||
def self.current_key_version | ||
"L" | ||
end | ||
|
||
# Eventually we will replace this with a dynamic lookup to allow key | ||
# rotation. | ||
def self.resolve_key(_algo_version, _key_version) | ||
LegacyKeyset.secret_key | ||
end | ||
end | ||
end | ||
end | ||
end |
20 changes: 20 additions & 0 deletions
20
lib/aca_entities/encryption/symmetric/legacy_encrypted_payload.rb
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,20 @@ | ||
# frozen_string_literal: true | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# A payload from before we versioned our algorithms or keys. | ||
class LegacyEncryptedPayload | ||
attr_reader :content | ||
|
||
def initialize(content_value) | ||
@content = content_value | ||
end | ||
|
||
def header? | ||
false | ||
end | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# frozen_string_literal: true | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# Manages key settings for the 'original' implementation. | ||
class LegacyKeyset | ||
def self.secret_key | ||
key = AcaEntities::Configuration::Encryption.config.secret_key | ||
[key].pack("H*") | ||
end | ||
|
||
def self.iv | ||
AcaEntities::Configuration::Encryption.config.iv | ||
end | ||
end | ||
end | ||
end | ||
end |
50 changes: 50 additions & 0 deletions
50
lib/aca_entities/encryption/symmetric/parse_encrypted_payload.rb
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,50 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'dry/monads' | ||
require 'dry/monads/do' | ||
|
||
module AcaEntities | ||
module Encryption | ||
module Symmetric | ||
# Parse the header and body for an encrypted payload. | ||
# | ||
# We might return an error in the case of an invalid payload, or we | ||
# we might return an empty payload, as the current data was provided | ||
# in the 'legacy' encryption format. | ||
class ParseEncryptedPayload | ||
send(:include, Dry::Monads[:result, :do]) | ||
send(:include, Dry::Monads[:try]) | ||
|
||
def call(payload) | ||
return Success(LegacyEncryptedPayload.new(payload)) if payload.blank? | ||
return Success(LegacyEncryptedPayload.new(payload)) unless payload.include?(".") | ||
|
||
payload_parts = yield split_parts(payload) | ||
parse_header_values(payload_parts) | ||
end | ||
|
||
def split_parts(payload) | ||
split_attempt = Try do | ||
payload_parts = payload.split(".").map(&:strip) | ||
raise InvalidPayloadHeaderError, "Payload only had #{payload_parts.length} parts, expected 4." if payload_parts.length < 4 | ||
payload_parts | ||
end | ||
|
||
split_attempt.to_result | ||
end | ||
|
||
def parse_header_values(payload_parts) | ||
parse_attempt = Try do | ||
algo_version = payload_parts.first | ||
key_version = payload_parts[1] | ||
nonce = Base64.decode64(payload_parts[2]) | ||
content = Base64.decode64(payload_parts[3]) | ||
EncryptedPayload.new(algo_version, key_version, nonce, content) | ||
end | ||
|
||
parse_attempt.to_result | ||
end | ||
end | ||
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
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
Oops, something went wrong.