diff --git a/VERSION b/VERSION index a75b92f1e..ef425ca98 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1 +5.2 diff --git a/app/controllers/api/teams_controller.rb b/app/controllers/api/teams_controller.rb index 8a5a6abe8..ad3ab9d6a 100644 --- a/app/controllers/api/teams_controller.rb +++ b/app/controllers/api/teams_controller.rb @@ -17,7 +17,7 @@ class Api::TeamsController < ApiController # GET /api/teams def index if team_id.present? - authorize Team.find(team_id), :team_member? + fetch_team elsif params['only_teammember_user_id'].present? authorize ::Team, :only_teammember? else @@ -28,6 +28,32 @@ def index private + def fetch_team + @team = Team.find(team_id) + authorize @team, :team_member? + + unless already_recrypted?(@team) + receive_transferred_encryptables(@team) if team.personal_team? + recrypt(@team) + end + end + + def receive_transferred_encryptables(team) + team.encryptables.select(&:transferred?).each do |encryptable| + team_password = decrypted_team_password(team) + EncryptableTransfer.new.receive(encryptable, session[:private_key], team_password) + end + end + + def already_recrypted?(team) + Crypto::Symmetric.latest_algorithm?(team) + end + + def recrypt(team) + private_key = session[:private_key] + Crypto::Symmetric::Recrypt.new(current_user, team, private_key).perform + end + def build_entry instance_variable_set(:"@#{ivar_name}", Team::Shared.create(current_user, model_params)) diff --git a/app/models/encryptable.rb b/app/models/encryptable.rb index 13a07ff89..c7cfc8f39 100644 --- a/app/models/encryptable.rb +++ b/app/models/encryptable.rb @@ -17,6 +17,8 @@ class Encryptable < ApplicationRecord serialize :encrypted_data, ::EncryptedData + class_attribute :used_encrypted_attrs + attr_readonly :type validates :type, presence: true @@ -26,12 +28,25 @@ class Encryptable < ApplicationRecord validates :name, presence: true validates :description, length: { maximum: 4000 } - def encrypt(_team_password) - raise 'implement in subclass' + def encrypt(team_password, encryption_algorithm = nil) + used_encrypted_attrs.each do |a| + encrypt_attr(a, team_password, encryption_algorithm) + end end - def decrypt(_team_password) - raise 'implement in subclass' + def decrypt(team_password) + encrypted_attrs = encrypted_data.used_attributes + encrypted_attrs.each do |a| + decrypt_attr(a, team_password) + end + end + + def recrypt(team_password, new_team_password, new_encryption_class) + decrypt(team_password) + + @new_encryption_class = new_encryption_class + encrypt(new_team_password) + save! end def self.policy_class @@ -75,30 +90,27 @@ def used_encrypted_data_attrs private - def encrypt_attr(attr, team_password) + def encrypt_attr(attr, team_password, encryption_algorithm = nil) cleartext_value = send(:"cleartext_#{attr}") - encrypted_value = if cleartext_value.presence - Crypto::Symmetric::Aes256.encrypt(cleartext_value, team_password) - end + encrypted_value = + encrypt_with_encryption_class(cleartext_value, team_password, encryption_algorithm) return if transferred? && encrypted_value.blank? - build_encrypted_data(attr, encrypted_value) - end - - def build_encrypted_data(attr, encrypted_value) attr_label = cleartext_custom_attr_label if attr == :custom_attr - encrypted_data.[]=( - attr, **{ label: attr_label, data: encrypted_value, iv: nil } - ) + encrypted_data.[]=(attr, **{ + label: attr_label, + data: encrypted_value[:data], + iv: encrypted_value[:iv] + }) end def decrypt_attr(attr, team_password) - encrypted_value = encrypted_data[attr].try(:[], :data) + encrypted_value = encrypted_value_hash(attr) - cleartext_value = if encrypted_value - Crypto::Symmetric::Aes256.decrypt(encrypted_value, team_password) + cleartext_value = if encrypted_value[:data].present? + encryption_class.decrypt(encrypted_value, team_password) end if attr == :custom_attr @@ -108,6 +120,27 @@ def decrypt_attr(attr, team_password) instance_variable_set("@cleartext_#{attr}", cleartext_value) end + def encrypted_value_hash(attr) + data_attr = encrypted_data[attr] + + data = data_attr.try(:[], :data) + iv = data_attr.try(:[], :iv) + { data: data, iv: iv } + end + + def encrypt_with_encryption_class(cleartext_value, team_password, encryption_algorithm) + if cleartext_value.presence + @new_encryption_class = encryption_algorithm unless encryption_algorithm.nil? + encryption_class.encrypt(cleartext_value, team_password) + else + { data: nil, iv: nil } + end + end + + def encryption_class + @new_encryption_class || team.encryption_class + end + def assert_human_receiver? unless User.find(receiver_id).is_a?(User::Human) errors.add(:receiver_id, 'Must be a human user') diff --git a/app/models/encryptable/credentials.rb b/app/models/encryptable/credentials.rb index 75ca54e2a..584eb9cba 100644 --- a/app/models/encryptable/credentials.rb +++ b/app/models/encryptable/credentials.rb @@ -4,6 +4,8 @@ class Encryptable::Credentials < Encryptable attr_accessor :cleartext_password, :cleartext_username, :cleartext_token, :cleartext_pin, :cleartext_email, :cleartext_custom_attr_label, :cleartext_custom_attr + self.used_encrypted_attrs = [:username, :password, :token, :pin, :email, :custom_attr].freeze + has_many :encryptable_files, class_name: 'Encryptable::File', foreign_key: :credential_id, @@ -12,23 +14,4 @@ class Encryptable::Credentials < Encryptable validates :name, length: { maximum: 70 } validates :name, uniqueness: { scope: :folder } validates :folder_id, presence: true - - def decrypt(team_password) - decrypt_attr(:username, team_password) - decrypt_attr(:password, team_password) - decrypt_attr(:token, team_password) - decrypt_attr(:pin, team_password) - decrypt_attr(:email, team_password) - decrypt_attr(:custom_attr, team_password) - end - - def encrypt(team_password) - encrypt_attr(:username, team_password) - encrypt_attr(:password, team_password) - encrypt_attr(:token, team_password) - encrypt_attr(:pin, team_password) - encrypt_attr(:email, team_password) - encrypt_attr(:custom_attr, team_password) - end - end diff --git a/app/models/encryptable/file.rb b/app/models/encryptable/file.rb index ba5e5eebb..c8724b944 100644 --- a/app/models/encryptable/file.rb +++ b/app/models/encryptable/file.rb @@ -3,6 +3,8 @@ class Encryptable::File < Encryptable attr_accessor :cleartext_file + self.used_encrypted_attrs = [:file].freeze + belongs_to :encryptable_credential, class_name: 'Encryptable::Credentials', foreign_key: :credential_id @@ -11,16 +13,6 @@ class Encryptable::File < Encryptable validate :file_size, on: [:create, :update] - def decrypt(team_password) - decrypt_attr(:file, team_password) - end - - def encrypt(team_password) - return if cleartext_file.empty? - - encrypt_attr(:file, team_password) - end - def team folder&.team || encryptable_credential.folder.team end diff --git a/app/models/encrypted_data.rb b/app/models/encrypted_data.rb index ce4e64fe2..d2e056f70 100644 --- a/app/models/encrypted_data.rb +++ b/app/models/encrypted_data.rb @@ -21,15 +21,19 @@ def []=(key, data:, iv:, label: nil) end def to_json(*_args) - @data.reject { |_, value| value[:data].blank? }.to_json + present_data.to_json end def used_attributes - @data.keys.map(&:to_s) + present_data.keys end private + def present_data + @data.reject { |_, value| value[:data].blank? } + end + def data_hash(iv, data, label = nil) hash = { iv: iv, data: data } hash[:label] = label if label diff --git a/app/models/team.rb b/app/models/team.rb index 2e8a961fe..bf25c0ea7 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -23,6 +23,16 @@ class Team < ApplicationRecord validates :name, presence: true validates :name, length: { maximum: 40 } validates :description, length: { maximum: 300 } + validates :encryption_algorithm, + inclusion: { in: ::Crypto::Symmetric::ALGORITHMS.keys }, allow_nil: false + + after_initialize :set_encryption_algorithm, if: :new_record? + + enum recrypt_state: { + failed: 0, + done: 1, + in_progress: 2 + }, _prefix: :recrypt def label name @@ -41,8 +51,8 @@ def teammember(user_id) end def decrypt_team_password(user, plaintext_private_key) - crypted_team_password = teammember(user.id).password - Crypto::Rsa.decrypt(crypted_team_password, plaintext_private_key) + encrypted_team_password = teammember(user.id).encrypted_team_password + Crypto::Rsa.decrypt(encrypted_team_password, plaintext_private_key) end def personal_team? @@ -60,10 +70,31 @@ def self.policy_class TeamPolicy end + def encryption_class + Crypto::Symmetric::ALGORITHMS[encryption_algorithm] + end + + def password_bitsize + encryption_class.password_bitsize + end + + def new_team_password + encryption_class.random_key + end + + def encryptables + encryptable_ids = Encryptable.joins(folder: :team).where('teams.id': id).pluck(:id) + Encryptable.where(id: encryptable_ids).or(Encryptable.where(credential_id: encryptable_ids)) + end + private def create_teammember(user, plaintext_team_password) encrypted_team_password = Crypto::Rsa.encrypt(plaintext_team_password, user.public_key) - teammembers.create!(password: encrypted_team_password, user: user) + teammembers.create!(encrypted_team_password: encrypted_team_password, user: user) + end + + def set_encryption_algorithm + self.encryption_algorithm = Crypto::Symmetric::LATEST_ALGORITHM end end diff --git a/app/models/team/personal.rb b/app/models/team/personal.rb index cae7dadf4..e59911c08 100644 --- a/app/models/team/personal.rb +++ b/app/models/team/personal.rb @@ -21,7 +21,7 @@ def create(owner) team = super(name: 'personal-team', personal_owner: owner, private: true) return team unless team.valid? - plaintext_team_password = Crypto::Symmetric::Aes256.random_key + plaintext_team_password = team.new_team_password team.add_user(owner, plaintext_team_password) team diff --git a/app/models/team/shared.rb b/app/models/team/shared.rb index ad467413e..33bf7ca84 100644 --- a/app/models/team/shared.rb +++ b/app/models/team/shared.rb @@ -20,7 +20,7 @@ def create(creator, params) team = super(params) return team unless team.valid? - plaintext_team_password = Crypto::Symmetric::Aes256.random_key + plaintext_team_password = team.new_team_password team.add_user(creator, plaintext_team_password) unless team.private? User::Human.admins.each do |a| diff --git a/app/models/teammember.rb b/app/models/teammember.rb index 6d1559a05..d9092719b 100644 --- a/app/models/teammember.rb +++ b/app/models/teammember.rb @@ -35,12 +35,10 @@ class Teammember < ApplicationRecord scope :in_private_teams, (-> { joins(:team).where('teams.private' => true) }) - def recrypt_team_password(user, admin, private_key) - teammember_admin = admin.teammembers.find_by(team_id: team_id) - team_password = Crypto::Rsa.decrypt(teammember_admin. - password, private_key) - - self.password = Crypto::Rsa.encrypt(team_password, user.public_key) + def reset_team_password(new_team_password) + public_key = user.public_key + encrypted_team_password = Crypto::Rsa.encrypt(new_team_password, public_key) + self.encrypted_team_password = encrypted_team_password save! end diff --git a/app/models/user/human.rb b/app/models/user/human.rb index c21ba5740..40d7c145a 100644 --- a/app/models/user/human.rb +++ b/app/models/user/human.rb @@ -181,7 +181,7 @@ def empower(actor, private_key) next if t.teammember?(self) active_teammember = t.teammembers.find_by user_id: actor.id - team_password = Crypto::Rsa.decrypt(active_teammember.password, private_key) + team_password = Crypto::Rsa.decrypt(active_teammember.encrypted_team_password, private_key) t.add_user(self, team_password) end end diff --git a/app/serializers/team_serializer.rb b/app/serializers/team_serializer.rb index a10864b1d..10889a912 100644 --- a/app/serializers/team_serializer.rb +++ b/app/serializers/team_serializer.rb @@ -16,7 +16,8 @@ class TeamSerializer < ApplicationSerializer # To hide STI name in Frontend type Team.name.pluralize - attributes :id, :name, :description, :private, :favourised, :deletable, :type + attributes :id, :name, :description, :private, :favourised, :deletable, :type, + :encryption_algorithm, :password_bitsize has_many :folders, serializer: FolderSerializer @@ -31,4 +32,8 @@ def deletable def personal_team object.personal_team? end + + def password_bitsize + object.encryption_class.password_bitsize + end end diff --git a/app/utils/crypto/symmetric.rb b/app/utils/crypto/symmetric.rb index 5e243935d..23de60fb5 100644 --- a/app/utils/crypto/symmetric.rb +++ b/app/utils/crypto/symmetric.rb @@ -3,7 +3,16 @@ require 'openssl' require 'digest/sha1' -class Crypto::Symmetric +class ::Crypto::Symmetric + class_attribute :password_bitsize + + LATEST_ALGORITHM = 'AES256IV' + + # Add further algorithms at the bottom + ALGORITHMS = { + AES256: ::Crypto::Symmetric::Aes256, + AES256IV: ::Crypto::Symmetric::Aes256iv + }.with_indifferent_access.freeze class << self @@ -18,5 +27,9 @@ def decrypt def random_key raise 'Implement in subclass' end + + def latest_algorithm?(entry) + LATEST_ALGORITHM == entry.encryption_algorithm + end end end diff --git a/app/utils/crypto/symmetric/aes256.rb b/app/utils/crypto/symmetric/aes256.rb index 48d5a277c..ec30fe44c 100644 --- a/app/utils/crypto/symmetric/aes256.rb +++ b/app/utils/crypto/symmetric/aes256.rb @@ -3,34 +3,37 @@ require 'openssl' require 'digest/sha1' -require_relative './aes256' +class ::Crypto::Symmetric::Aes256 < ::Crypto::Symmetric -class Crypto::Symmetric::Aes256 < Crypto::Symmetric CIPHER ||= 'AES-256-CBC' MAGIC ||= 'Salted__' SALT_LENGTH ||= 8 ITERATION_COUNT ||= 1000 - class << self + self.password_bitsize = OpenSSL::Cipher.new(CIPHER).key_len * 8 + class << self def encrypt(data, key) cipher = cipher_encrypt_mode # set encryption key cipher.key = key - # return encrypted data - cipher.update(data) + cipher.final + # encrypt given data + encrypted_data = cipher.update(data) + cipher.final + + # return data and nil iv value + { data: encrypted_data, iv: nil } end - def decrypt(data, key) + def decrypt(encrypted_data, key) cipher = cipher_decrypt_mode # set decryption key cipher.key = key # decrypt data - cipher.update(data) + cipher.final + cipher.update(encrypted_data[:data]) + cipher.final end def encrypt_with_salt(data, key) diff --git a/app/utils/crypto/symmetric/aes256iv.rb b/app/utils/crypto/symmetric/aes256iv.rb index fddb63c21..29104f1ad 100644 --- a/app/utils/crypto/symmetric/aes256iv.rb +++ b/app/utils/crypto/symmetric/aes256iv.rb @@ -3,9 +3,7 @@ require 'openssl' require 'digest/sha1' -require_relative './aes256' - -class Crypto::Symmetric::Aes256iv < Crypto::Symmetric::Aes256 +class ::Crypto::Symmetric::Aes256iv < ::Crypto::Symmetric::Aes256 class << self @@ -19,17 +17,17 @@ def encrypt(data, key) # encrypt given data encrypted_data = cipher.update(data) + cipher.final - [encrypted_data, iv] + { data: encrypted_data, iv: iv } end - def decrypt(data, key, iv) + def decrypt(encrypted_data, key) cipher = cipher_decrypt_mode cipher.key = key - cipher.iv = iv + cipher.iv = encrypted_data[:iv] - # decrypt data - cipher.update(data) + cipher.final + # decrypt given data + cipher.update(encrypted_data[:data]) + cipher.final end end end diff --git a/app/utils/crypto/symmetric/recrypt.rb b/app/utils/crypto/symmetric/recrypt.rb new file mode 100644 index 000000000..73be58abe --- /dev/null +++ b/app/utils/crypto/symmetric/recrypt.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Crypto::Symmetric::Recrypt + + def initialize(current_user, team, private_key) + @current_user = current_user + @team = team + @private_key = private_key + end + + def perform + return if already_recrypted? || recrypt_locked? + + @team.recrypt_in_progress! + @team_password = @team.decrypt_team_password(@current_user, @private_key) + @new_team_password = @team.new_team_password + + begin + recrypt + rescue => e # rubocop:disable Style/RescueStandardError + @team.recrypt_failed! + raise "Recrypt failed: #{e.message}" + end + end + + private + + def already_recrypted? + Crypto::Symmetric.latest_algorithm?(@team) + end + + def recrypt_locked? + @team.recrypt_in_progress? || @team.recrypt_failed? + end + + def recrypt + ActiveRecord::Base.transaction do + @team.encryptables.find_each do |encryptable| + encryptable.recrypt(@team_password, @new_team_password, latest_encryption_class) + end + + update_team + end + end + + def update_team + @team.teammembers.find_each do |member| + member.reset_team_password(@new_team_password) + end + + @team.encryption_algorithm = ::Crypto::Symmetric::LATEST_ALGORITHM + @team.recrypt_done! + end + + def latest_encryption_class + encryption_algorithm = ::Crypto::Symmetric::LATEST_ALGORITHM + Crypto::Symmetric::ALGORITHMS[encryption_algorithm] + end + +end diff --git a/app/utils/encryptable_transfer.rb b/app/utils/encryptable_transfer.rb index 675340522..f0fdef4e0 100644 --- a/app/utils/encryptable_transfer.rb +++ b/app/utils/encryptable_transfer.rb @@ -3,18 +3,12 @@ class EncryptableTransfer def transfer(encryptable, receiver, sender) - transfer_password = new_transfer_password - encryptable.encrypt(transfer_password) + encryption_algorithm = receiver_encryption_algorithm(receiver) - encryptable.name = encryptable_destination_name(encryptable, receiver) + transfer_password = encryption_algorithm.random_key + encryptable.encrypt(transfer_password, encryption_algorithm) - encryptable.update!( - folder: receiver.inbox_folder, - sender_id: sender.id, - encrypted_transfer_password: - Base64.encode64(encrypted_transfer_password(transfer_password, receiver)) - ) - encryptable + update_encryptable(encryptable, receiver, sender, transfer_password) end def receive(encryptable, private_key, personal_team_password) @@ -31,6 +25,18 @@ def receive(encryptable, private_key, personal_team_password) private + def update_encryptable(encryptable, receiver, sender, transfer_password) + encryptable.name = encryptable_destination_name(encryptable, receiver) + + encryptable.update!( + folder: receiver.inbox_folder, + sender_id: sender.id, + encrypted_transfer_password: + Base64.encode64(encrypted_transfer_password(transfer_password, receiver)) + ) + encryptable + end + def encryptable_destination_name(encryptable, receiver) existing_names = receiver.inbox_folder.encryptables.pluck(:name) is_file = encryptable.is_a?(Encryptable::File) @@ -45,8 +51,9 @@ def encrypted_transfer_password(password, receiver) ) end - def new_transfer_password - Crypto::Symmetric::Aes256.random_key + def receiver_encryption_algorithm(receiver) + encryption_algorithm = receiver.personal_team.encryption_algorithm + Crypto::Symmetric::ALGORITHMS[encryption_algorithm] end def transfered_name(name, existing_names, is_file) diff --git a/db/migrate/20220517131300_change_tables_to_support_recrypt.rb b/db/migrate/20220517131300_change_tables_to_support_recrypt.rb new file mode 100644 index 000000000..6bb15e2ac --- /dev/null +++ b/db/migrate/20220517131300_change_tables_to_support_recrypt.rb @@ -0,0 +1,8 @@ +class ChangeTablesToSupportRecrypt < ActiveRecord::Migration[7.0] + def change + add_column :teams, :recrypt_state, :integer, default: Team.recrypt_states[:done], null: false + add_column :teams, :encryption_algorithm, :string + + Team.update_all(encryption_algorithm: 'AES256') + end +end diff --git a/db/migrate/20220609063817_rename_password_to_encrypted_team_password.rb b/db/migrate/20220609063817_rename_password_to_encrypted_team_password.rb new file mode 100644 index 000000000..301f517a5 --- /dev/null +++ b/db/migrate/20220609063817_rename_password_to_encrypted_team_password.rb @@ -0,0 +1,5 @@ +class RenamePasswordToEncryptedTeamPassword < ActiveRecord::Migration[7.0] + def change + rename_column :teammembers, :password, :encrypted_team_password + end +end diff --git a/db/schema.rb b/db/schema.rb index 4cc966821..40e3c98f2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -49,7 +49,7 @@ create_table "teammembers", force: :cascade do |t| t.integer "team_id", default: 0, null: false - t.binary "password", null: false + t.binary "encrypted_team_password", null: false t.integer "user_id", default: 0, null: false t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false @@ -64,6 +64,8 @@ t.boolean "private", default: false, null: false t.string "type", default: "Team::Shared", null: false t.integer "personal_owner_id" + t.integer "recrypt_state", default: 1, null: false + t.string "encryption_algorithm" t.index ["name"], name: "index_teams_on_name" end diff --git a/db/seeds/support/team_folders_encryptables_seeder.rb b/db/seeds/support/team_folders_encryptables_seeder.rb index 39c45e10b..e72a1d67c 100644 --- a/db/seeds/support/team_folders_encryptables_seeder.rb +++ b/db/seeds/support/team_folders_encryptables_seeder.rb @@ -14,7 +14,7 @@ def seed_team(name, members, admin = false) seed_folder(team) end - plaintext_team_pw = Crypto::Symmetric::Aes256.random_key + plaintext_team_pw = Crypto::Symmetric::Aes256iv.random_key members.each do |m| u = user(m) diff --git a/frontend/app/components/encryptable/show.js b/frontend/app/components/encryptable/show.js index 96adf1adf..202b485e2 100644 --- a/frontend/app/components/encryptable/show.js +++ b/frontend/app/components/encryptable/show.js @@ -5,6 +5,7 @@ import { inject as service } from "@ember/service"; export default class ShowComponent extends Component { @service router; + @service navService; constructor() { super(...arguments); diff --git a/frontend/app/components/team/index.js b/frontend/app/components/team/index.js new file mode 100644 index 000000000..8ab570625 --- /dev/null +++ b/frontend/app/components/team/index.js @@ -0,0 +1,15 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import { tracked } from "@glimmer/tracking"; + +export default class Index extends Component { + @service navService; + @service loading; + + @tracked model = this.args.model; + @tracked q = this.args.q; + + constructor() { + super(...arguments); + } +} diff --git a/frontend/app/controllers/teams/index.js b/frontend/app/controllers/teams/index.js new file mode 100644 index 000000000..1a03fc946 --- /dev/null +++ b/frontend/app/controllers/teams/index.js @@ -0,0 +1,6 @@ +import { inject as service } from "@ember/service"; +import ApplicationController from "../application"; + +export default class TeamsIndexController extends ApplicationController { + @service loading; +} diff --git a/frontend/app/models/team.js b/frontend/app/models/team.js index 1cb4b1130..004cea361 100644 --- a/frontend/app/models/team.js +++ b/frontend/app/models/team.js @@ -4,6 +4,8 @@ export default class Team extends Model { @attr("string") name; @attr("string") description; @attr("string") type; + @attr("string") encryptionAlgorithm; + @attr("number") passwordBitsize; @attr("boolean") private; @attr("boolean") favourised; @attr("boolean") deletable; diff --git a/frontend/app/routes/teams/show.js b/frontend/app/routes/teams/show.js index 78fba43b4..fe150094a 100644 --- a/frontend/app/routes/teams/show.js +++ b/frontend/app/routes/teams/show.js @@ -8,6 +8,7 @@ export default class TeamsShowRoute extends BaseRoute { @service intl; templateName = "teams/index"; + controllerName = "teams/index"; afterModel(_resolvedModel, transition) { this.navService.setSelectedTeamById(transition.to.params.team_id); diff --git a/frontend/app/styles/encryptable-show.scss b/frontend/app/styles/encryptable-show.scss index a81fda9d8..416f15c42 100644 --- a/frontend/app/styles/encryptable-show.scss +++ b/frontend/app/styles/encryptable-show.scss @@ -126,3 +126,9 @@ justify-content: center; } } + +.encryptable-lock-icon { + border: 0; + margin-left: 15px; + margin-top: 7px; +} diff --git a/frontend/app/templates/components/encryptable/attribute-field.hbs b/frontend/app/templates/components/encryptable/attribute-field.hbs index 07bfea2ad..e0b29a5e1 100644 --- a/frontend/app/templates/components/encryptable/attribute-field.hbs +++ b/frontend/app/templates/components/encryptable/attribute-field.hbs @@ -7,14 +7,22 @@ {{/if}} {{/unless}} -
- {{#if this.isAttributeVisible}} - - {{else}} - {{t (concat "encryptable/credentials.show.show_" this.args.attribute)}} - {{/if}} - +
+
+ {{#if this.isAttributeVisible}} + + {{else}} + {{t (concat "encryptable/credentials.show.show_" this.args.attribute)}} + {{/if}} + +
+ {{#unless this.args.row}} + + encrypted value + + + {{/unless}}
diff --git a/frontend/app/templates/components/encryptable/form.hbs b/frontend/app/templates/components/encryptable/form.hbs index 654833e5b..8e21862ec 100644 --- a/frontend/app/templates/components/encryptable/form.hbs +++ b/frontend/app/templates/components/encryptable/form.hbs @@ -111,7 +111,7 @@ {{!-- Bind Popper to modal with viewportSelector, more information: https://www.ember-bootstrap.com/api/classes/Components.Tooltip.html#property_viewportPadding--}} - + encrypted value diff --git a/frontend/app/templates/teams/index.hbs b/frontend/app/templates/teams/index.hbs index 50c24f406..ca2d8e893 100644 --- a/frontend/app/templates/teams/index.hbs +++ b/frontend/app/templates/teams/index.hbs @@ -1,14 +1,22 @@
- {{#if this.q}} - - {{/if}} - {{#if @model}} - {{#each this.model as |team|}} - - {{/each}} + {{#if this.loading.isLoading}} +
+
+ Loading... +
+
{{else}} -
{{t "teams.index.no_content"}}
+ {{#if this.q}} + + {{/if}} + {{#if @model}} + {{#each this.model as |team|}} + + {{/each}} + {{else}} +
{{t "teams.index.no_content"}}
+ {{/if}} {{/if}}
diff --git a/frontend/package.json b/frontend/package.json index 99e211a55..277524350 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,6 +57,7 @@ "ember-in-viewport": "^3.9.0", "ember-intl": "^5.7.2", "ember-load-initializers": "^2.1.2", + "ember-loading": "^2.0.0", "ember-moment": "^9.0.1", "ember-notify": "^6.0.2", "ember-power-select": "^4.0.3", diff --git a/frontend/public/assets/images/encrypted_small.svg b/frontend/public/assets/images/encrypted_small.svg index 00bbc319b..302de567c 100644 --- a/frontend/public/assets/images/encrypted_small.svg +++ b/frontend/public/assets/images/encrypted_small.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/public/assets/images/not_encrypted_small.svg b/frontend/public/assets/images/not_encrypted_small.svg index cec092a9f..50c713b3f 100644 --- a/frontend/public/assets/images/not_encrypted_small.svg +++ b/frontend/public/assets/images/not_encrypted_small.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/frontend/tests/integration/components/encryptable/row-test.js b/frontend/tests/integration/components/encryptable/row-test.js index 808e7e066..b6a11963d 100644 --- a/frontend/tests/integration/components/encryptable/row-test.js +++ b/frontend/tests/integration/components/encryptable/row-test.js @@ -62,6 +62,9 @@ module("Integration | Component | encryptable/row", function (hooks) { assert.dom(this.element.querySelector("#email-field")).doesNotExist(); assert.dom(this.element.querySelector("#token-field")).doesNotExist(); assert.dom(this.element.querySelector("#custom-attr-field")).doesNotExist(); + assert + .dom(this.element.querySelector(".encryptable-lock-icon")) + .doesNotExist(); }); test("it renders with two set attribute", async function (assert) { @@ -107,6 +110,9 @@ module("Integration | Component | encryptable/row", function (hooks) { assert.dom(this.element.querySelector("#show-email")).doesNotExist(); assert.dom(this.element.querySelector("#show-token")).doesNotExist(); assert.dom(this.element.querySelector("#show-customAttr")).doesNotExist(); + assert + .dom(this.element.querySelector(".encryptable-lock-icon")) + .doesNotExist(); }); test("it renders with three or more set attribute", async function (assert) { @@ -147,6 +153,9 @@ module("Integration | Component | encryptable/row", function (hooks) { assert.dom(this.element.querySelector("#email-field")).doesNotExist(); assert.dom(this.element.querySelector("#token-field")).doesNotExist(); assert.dom(this.element.querySelector("#custom-attr-field")).doesNotExist(); + assert + .dom(this.element.querySelector(".encryptable-lock-icon")) + .doesNotExist(); }); test("it renders encryptable row for transferred file", async function (assert) { diff --git a/frontend/tests/integration/components/encryptable/show-test.js b/frontend/tests/integration/components/encryptable/show-test.js index be1b8f132..8c2c47069 100644 --- a/frontend/tests/integration/components/encryptable/show-test.js +++ b/frontend/tests/integration/components/encryptable/show-test.js @@ -74,6 +74,11 @@ module("Integration | Component | encryptable/show", function (hooks) { .doesNotExist(); assert.dom(this.element.querySelector("#show-customAttr")).doesNotExist(); + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); + await click("#show-password"); assert.equal( @@ -131,6 +136,11 @@ module("Integration | Component | encryptable/show", function (hooks) { "Show custom attribute" ); + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); + await click("#show-token"); assert.equal(this.element.querySelector("#cleartext_token").value, "token"); @@ -236,6 +246,11 @@ module("Integration | Component | encryptable/show", function (hooks) { this.element.querySelector("#encryptable-file-actions").innerText, "Actions" ); + + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); }); test("should render all german translations and encryptable values", async function (assert) { @@ -327,6 +342,11 @@ module("Integration | Component | encryptable/show", function (hooks) { this.element.querySelector("#encryptable-file-actions").innerText, "Aktionen" ); + + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); }); test("should render all swiss german translations and encryptable values", async function (assert) { @@ -418,6 +438,11 @@ module("Integration | Component | encryptable/show", function (hooks) { this.element.querySelector("#encryptable-file-actions").innerText, "Aktionä" ); + + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); }); test("should render all french translations and encryptable values", async function (assert) { @@ -509,6 +534,11 @@ module("Integration | Component | encryptable/show", function (hooks) { this.element.querySelector("#encryptable-file-actions").innerText, "Actions" ); + + let encryption_algorithm = this.element.querySelector( + ".encryptable-lock-icon" + ); + assert.ok(isPresent(encryption_algorithm)); }); test("it renders a transferred encryptable file", async function (assert) { @@ -534,6 +564,10 @@ module("Integration | Component | encryptable/show", function (hooks) { assert.ok(text.includes("Download file")); assert.ok(text.includes("Sender name: Bob Beier")); + assert + .dom(this.element.querySelector(".encryptable-lock-icon")) + .doesNotExist(); + let deleteButton = this.element.querySelector('.icon-button[alt="delete"]'); assert.ok(isPresent(deleteButton)); }); diff --git a/frontend/tests/integration/components/team/show-test.js b/frontend/tests/integration/components/team/show-test.js index dc2658942..827435442 100644 --- a/frontend/tests/integration/components/team/show-test.js +++ b/frontend/tests/integration/components/team/show-test.js @@ -43,6 +43,7 @@ module("Integration | Component | team/show", function (hooks) { name: "BBT", description: "Berufsbildungsteam of Puzzle ITC", folders: [folder], + encryptionAlgorithm: "AES256", userFavouriteTeams: [{ favourised: true }] }); diff --git a/frontend/translations/ch_be.yml b/frontend/translations/ch_be.yml index dec73bef9..d39b95db2 100644 --- a/frontend/translations/ch_be.yml +++ b/frontend/translations/ch_be.yml @@ -111,7 +111,6 @@ ch_be: new: title: Nöi Zuägangsdate form: - encryptedField: Das Fäld wird verschlüsslät notEncryptedField: Das Fäld wird nid verschlüsslät row: clickToSeeAttrs: Klick wenn aui Attribut möchtisch gseh @@ -387,6 +386,7 @@ ch_be: delete_encryptables: Zuägangsdate lösche configure: Konfigurier d'Mitgliedr sent_by: Gsändet vo + encryption_algorithm: "Verschlüsslet mit " helpers: label: diff --git a/frontend/translations/de.yml b/frontend/translations/de.yml index d6942978e..bd8d4ea85 100644 --- a/frontend/translations/de.yml +++ b/frontend/translations/de.yml @@ -111,7 +111,6 @@ de: new: title: Neue Zugangsdaten form: - encryptedField: Dieses Feld wird verschlüsselt notEncryptedField: Dieses Feld wird nicht verschlüsselt row: clickToSeeAttrs: Klicke um alle Attribute zu sehen @@ -387,6 +386,7 @@ de: delete_encryptables: Zugangsdaten löschen configure: Konfiguriere Mitglieder sent_by: Gesendet von + encryption_algorithm: "Verschlüsselt mit " helpers: label: diff --git a/frontend/translations/en.yml b/frontend/translations/en.yml index 9c9d03be2..fc7826ee8 100644 --- a/frontend/translations/en.yml +++ b/frontend/translations/en.yml @@ -112,7 +112,6 @@ en: new: title: New Credentials form: - encryptedField: This field is encrypted notEncryptedField: This field is not encrypted row: clickToSeeAttrs: Click to see all attributes @@ -387,6 +386,7 @@ en: delete_encryptables: Delete the Credentials configure: Configure Members sent_by: Sent by + encryption_algorithm: "Encrypted with " helpers: label: diff --git a/frontend/translations/fr.yml b/frontend/translations/fr.yml index 8c6383af5..5370cc929 100644 --- a/frontend/translations/fr.yml +++ b/frontend/translations/fr.yml @@ -112,7 +112,6 @@ fr: new: title: Nouveau compte form: - encryptedField: Ce champ est crypté notEncryptedField: Ce champ n'est pas crypté row: clickToSeeAttrs: Cliquez pour voir tous les attributs @@ -388,6 +387,7 @@ fr: delete_encryptables: Supprimer le compte configure: Configurer les membres sent_by: Envoyé par + encryption_algorithm: "Crypté avec " helpers: label: diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c930bb317..d838a0a51 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -514,16 +514,16 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== +"@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== -"@babel/helper-plugin-utils@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - "@babel/helper-remap-async-to-generator@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" @@ -2186,6 +2186,15 @@ "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" +"@babel/types@^7.10.2", "@babel/types@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + "@babel/types@^7.12.1", "@babel/types@^7.13.0", "@babel/types@^7.13.16", "@babel/types@^7.14.1", "@babel/types@^7.4.4": version "7.14.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.4.tgz#bfd6980108168593b38b3eb48a24aa026b919bc0" @@ -2202,15 +2211,6 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - to-fast-properties "^2.0.0" - "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -7675,6 +7675,17 @@ ember-compatibility-helpers@^1.2.4, ember-compatibility-helpers@^1.2.5: fs-extra "^9.1.0" semver "^5.4.1" +ember-concurrency-async@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-concurrency-async/-/ember-concurrency-async-1.0.0.tgz#635ec2b7b11c083699801b85c5bb757bb7ebfd45" + integrity sha512-otE1UcF+VYva8qdkYayHVBrGDG+Lt9oYLLMt3heEo98Mv9abdjrdaLzvSMMspzI3ncgKtmZsEwn+aubvq+Zhhw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/types" "^7.10.2" + ember-cli-babel "^7.19.0" + ember-cli-babel-plugin-helpers "^1.1.1" + ember-cli-htmlbars "^4.3.1" + ember-concurrency-decorators@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/ember-concurrency-decorators/-/ember-concurrency-decorators-2.0.3.tgz#2816c9a0283b90ba5340fc5b4e0b92ea91f7d6e3" @@ -7685,6 +7696,14 @@ ember-concurrency-decorators@^2.0.0: ember-cli-htmlbars "^4.3.1" ember-cli-typescript "^3.1.4" +ember-concurrency-ts@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/ember-concurrency-ts/-/ember-concurrency-ts-0.3.1.tgz#0b3b524a6a96c8ab749b20e1d4da00569773567a" + integrity sha512-lE9uqPgK1Y9PN/0BJ5zE2a+h95izRCn6FCyt7qVV3012TlblTynsBaoUuAbN1T3KfzFsrJaXwsxzRbDjEde2Sw== + dependencies: + ember-cli-babel "^7.19.0" + ember-cli-htmlbars "^4.3.1" + "ember-concurrency@>=1.0.0 <3": version "2.1.0" resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-2.1.0.tgz#5e55c19f43fb245c4fbe0628cbf26cc6561af40c" @@ -7696,7 +7715,7 @@ ember-concurrency-decorators@^2.0.0: ember-compatibility-helpers "^1.2.0" ember-destroyable-polyfill "^2.0.2" -"ember-concurrency@>=1.3.0 <3": +"ember-concurrency@>=1.3.0 <3", ember-concurrency@^2.0.3: version "2.3.7" resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-2.3.7.tgz#52d786e37704b9054da1952638797e23714ec0e1" integrity sha512-sz6sTIXN/CuLb5wdpauFa+rWXuvXXSnSHS4kuNzU5GSMDX1pLBWSuovoUk61FUe6CYRqBmT1/UushObwBGickQ== @@ -7881,6 +7900,18 @@ ember-load-initializers@^2.1.2: ember-cli-babel "^7.13.0" ember-cli-typescript "^2.0.2" +ember-loading@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-loading/-/ember-loading-2.0.0.tgz#49cdbed83b4ef29be00d28e5a532a72113233b31" + integrity sha512-aGFZszIwmrIZ5XINv9TXQGWrfpKIiPwYiakEmRXx0Jvr5ZnUyKtCO84+FaKMyezrbDWsq0UjYqmdiqSAXhIkIA== + dependencies: + ember-cli-babel "^7.26.6" + ember-cli-htmlbars "^5.7.1" + ember-cli-typescript "^4.1.0" + ember-concurrency "^2.0.3" + ember-concurrency-async "^1.0.0" + ember-concurrency-ts "^0.3.0" + ember-maybe-in-element@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/ember-maybe-in-element/-/ember-maybe-in-element-2.0.3.tgz#640ea56b492bdacd1c41c128c2163d933c18c3ec" diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index c33953c9c..98002e55f 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,5 +1,9 @@ +## Version 5.2 +- Recrypt encrypted db data with AES256iv + ## Version 5.1 - Introduce Encryptable Credentials Sharing +- Introduce ability to dynamically add new Encryption Alogrithms ## Version 5.0 - Introduce new Attributes for Credentials diff --git a/spec/controllers/api/encryptables_controller_spec.rb b/spec/controllers/api/encryptables_controller_spec.rb index cf603457b..87af8811f 100644 --- a/spec/controllers/api/encryptables_controller_spec.rb +++ b/spec/controllers/api/encryptables_controller_spec.rb @@ -23,7 +23,6 @@ end let(:credentials1) { encryptables(:credentials1) } let(:file1) { encryptables(:file1) } - let(:transferred_file1) { encryptables(:transferredFile1) } context 'GET index' do @@ -61,7 +60,7 @@ credentials1_json_attributes = credentials1_json['attributes'] credentials1_json_relationships = credentials1_json['relationships'] - expect(data.count).to eq 2 + expect(data.count).to eq 1 expect(credentials1_json_attributes['name']).to eq credentials1.name expect(credentials1_json['id']).to eq credentials1.id.to_s expect(credentials1_json_attributes['cleartext_username']).to be_nil @@ -86,7 +85,7 @@ credentials1_json_attributes = credentials1_json['attributes'] credentials1_json_relationships = credentials1_json['relationships'] - expect(data.count).to eq 2 + expect(data.count).to eq 1 expect(credentials1_json_attributes['name']).to eq credentials1.name expect(credentials1_json['id']).to eq credentials1.id.to_s expect(credentials1_json_attributes['cleartext_username']).to be_nil @@ -764,32 +763,58 @@ expect(received_file.cleartext_file).to eq('certificate') end - it 'download transferred encryptable' do - login_as(:bob) + context 'recrypt' do + it 'download transferred encryptable old encryption algorithm' do + login_as(:bob) - encryptable_file = prepare_transferred_encryptable + encryptable_file = prepare_transferred_encryptable(Crypto::Symmetric::Aes256) - login_as(:alice) + login_as(:alice) + + expect(controller).to receive(:send_file).exactly(:once) + + get :show, params: { id: encryptable_file.id }, xhr: true + end + + it 'Does return encrpytable record and dont start download old encryption algorithm' do + login_as(:bob) + + encryptable_file = prepare_transferred_encryptable(Crypto::Symmetric::Aes256) + + login_as(:alice) - expect(controller).to receive(:send_file).exactly(:once) + expect(controller).not_to receive(:send_file) + expect_any_instance_of(CrudController).to receive(:render_entry).exactly(:once) - get :show, params: { id: encryptable_file.id }, xhr: true + get :show, params: { id: encryptable_file.id }, xhr: true + end end - it 'displays transferred encryptable and dont download it' do - login_as(:bob) + context 'Most recent encryption algorithm' do + it 'downloads transferred encryptable encrypted with most recent algorithm' do + login_as(:bob) - encryptable_file = prepare_transferred_encryptable + encryptable_file = prepare_transferred_encryptable(Crypto::Symmetric::Aes256iv) - login_as(:alice) + login_as(:alice) + + expect(controller).to receive(:send_file).exactly(:once) + + get :show, params: { id: encryptable_file.id }, xhr: true + end + + it 'Does return encrpytable record and not the file to download with most recent algorithm' do + login_as(:bob) - expect(controller).not_to receive(:send_file) - expect_any_instance_of(CrudController).to receive(:render_entry).exactly(:once) + encryptable_file = prepare_transferred_encryptable(Crypto::Symmetric::Aes256iv) - get :show, params: { id: encryptable_file.id, - encryptable: ActionController::Parameters.new({ - test: 1 - }).permit! }, xhr: true + login_as(:alice) + + expect(controller).not_to receive(:send_file) + expect_any_instance_of(CrudController).to receive(:render_entry).exactly(:once) + + get :show, params: { id: encryptable_file.id }, xhr: true + end end end @@ -805,12 +830,13 @@ def create_file file end - def prepare_transferred_encryptable + def prepare_transferred_encryptable(encryption_algorithm) encryptable_file = Encryptable::File.new(name: 'file', + folder_id: alice.inbox_folder.id, cleartext_file: file_fixture('test_file.txt').read, content_type: 'text/plain') - transfer_password = Crypto::Symmetric::Aes256.random_key + transfer_password = encryption_algorithm.random_key encryptable_file.encrypt(transfer_password) diff --git a/spec/controllers/api/teams_controller_spec.rb b/spec/controllers/api/teams_controller_spec.rb index 8db35393d..fd282b0ec 100644 --- a/spec/controllers/api/teams_controller_spec.rb +++ b/spec/controllers/api/teams_controller_spec.rb @@ -14,6 +14,7 @@ let!(:team4) { Fabricate(:non_private_team) } let(:bobs_private_key) { bob.decrypt_private_key('password') } let!(:team3_user) { team3.teammembers.first.user } + let(:test_file) { FixturesHelper.read_file('test_file.txt') } context 'GET index' do it 'should get team for search term' do @@ -217,8 +218,8 @@ expect(encryptables.last['attributes']['name']).to eq(filename1) expect(encryptables.last['attributes']['name']).not_to eq(filename2) - expect(encryptables.count).to be(3) - expect(included.size).to be(4) + expect(encryptables.count).to be(2) + expect(included.size).to be(3) end it 'returns sender_name if transferred encryptable' do @@ -267,6 +268,7 @@ team3_attributes['favourised'] = false team3_attributes['deletable'] = false team3_attributes['personal_team'] = false + team3_attributes['password_bitsize'] = 256 expect(team3_attributes).to include(attributes_team) folder_relationships_length = data.first['relationships']['folders']['data'].size @@ -381,6 +383,122 @@ end end end + + context 'Recrypt' do + it 'triggers team recrypt if latest algorithm is not applied on team' do + admin = users(:admin) + login_as(:admin) + api_user = admin.api_users.create! + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + team = Fabricate(:non_private_team) + + # Add api user to team + admins_private_key = admin.decrypt_private_key('password') + plaintext_team_password = team.decrypt_team_password(admin, admins_private_key) + team.add_user(api_user, plaintext_team_password) + + expect(team.encryption_algorithm).to eq('AES256') + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + get :index, params: { team_id: team.id }, xhr: true + + expect(response.status).to be(200) + + team.reload + expect(team.encryption_algorithm).to eq('AES256IV') + end + + it 'does not recrypt if latest algorithm' do + login_as(:bob) + + get :index, params: { team_id: team1.id }, xhr: true + + expect(team1.encryption_algorithm).to eq('AES256IV') + + expect(response.status).to be(200) + + team1.reload + expect(team1.encryption_algorithm).to eq('AES256IV') + end + end + + context 'Receive transferred encryptables' do + it 'Receive method gets called to encrypt transferred encryptable' do + login_as(:bob) + + Fabricate( + :transferred_file, + id_sender: alice.id, + receiver_inbox_folder: bob.inbox_folder, + receiver_pk: bob.public_key, + encryption_algorithm: Crypto::Symmetric::Aes256 + ) + + expect_any_instance_of(EncryptableTransfer) + .to receive(:receive) + .exactly(1).times + .and_return(nil) + + default_folder = Folder.new(name: 'default', team: personal_team_bob) + + personal_team_bob.folders = [default_folder, bob.inbox_folder] + + personal_team_bob.encryption_algorithm = 'AES256' + personal_team_bob.save! + + expect do + get :index, params: { team_id: personal_team_bob.id }, xhr: true + end.to raise_error(RuntimeError) + + end + + it 'Receive method to encrypt transferred encryptable dont get called on recent algorithm' do + login_as(:bob) + + Fabricate( + :transferred_file, + id_sender: alice.id, + receiver_inbox_folder: bob.inbox_folder, + receiver_pk: bob.public_key, + encryption_algorithm: Crypto::Symmetric::Aes256iv + ) + + expect_any_instance_of(EncryptableTransfer) + .not_to receive(:receive) + .and_return(nil) + + default_folder = Folder.new(name: 'default', team: personal_team_bob) + + personal_team_bob.folders = [default_folder, bob.inbox_folder] + + get :index, params: { team_id: personal_team_bob.id }, xhr: true + end + + it 'It does not receive encryptables in personal team if they are not transferred' do + login_as(:bob) + + params = {} + params[:name] = 'Shopping Account' + params[:folder_id] = bob.inbox_folder.id + params[:type] = 'Encryptable::Credentials' + params[:cleartext_username] = 'username' + Encryptable::Credentials.new(params) + + expect_any_instance_of(EncryptableTransfer) + .not_to receive(:receive) + .exactly(1).times + .and_return(nil) + + default_folder = Folder.new(name: 'default', team: personal_team_bob) + + personal_team_bob.folders = [default_folder, bob.inbox_folder] + + get :index, params: { team_id: personal_team_bob.id }, xhr: true + end + end end context 'PUT update' do diff --git a/spec/fabricators/encryptables/credentials_fabricator.rb b/spec/fabricators/encryptables/credentials_fabricator.rb index f78acd1f5..e52163a71 100644 --- a/spec/fabricators/encryptables/credentials_fabricator.rb +++ b/spec/fabricators/encryptables/credentials_fabricator.rb @@ -5,12 +5,66 @@ # See the COPYING file at the top-level directory or at # https://github.com/puzzle/cryptopus. -Fabricator(:credential, from: 'Encryptable::Credentials') do +Fabricator(:credential_all_attrs, from: 'Encryptable::Credentials') do transient :team_password name { Faker::Team.creature } cleartext_username { Faker::Internet.user_name } cleartext_password { Faker::Internet.password } - before_create do |account, attrs| - account.encrypt(attrs[:team_password]) + cleartext_token { Faker::Internet.uuid } + cleartext_pin { Faker::Internet.ip_v4_address } + cleartext_email { Faker::Internet.email } + cleartext_custom_attr_label { Faker::Internet.user_name } + cleartext_custom_attr { Faker::Internet.password } + before_create do |encryptable, attrs| + encryptable.encrypt(attrs[:team_password]) + end +end + +Fabricator(:credential_one_attr, from: 'Encryptable::Credentials') do + transient :team_password + name { Faker::Team.creature } + cleartext_token { Faker::Internet.uuid } + before_create do |encryptable, attrs| + encryptable.encrypt(attrs[:team_password]) + end +end + +Fabricator(:credential_no_attr, from: 'Encryptable::Credentials') do + transient :team_password + name { Faker::Team.creature } + before_create do |encryptable, attrs| + encryptable.encrypt(attrs[:team_password]) + end +end + +Fabricator(:file, from: 'Encryptable::File') do + transient :team_password + name { Faker::File.file_name } + cleartext_file { Faker::Hacker.say_something_smart } + before_create do |encryptable, attrs| + encryptable.encrypt(attrs[:team_password]) + end +end + +Fabricator(:transferred_file, from: 'Encryptable::File') do + transient :id_sender, :receiver_inbox_folder, :receiver_pk, :encryption_algorithm + + name { Faker::File.file_name } + cleartext_file { Faker::Hacker.say_something_smart } + folder { |attrs| attrs[:receiver_inbox_folder] } + sender_id { |attrs| attrs[:id_sender] } + type { Encryptable::File } + + before_create do |encryptable, attrs| + transfer_password = attrs[:encryption_algorithm].random_key + + encrypted_transfer_password = Crypto::Rsa.encrypt( + transfer_password, + attrs[:receiver_pk] + ) + + encryptable.encrypted_transfer_password = Base64.encode64(encrypted_transfer_password) + encryptable.encrypt(transfer_password, Crypto::Symmetric::Aes256iv) + end end diff --git a/spec/fabricators/teams_fabricator.rb b/spec/fabricators/teams_fabricator.rb index 4631b198c..df8f67c27 100644 --- a/spec/fabricators/teams_fabricator.rb +++ b/spec/fabricators/teams_fabricator.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../app/utils/crypto/symmetric/aes256' - Fabricator(:non_private_team, from: :team) do |t| t.name { Faker::App.name } t.description { Faker::Hacker.say_something_smart } @@ -9,13 +7,32 @@ t.private false t.type Team::Shared after_create do |team| - team_password = Crypto::Symmetric::Aes256.random_key + team_password = team.new_team_password + team.add_user(Fabricate(:user), team_password) + User::Human.admins.each do |a| + team.add_user(a, team_password) + end + folder = Fabricate(:folder, team: team) + Fabricate(:credential_all_attrs, folder: folder, team_password: team_password) + end +end + +Fabricator(:old_encryption_algo_team, from: :team) do |t| + t.name { Faker::App.name } + t.description { Faker::Hacker.say_something_smart } + t.visible true + t.private false + t.type Team::Shared + t.encryption_algorithm 'AES256' + after_create do |team| + team_password = team.new_team_password team.add_user(Fabricate(:user), team_password) User::Human.admins.each do |a| team.add_user(a, team_password) end folder = Fabricate(:folder, team: team) - Fabricate(:credential, folder: folder, team_password: team_password) + Fabricate(:credential_one_attr, folder: folder, team_password: team_password) + Fabricate(:credential_no_attr, folder: folder, team_password: team_password) end end @@ -24,11 +41,12 @@ t.description { Faker::Hacker.say_something_smart } t.visible true t.private true + t.encryption_algorithm 'AES256IV' t.type Team::Shared after_create do |team| - team_password = Crypto::Symmetric::Aes256.random_key + team_password = team.new_team_password team.add_user(Fabricate(:user), team_password) folder = Fabricate(:folder, team: team) - Fabricate(:credential, folder: folder, team_password: team_password) + Fabricate(:credential_all_attrs, folder: folder, team_password: team_password) end end diff --git a/spec/fixtures/encryptables.yml b/spec/fixtures/encryptables.yml index 3046b38a8..2743926f7 100644 --- a/spec/fixtures/encryptables.yml +++ b/spec/fixtures/encryptables.yml @@ -23,8 +23,8 @@ credentials1: name: Personal Mailbox description: Mailprovider One folder: folder1 - encrypted_data: '{"password":{"iv":null,"data":"pulO7xz5jDwUVQzbOqJzIw=="},"username":{"iv":null,"data":"0CkUu2Bd9eNB4OCuXVC3TA=="},"token":{"iv":null,"data":"s7L8uN3B82rE0jfxbZTBLA=="} - ,"pin":{"iv":null,"data":"hxFbaVAiXsYU41KWAsguxw=="},"email":{"iv":null,"data":"iQAeALndU+eKpkGwGNDHgw=="},"custom_attr":{"iv":null, "label":"Access Code","data":"6qN+5x+eYPekkZB/mfpgBg=="}}' + encrypted_data: '{"password":{"iv":"H9hjp4QeXwag2Py5BbIUsw==","data":"vDItEQ+iQ1e55wcu9yPeSw=="},"username":{"iv":"ANa+aisVyKLEZs660fBa4w==","data":"Go6I8GJMejqRWS8QxfL+iA=="},"token":{"iv":"eVqNiT+wZXHy7FB8vl76kg==","data":"HQqUtWGazCQue+uTloDGyA=="} + ,"pin":{"iv":"80e9BgLcDlJzmitc+KydAQ==","data":"4uXc5coIBOQ2CYSLQOeu6A=="},"email":{"iv":"ybQu7D5HgofF2WKkfrmv9w==","data":"QNQRKGFpN49nLN0MImhp6A=="},"custom_attr":{"iv":"iC1hdqLGuzeGXHmXFP6qmQ==", "label":"Access Code","data":"Bz2mauB+jEbVinb6nPsoQQ=="}}' sender_id: null encrypted_transfer_password: null @@ -33,7 +33,7 @@ credentials2: name: Twitter Account description: My personal twitter account folder: folder2 - encrypted_data: '{"password":{"iv":null,"data":"X2i8woXXwIHew6zcnBws9Q=="},"username":{"iv":null,"data":"Kvkd66uUiNq4Gw4Yh7PvVg=="}}' + encrypted_data: '{"password":{"iv":"fY77ROMiOCbMrpxSMiciTA==","data":"MP70lYnYplRoNw0XfUAYIQ=="},"username":{"iv":"2vjsDqePJPuYCWSzn1+OSA==","data":"Y3Q7oUop9SuPhH79r6mTBA=="}}' file1: type: Encryptable::File @@ -41,12 +41,4 @@ file1: description: One-Time access codes encryptable_credential: credentials1 content_type: 'text/plain' - encrypted_data: '{"file":{"iv":null,"data":"FvJS9jooGEX1aXqB0iP7wB6h2YwO479OM+RpNmBlbORivbVPky0rR4u3lNLN+DGIL93gQAlVHDw1CZe9zDoTgSyxsQFflQwGk3DMDS9xhoQSJzTkBPBIb33j9H7WG37CQwdNNFnn/NExiBZb+9dbmHGqw8KWvRd3Xd/oSlTr6w/c0gz3UEYfhC5l6P3xnDc2"}}' - -transferred_file1: - type: Encryptable::File - name: message.txt - description: Hello Alice here are xy - folder: inbox_folder_alice - content_type: 'text/plain' - encrypted_data: '{"file":{"iv":null,"data":"FvJS9jooGEX1aXqB0iP7wB6h2YwO479OM+RpNmBlbORivbVPky0rR4u3lNLN+DGIL93gQAlVHDw1CZe9zDoTgSyxsQFflQwGk3DMDS9xhoQSJzTkBPBIb33j9H7WG37CQwdNNFnn/NExiBZb+9dbmHGqw8KWvRd3Xd/oSlTr6w/c0gz3UEYfhC5l6P3xnDc2"}}' + encrypted_data: '{"file":{"iv":"JYwyW8IlX8dGttecsI/ImA==","data":"wkx7Ep10Ofe626QW8faqu64bDoQQm6Lf7w4hHWRU/vtkv+DeMWaZoLnK5n0rb21H7b9KOnYLWKGgohOaBEHiS1I8wML7IAi+mDSXQGvpooFF6AsWRFBRJMpetRIATzwwxxf+3le4G+u7L3U4NUH/KKdX5NrGUcjk3Nq2/PdCo4jkpLGKJVTgy8mNUWomgguC"}}' diff --git a/spec/fixtures/files/encryptables/legacy_ose_secret.encrypted_data b/spec/fixtures/files/encryptables/legacy_ose_secret.encrypted_data new file mode 100644 index 000000000..ea6b9e601 --- /dev/null +++ b/spec/fixtures/files/encryptables/legacy_ose_secret.encrypted_data @@ -0,0 +1 @@ +9zUb++e6Pj7BLmCmetSwalSVvxXB2UmregR9Ldvu/NmkErDSRWck+RKjBoZWKHeSOeAeNl+tHRTlF4Q6jU5HECceAckWWPy7wWfCtZKrkss94CfJf68L8tHnAyfPnx9/VvNiomFWbUvc3IjvIDLwGXLRIn5HcAxbr+ryTYksT4EEKpRxVEihG96MrooPVFobg0/Ot/bs1tHAiYzjNoWPlBaBWzMzLP4rAvT54Lb0f5B3GZOvwmoZw7nxHP8Wy4ktH/IIQizH6In3Jk2meYsMbJyPEYvTr3g1c0IPxPURB6p+5VBe1ueDPs50YoNPz7E1L4anP3iAssIz7gZC+eUhYwwqfpewiZHLXFyWZoXXtW4UesSdVQWlV1Ih2jbV34LontH7NwlVmoXj8N6GIk4b2HqKfUkRsSE4furRlen5UEtfAPyoWsXWkmgqwKY2XfmcbIoO7d5XdGXEuMbSZsrCDUjHI+WF5JkXW7om0aJ5LehnCFvlwdN74NJWyosRKpnkKz/R+tcCinNJ9DYUeMZYSn+CPaJ2KqkMBjKjJgvy7/fOw3Du8IFVY+qsOubxnBi+gMFLdv7dTm5Y1MIN2hSZJFL3T810m/WUTAxd0wOQPinuRVpeNy4NoluNbdRkyWXIGx1Rev65k5CeR/bYnnlnoO6EmbA7/UukgpnQKHngL8ttHImV7LypqT6XZ7DGqb3bIJeR01mqNBOmGhP1LEQKEJNP2/UaRI1uvj28vOxKAaFjllsLuKb4fxk+dyxMfc/AFTOiDA2ld6Si+miBiLt+OumSz1fL6ijQpL0zghtNWnWk0VP4c++r3e/1YKvyJqwWA3aL+PyjTmu71WxR/yNwxEMjyp5UZVEjVGw+xZX3ZGUUK8vpnwpNHJMzGYjsi8aH5SCRMDhu98N0DXDbmlsmyKVpF8/Vl/BKn7nw4Xmfw7iM2YrnG7ulkPcTUno2KEwxydXFcKh4RpH49LJZLYoGhEzn/5Z/fS/LQBCkhfOt1fVHTCJWdyLkKPnXCxat0hby9VDAQ0MbeFUBZmsXEqukAI/7mKjrAq/vUsYKdcTl9ZOFvVcfdEkUFDsy5iv/uMghoA2dcFWLw0ehsosEvkHRoBEhyoY8nLW13WZl//Y1LWFgLg4EPUEbjGMGYXpF2op4F7+ahgtwbQS6jEtD9vBr0jUvurc2adDsru0IvVfdyAklhvtsVuHBSP6usvdhG7agbYeI769PRwZ0Z9YrIhZhnnaGD9hPni1tIQ9wyw3Trbqz+i1fbYJVtQyWx96np5eR/3IaydwTg5pVEh7fKukvUWOFwBmrdsX9vlE48/YMhjRtINouTWuWeWlpQvf/D1PuG9pDZ29qqJ5xIxmHb+gc/kB8w5vLQ7o/u35xGhSwHtDhwTEIa2KfTc7w4mQu1CJuDZz/1QHhNP6ZnvNqWiAdZlZUVJ3o+YWgCqMX1Y/sERf9vxSW3RkTCVm2xXppcwTKoCi4Y+9B9/5Hk5zanN1GMVu5dYFCjFv36s430JEttpkWEA1Oqe4AfZfvMsnTjLqG \ No newline at end of file diff --git a/spec/fixtures/files/users/admin/team1_password.crypt b/spec/fixtures/files/users/admin/team1_password.crypt index bac01d7dc..8a8406031 100644 Binary files a/spec/fixtures/files/users/admin/team1_password.crypt and b/spec/fixtures/files/users/admin/team1_password.crypt differ diff --git a/spec/fixtures/files/users/alice/team1_password.crypt b/spec/fixtures/files/users/alice/team1_password.crypt index a7b8fae35..e871df083 100644 Binary files a/spec/fixtures/files/users/alice/team1_password.crypt and b/spec/fixtures/files/users/alice/team1_password.crypt differ diff --git a/spec/fixtures/files/users/bob/team1_password.crypt b/spec/fixtures/files/users/bob/team1_password.crypt index 6072c8850..c8d5000d2 100644 Binary files a/spec/fixtures/files/users/bob/team1_password.crypt and b/spec/fixtures/files/users/bob/team1_password.crypt differ diff --git a/spec/fixtures/files/users/bob/team2_password.crypt b/spec/fixtures/files/users/bob/team2_password.crypt index a918d7026..2821bd0bf 100644 Binary files a/spec/fixtures/files/users/bob/team2_password.crypt and b/spec/fixtures/files/users/bob/team2_password.crypt differ diff --git a/spec/fixtures/files/users/root/team1_password.crypt b/spec/fixtures/files/users/root/team1_password.crypt index 1cf86191b..7448ce1d5 100644 Binary files a/spec/fixtures/files/users/root/team1_password.crypt and b/spec/fixtures/files/users/root/team1_password.crypt differ diff --git a/spec/fixtures/teammembers.yml b/spec/fixtures/teammembers.yml index 642fa0b14..7d4677391 100644 --- a/spec/fixtures/teammembers.yml +++ b/spec/fixtures/teammembers.yml @@ -18,44 +18,44 @@ team1_bob: team: team1 user: bob - password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'team1')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'team1')}\"" %> team1_root: team: team1 user: root - password: <%= "!!binary \"#{FixturesHelper.read_team_password('root', 'team1')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('root', 'team1')}\"" %> team1_alice: team: team1 user: alice - password: <%= "!!binary \"#{FixturesHelper.read_team_password('alice', 'team1')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('alice', 'team1')}\"" %> team1_admin: - team: team1 - user: admin - password: <%= "!!binary \"#{FixturesHelper.read_team_password('admin', 'team1')}\"" %> + team: team1 + user: admin + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('admin', 'team1')}\"" %> team2_bob: - team: team2 - user: bob - password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'team2')}\"" %> + team: team2 + user: bob + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'team2')}\"" %> bob_personal_team: team: personal_team_bob user: bob - password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'personal_team')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('bob', 'personal_team')}\"" %> alice_personal_team: team: personal_team_alice user: alice - password: <%= "!!binary \"#{FixturesHelper.read_team_password('alice', 'personal_team')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('alice', 'personal_team')}\"" %> admin_personal_team: team: personal_team_admin user: admin - password: <%= "!!binary \"#{FixturesHelper.read_team_password('admin', 'personal_team')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('admin', 'personal_team')}\"" %> root_personal_team: team: personal_team_root user: root - password: <%= "!!binary \"#{FixturesHelper.read_team_password('root', 'personal_team')}\"" %> + encrypted_team_password: <%= "!!binary \"#{FixturesHelper.read_team_password('root', 'personal_team')}\"" %> diff --git a/spec/fixtures/teams.yml b/spec/fixtures/teams.yml index 8b346be3a..1896e52bf 100644 --- a/spec/fixtures/teams.yml +++ b/spec/fixtures/teams.yml @@ -22,6 +22,7 @@ team1: description: public visible: true private: false + encryption_algorithm: 'AES256IV' team2: type: Team::Shared @@ -29,33 +30,39 @@ team2: description: public visible: true private: true + encryption_algorithm: 'AES256IV' personal_team_bob: type: Team::Personal name: personal-team private: true personal_owner: bob + encryption_algorithm: 'AES256IV' personal_team_alice: type: Team::Personal name: personal-team private: true personal_owner: alice + encryption_algorithm: 'AES256IV' personal_team_root: type: Team::Personal name: personal-team private: true personal_owner: root + encryption_algorithm: 'AES256IV' personal_team_admin: type: Team::Personal name: personal-team private: true personal_owner: admin + encryption_algorithm: 'AES256IV' personal_team_conf_admin: type: Team::Personal name: personal-team private: true personal_owner: conf_admin + encryption_algorithm: 'AES256IV' diff --git a/spec/migrations/move_file_entries_to_encryptable_files_spec.rb.disabled b/spec/migrations/move_file_entries_to_encryptable_files_spec.rb.disabled index d78cac9de..97d96fd77 100644 --- a/spec/migrations/move_file_entries_to_encryptable_files_spec.rb.disabled +++ b/spec/migrations/move_file_entries_to_encryptable_files_spec.rb.disabled @@ -143,13 +143,14 @@ describe MoveFileEntriesToEncryptableFiles do def encrypt(team_password) return if cleartext_file.blank? - self.file = ::Crypto::Symmetric::Aes256.encrypt(cleartext_file, team_password) + self.file = ::Crypto::Symmetric::Aes256.encrypt(cleartext_file, team_password)[:data] end def decrypt(team_password) return if file.blank? - self.cleartext_file = ::Crypto::Symmetric::Aes256.decrypt(file, team_password) + encrypted_values = { data: file, iv: nil } + self.cleartext_file = ::Crypto::Symmetric::Aes256.decrypt(encrypted_values, team_password) end end # rubocop:enable Lint/ConstantDefinitionInBlock diff --git a/spec/migrations/use_encrypted_data_for_account_credentials_spec.rb.disabled b/spec/migrations/use_encrypted_data_for_account_credentials_spec.rb.disabled index 6aa79e21c..9e890eab7 100644 --- a/spec/migrations/use_encrypted_data_for_account_credentials_spec.rb.disabled +++ b/spec/migrations/use_encrypted_data_for_account_credentials_spec.rb.disabled @@ -172,7 +172,8 @@ class LegacyAccountCredentialsBefore < ApplicationRecord crypted_value = send(attr) return if crypted_value.blank? - Crypto::Symmetric::Aes256.decrypt(crypted_value, team_password) + encrypted_values = { data: crypted_value, iv: nil } + Crypto::Symmetric::Aes256.decrypt(encrypted_values, team_password) end end @@ -193,10 +194,12 @@ class LegacyAccountCredentialsAfter < ApplicationRecord private def decrypt_attr(attr, team_password) - encrypted_value = encrypted_data[attr].try(:[], :data) + data = encrypted_data[attr].try(:[], :data) + iv = encrypted_data[attr].try(:[], :iv) - cleartext_value = if encrypted_value - Crypto::Symmetric::Aes256.decrypt(encrypted_value, team_password) + encrypted_values = { data: data, iv: iv } + cleartext_value = if data.present? + Crypto::Symmetric::Aes256.decrypt(encrypted_values, team_password) end instance_variable_set("@cleartext_#{attr}", cleartext_value) diff --git a/spec/models/encryptable_spec.rb b/spec/models/encryptable_spec.rb index 67fa7c0c7..ddbe80784 100644 --- a/spec/models/encryptable_spec.rb +++ b/spec/models/encryptable_spec.rb @@ -8,6 +8,7 @@ let(:bobs_private_key) { bob.decrypt_private_key('password') } let(:encryptable) { encryptables(:credentials1) } let(:team) { teams(:team1) } + let(:team_password) { team.decrypt_team_password(bob, bobs_private_key) } it 'does not create second credential in same folder' do params = {} @@ -39,8 +40,6 @@ end it 'decrypts all attributes' do - team_password = team.decrypt_team_password(bob, bobs_private_key) - encryptable.decrypt(team_password) expect(encryptable.cleartext_username).to eq('test') @@ -53,8 +52,6 @@ end it 'updates all attributes' do - team_password = team.decrypt_team_password(bob, bobs_private_key) - encryptable.cleartext_username = 'new' encryptable.cleartext_password = 'foo' encryptable.cleartext_token = 'boo' @@ -77,6 +74,17 @@ expect(encryptable.cleartext_custom_attr).to eq('yoo') end + it 'removes attribute by saving nil value to database' do + encryptable.cleartext_username = nil + encryptable.encrypt(team_password) + encryptable.save! + + encryptable.reload + encryptable.decrypt(team_password) + + expect(encryptable.cleartext_username).to eq(nil) + end + it 'does not create credential if name is empty' do params = {} params[:name] = '' diff --git a/spec/models/user/human_spec.rb b/spec/models/user/human_spec.rb index 7e9624132..f48da50b4 100644 --- a/spec/models/user/human_spec.rb +++ b/spec/models/user/human_spec.rb @@ -95,7 +95,7 @@ context 'encryptables' do it 'only returns encryptables where alice is member' do encryptables = alice.encryptables - expect(encryptables.count).to eq(2) + expect(encryptables.count).to eq(1) expect(encryptables.first.name).to eq('Personal Mailbox') end end diff --git a/spec/support/test_helper.rb b/spec/support/test_helper.rb index 3a9db9d05..b13436e80 100644 --- a/spec/support/test_helper.rb +++ b/spec/support/test_helper.rb @@ -58,9 +58,9 @@ def oidc_settings # static team passwords extracted from fixtures def team1_password - Base64.strict_decode64('LPTDTUOnL201Fn24GYP8ZRpE79m9ucBY8cF/tcCKcCs=') + Base64.strict_decode64('lxJqm1TGdue4y9b/njzh+vKtDXESeywOpu+kPp7qTJ8=') end def team2_password - Base64.strict_decode64('Xyj5d0yF9D/XOCIi9Iz5bsgNs9KvvcKkJAtCsoENNj4=') + Base64.strict_decode64('A/UIOlRXNYTZDWsRBZmHwuTiujU/2HRi5rIQ8QNvWMA=') end diff --git a/spec/unit/serializers/folder_serializer_spec.rb b/spec/unit/serializers/folder_serializer_spec.rb index 9be8a4bf4..39e675a07 100644 --- a/spec/unit/serializers/folder_serializer_spec.rb +++ b/spec/unit/serializers/folder_serializer_spec.rb @@ -7,23 +7,21 @@ let(:bob) { users(:bob) } - context 'No files transferred' do + context 'Transferred count' do - it 'should return 0 unread transferred files' do + it 'returns 0 if no transferred files in inbox folder' do as_json = JSON.parse(FolderSerializer.new(folders(:inbox_folder_alice)).to_json) expect(as_json['unread_transferred_count']).to eq(0) end - end - - context 'Some files transferred' do - it 'should return 1 unread transferred file' do + it 'returns 1 if unread transferred file present in inbox folder' do encryptable_file = Encryptable::File.new(name: 'file', + folder_id: alice.inbox_folder.id, cleartext_file: file_fixture('test_file.txt').read, content_type: 'text/plain') - transfer_password = Crypto::Symmetric::Aes256.random_key + transfer_password = Crypto::Symmetric::Aes256iv.random_key encryptable_file.encrypt(transfer_password) @@ -40,5 +38,11 @@ expect(as_json['unread_transferred_count']).to eq(1) end + + it 'does not return count for non inbox folder' do + as_json = JSON.parse(FolderSerializer.new(folders(:folder2)).to_json) + + expect(as_json['unread_transferred_count']).to eq(nil) + end end end diff --git a/spec/unit/serializers/team_list_serializer_spec.rb b/spec/unit/serializers/team_list_serializer_spec.rb index 05c618944..c4d4255dc 100644 --- a/spec/unit/serializers/team_list_serializer_spec.rb +++ b/spec/unit/serializers/team_list_serializer_spec.rb @@ -7,29 +7,21 @@ let(:bob) { users(:bob) } - context 'No files transferred' do + context 'Transferred count' do - it 'should return 0 unread transferred files' do + it 'returns 0 if no transferred files in inbox folder' do as_json = JSON.parse(TeamListSerializer.new(teams(:personal_team_alice)).to_json) expect(as_json['unread_count']).to eq(0) end - it 'should return nil unread transferred files when no personal team' do - as_json = JSON.parse(TeamListSerializer.new(teams(:team2)).to_json) - - expect(as_json['unread_count']).to eq(nil) - end - end - - context 'Some files transferred' do - - it 'should return 1 unread transferred file' do + it 'returns 1 if unread transferred file present in inbox folder' do encryptable_file = Encryptable::File.new(name: 'file', + folder_id: alice.inbox_folder.id, cleartext_file: file_fixture('test_file.txt').read, content_type: 'text/plain') - transfer_password = Crypto::Symmetric::Aes256.random_key + transfer_password = Crypto::Symmetric::Aes256iv.random_key encryptable_file.encrypt(transfer_password) @@ -46,5 +38,11 @@ expect(as_json['unread_count']).to eq(1) end + + it 'does not return count for non personal team' do + as_json = JSON.parse(TeamListSerializer.new(teams(:team2)).to_json) + + expect(as_json['unread_count']).to eq(nil) + end end end diff --git a/spec/unit/utils/crypto/rsa_spec.rb b/spec/unit/utils/crypto/rsa_spec.rb index bd032cb34..9d0c4a891 100644 --- a/spec/unit/utils/crypto/rsa_spec.rb +++ b/spec/unit/utils/crypto/rsa_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_relative '../../../../app/utils/crypto/rsa' -require_relative '../../../../app/utils/crypto/symmetric/aes256' +require_relative '../../../../app/utils/crypto/symmetric' describe Crypto::Rsa do let(:keypair) { Crypto::Rsa.generate_new_keypair } diff --git a/spec/unit/utils/crypto/symmetric/aes256_spec.rb b/spec/unit/utils/crypto/symmetric/aes256_spec.rb index 939898f08..58e26f1ad 100644 --- a/spec/unit/utils/crypto/symmetric/aes256_spec.rb +++ b/spec/unit/utils/crypto/symmetric/aes256_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' +require_relative '../../../../../app/utils/crypto/symmetric' require_relative '../../../../../app/utils/crypto/symmetric/aes256' require_relative '../../../../../app/utils/crypto/rsa' diff --git a/spec/unit/utils/crypto/symmetric/aes256iv_spec.rb b/spec/unit/utils/crypto/symmetric/aes256iv_spec.rb index f0149782d..00fb2b446 100644 --- a/spec/unit/utils/crypto/symmetric/aes256iv_spec.rb +++ b/spec/unit/utils/crypto/symmetric/aes256iv_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' +require_relative '../../../../../app/utils/crypto/symmetric' +require_relative '../../../../../app/utils/crypto/symmetric/aes256' require_relative '../../../../../app/utils/crypto/symmetric/aes256iv' describe Crypto::Symmetric::Aes256iv do @@ -14,9 +16,8 @@ key = team_password data = Base64.strict_decode64('test') encrypted_values = described_class.encrypt(data, key) - encrypted_data, iv = encrypted_values - result = described_class.decrypt(encrypted_data, key, iv) + result = described_class.decrypt(encrypted_values, key) encoded_result = Base64.strict_encode64(result) expect(encoded_result).to eq('test') end diff --git a/spec/unit/utils/crypto/symmetric/recrypt_spec.rb b/spec/unit/utils/crypto/symmetric/recrypt_spec.rb new file mode 100644 index 000000000..189a0a74a --- /dev/null +++ b/spec/unit/utils/crypto/symmetric/recrypt_spec.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Crypto::Symmetric::Recrypt do + let(:admin) { users(:admin) } + let(:bob) { users(:admin) } + let(:admin_pk) { admin.decrypt_private_key('password') } + let(:bob_pk) { bob.decrypt_private_key('password') } + let(:fabricate_team) { Fabricate(:non_private_team) } + let(:old_algo_team) { Fabricate(:old_encryption_algo_team) } + + it 'does recrypt team encryptable if only token is set as attribute' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + expect(old_algo_team).to be_persisted + expect(old_algo_team.read_attribute(:encryption_algorithm)).to eq 'AES256' + expect(old_algo_team.encryption_algorithm).to eq 'AES256' + expect(old_algo_team.recrypt_state).to eq 'done' + + team_password = old_algo_team.decrypt_team_password(admin, admin_pk) + encryptable = old_algo_team.encryptables.first + encryptable.decrypt(team_password) + encryptable_token = encryptable.cleartext_token + expect(encryptable_token).to be_present + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + described_class.new(admin, old_algo_team, admin_pk).perform + + old_algo_team.reload + expect(old_algo_team.read_attribute(:encryption_algorithm)).to eq 'AES256IV' + expect(old_algo_team.encryption_algorithm).to eq 'AES256IV' + expect(old_algo_team.recrypt_state).to eq 'done' + + encryptable = Encryptable.find(encryptable.id) + new_team_password = old_algo_team.decrypt_team_password(admin, admin_pk) + expect(new_team_password).not_to eq(team_password) + encryptable.decrypt(new_team_password) + expect(encryptable.cleartext_token).to eq(encryptable_token) + + encrypted_data = encryptable.encrypted_data + expect(encrypted_data[:token][:iv]).to be_present + end + + it 'does recrypt team encryptable if all attributes are set' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + expect(fabricate_team).to be_persisted + expect(fabricate_team.read_attribute(:encryption_algorithm)).to eq 'AES256' + expect(fabricate_team.encryption_algorithm).to eq 'AES256' + expect(fabricate_team.recrypt_state).to eq 'done' + + team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + encryptable = fabricate_team.encryptables.first + encryptable.decrypt(team_password) + encryptable_username = encryptable.cleartext_username + encryptable_password = encryptable.cleartext_password + encryptable_token = encryptable.cleartext_token + encryptable_pin = encryptable.cleartext_pin + encryptable_email = encryptable.cleartext_email + encryptable_custom_label = encryptable.cleartext_custom_attr_label + encryptable_custom_attr = encryptable.cleartext_custom_attr + expect(encryptable_username).to be_present + expect(encryptable_password).to be_present + expect(encryptable_token).to be_present + expect(encryptable_pin).to be_present + expect(encryptable_email).to be_present + expect(encryptable_custom_label).to be_present + expect(encryptable_custom_attr).to be_present + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + described_class.new(admin, fabricate_team, admin_pk).perform + + fabricate_team.reload + expect(fabricate_team.read_attribute(:encryption_algorithm)).to eq 'AES256IV' + expect(fabricate_team.encryption_algorithm).to eq 'AES256IV' + expect(fabricate_team.recrypt_state).to eq 'done' + + encryptable = Encryptable.find(encryptable.id) + new_team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + expect(new_team_password).not_to eq(team_password) + encryptable.decrypt(new_team_password) + + expect(encryptable.cleartext_username).to eq(encryptable_username) + expect(encryptable.cleartext_password).to eq(encryptable_password) + expect(encryptable.cleartext_token).to eq(encryptable_token) + expect(encryptable.cleartext_pin).to eq(encryptable_pin) + expect(encryptable.cleartext_email).to eq(encryptable_email) + expect(encryptable.cleartext_custom_attr_label).to eq(encryptable_custom_label) + expect(encryptable.cleartext_custom_attr).to eq(encryptable_custom_attr) + + encrypted_data = encryptable.encrypted_data + expect(encrypted_data[:username][:iv]).to be_present + expect(encrypted_data[:password][:iv]).to be_present + expect(encrypted_data[:token][:iv]).to be_present + expect(encrypted_data[:pin][:iv]).to be_present + expect(encrypted_data[:email][:iv]).to be_present + expect(encrypted_data[:custom_attr][:iv]).to be_present + end + + it 'does recrypt team encryptable if no attribute is set' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + expect(old_algo_team).to be_persisted + expect(old_algo_team.read_attribute(:encryption_algorithm)).to eq 'AES256' + expect(old_algo_team.encryption_algorithm).to eq 'AES256' + expect(old_algo_team.recrypt_state).to eq 'done' + + team_password = old_algo_team.decrypt_team_password(admin, admin_pk) + encryptable = old_algo_team.encryptables.last + encryptable.decrypt(team_password) + encryptable_token = encryptable.cleartext_token + expect(encryptable_token).to be_nil + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + described_class.new(admin, old_algo_team, admin_pk).perform + + old_algo_team.reload + expect(old_algo_team.read_attribute(:encryption_algorithm)).to eq 'AES256IV' + expect(old_algo_team.encryption_algorithm).to eq 'AES256IV' + expect(old_algo_team.recrypt_state).to eq 'done' + + encryptable = Encryptable.find(encryptable.id) + new_team_password = old_algo_team.decrypt_team_password(admin, admin_pk) + expect(new_team_password).not_to eq(team_password) + encryptable.decrypt(new_team_password) + expect(encryptable.cleartext_token).to eq(encryptable_token) + + encrypted_data = encryptable.encrypted_data + expect(encrypted_data[:username]).to be_nil + expect(encrypted_data[:password]).to be_nil + expect(encrypted_data[:pin]).to be_nil + expect(encrypted_data[:token]).to be_nil + expect(encrypted_data[:email]).to be_nil + end + + it 'does not recrypt team encryptables if default algorithm is already in use' do + expect(fabricate_team).to be_persisted + # make sure encryption algorithm is persisted + expect(fabricate_team.read_attribute(:encryption_algorithm)).to eq 'AES256IV' + expect(fabricate_team.encryption_algorithm).to eq 'AES256IV' + expect(fabricate_team.recrypt_state).to eq 'done' + + team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + encryptable = fabricate_team.encryptables.where.not(name: 'broken encryptable').first + encryptable.decrypt(team_password) + + username = encryptable.cleartext_username + password = encryptable.cleartext_password + + described_class.new(admin, fabricate_team, admin_pk).perform + + expect(fabricate_team.encryption_algorithm).to eq 'AES256IV' + expect(fabricate_team.recrypt_state).to eq 'done' + + encryptable.reload.decrypt(team_password) + + expect(encryptable.cleartext_username).to eq(username) + expect(encryptable.cleartext_password).to eq(password) + end + + it 'aborts recrypt if error occurs' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + create_broken_encryptable(fabricate_team) + + team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + encryptable = fabricate_team.encryptables.where.not(name: 'broken encryptable').first + + encryptable.decrypt(team_password) + username = encryptable.cleartext_username + password = encryptable.cleartext_password + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + expect do + described_class.new(admin, fabricate_team, admin_pk).perform + end.to raise_error(RuntimeError, 'Recrypt failed: wrong final block length') + + expect(fabricate_team.reload.encryption_algorithm).to eq 'AES256' + expect(fabricate_team.recrypt_state).to eq 'failed' + + encryptable.reload.decrypt(team_password) + + expect(encryptable.cleartext_username).to eq(username) + expect(encryptable.cleartext_password).to eq(password) + end + + it 'resets teampassword with a newly generated for each teammember' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + old_team_passwords = fabricate_team.teammembers.pluck(:encrypted_team_password) + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + described_class.new(admin, fabricate_team, admin_pk).perform + + new_team_passwords = fabricate_team.teammembers.pluck(:encrypted_team_password) + + expect(new_team_passwords).not_to eq(old_team_passwords) + end + + it 'recrypts nested encryptable files' do + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256') + + team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + encryptable = fabricate_team.encryptables.first + file = Fabricate(:file, encryptable_credential: encryptable, team_password: team_password) + + team_password = fabricate_team.decrypt_team_password(admin, admin_pk) + file.decrypt(team_password) + cleartext_file = file.cleartext_file + expect(cleartext_file).to be_present + + stub_const('::Crypto::Symmetric::LATEST_ALGORITHM', 'AES256IV') + + described_class.new(admin, fabricate_team, admin_pk).perform + + file = Encryptable::File.find(file.id) + file.decrypt(fabricate_team.decrypt_team_password(admin, admin_pk)) + + expect(file.cleartext_file).to eq(cleartext_file) + end + + private + + def create_broken_encryptable(team) + broken_encryptable = Encryptable::Credentials.new + broken_encryptable.folder = team.folders.first + broken_encryptable.name = 'broken encryptable' + broken_encryptable.encrypted_data.[]=(:username, **{ iv: nil, data: 'broken' }) + broken_encryptable.encrypted_data.[]=(:password, **{ iv: nil, data: 'broken' }) + broken_encryptable.save! + end + +end diff --git a/spec/unit/utils/encryptable_move_handler_spec.rb b/spec/unit/utils/encryptable_move_handler_spec.rb index ef13f4cdc..34f6d9c21 100644 --- a/spec/unit/utils/encryptable_move_handler_spec.rb +++ b/spec/unit/utils/encryptable_move_handler_spec.rb @@ -15,7 +15,7 @@ private_key = decrypt_private_key(bob) target_folder = folders(:folder2) team_password = target_folder.team.decrypt_team_password(bob, private_key) - Fabricate(:credential, + Fabricate(:credential_all_attrs, folder: target_folder, team_password: team_password, name: 'credentials1') @@ -109,8 +109,8 @@ credential.save! - decrypted = credential.decrypt(new_folder.team.decrypt_team_password(bob, private_key)) - expect(decrypted).to eq('abc42-code-42') + credential.decrypt(new_folder.team.decrypt_team_password(bob, private_key)) + expect(credential.cleartext_custom_attr).to eq('abc42-code-42') expect(credential.folder).to eq(new_folder) end end