Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/701: Finish recrypt #728

Merged
merged 45 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6b06189
Start taking files from feature/593-recrypt
lkleisa Jun 23, 2023
ac85e5e
Work on taking files from feature/593-recrypt
lkleisa Jun 26, 2023
68953ca
Adjust backend so recrypt works also with encryptable transfer
lkleisa Jun 28, 2023
3807c09
Adjust translations
lkleisa Jun 28, 2023
b83a210
Adjust styles
lkleisa Jun 28, 2023
ebbabb2
Adjust encryptable show
lkleisa Jun 28, 2023
1b7d4db
Adjust rsa spec
lkleisa Jun 28, 2023
4dbb1fa
Add ember-loading to package.json
lkleisa Jun 28, 2023
2c44bdf
Describe some test cases for recrypt
lkleisa Jun 28, 2023
c35f931
Add iv to encryptable fixtures
lkleisa Jun 30, 2023
8d445aa
Add new credential fabricators for recrypt spec
lkleisa Jun 30, 2023
cee08ce
Add new team fabricator for recrypt spec
lkleisa Jun 30, 2023
40e3d23
Write new tests for recrypt and adjust existings
lkleisa Jun 30, 2023
de99d4f
Adjust test to new fabricators and recrypt logic
lkleisa Jun 30, 2023
f3adcf7
Clean up encryptable model
lkleisa Jun 30, 2023
c19e3bd
Fix rubocop offense
lkleisa Jul 3, 2023
16f7013
Align bstooltip in encryptable show
lkleisa Jul 3, 2023
093e731
Add tests for encryptable show and row
lkleisa Jul 3, 2023
07b5c84
Changed not used code back
lkleisa Jul 4, 2023
984e980
Remove unused fixture transferred_file
lkleisa Jul 4, 2023
6b07c4f
Add new encryption algorithm to fabricator
lkleisa Jul 4, 2023
1b27a1c
Use other translation in encryptable form
lkleisa Jul 4, 2023
6eeeac9
Add route to team index
lkleisa Jul 4, 2023
a561291
Remove unused code
lkleisa Jul 4, 2023
b0f652b
Receive transferred encryptable before recrypt
lkleisa Jul 4, 2023
50b0de5
Write tests for receive transferred encryptables before recrypt
lkleisa Jul 5, 2023
a05d5c6
Adjust tests for both algorithm types
lkleisa Jul 5, 2023
f25e3f0
Implement feedback
lkleisa Jul 6, 2023
d4a0920
Use bootstrap spinner
lkleisa Jul 6, 2023
1b97f72
Split up tests in encryptables_controller_spec
lkleisa Jul 6, 2023
5801658
Fix backend spec
lkleisa Jul 7, 2023
027a703
Beautify encrypt_with_encryption_class method
lkleisa Jul 7, 2023
c2530c0
Beautify lock icon on encryptable show
lkleisa Jul 10, 2023
769b7af
Implement feedback
lkleisa Jul 11, 2023
6783d7c
Adjust lock in encryptable show
lkleisa Jul 11, 2023
bcfc720
Use fabricator for transferred files
lkleisa Jul 11, 2023
4ae736d
Change css class of attribute field
lkleisa Jul 12, 2023
80fb9ef
Make rubocop happy
lkleisa Jul 12, 2023
30d9aa2
Fix rebase problems with credential sharing
lkleisa Jul 12, 2023
bb06de4
Remove rubocop disable
lkleisa Jul 12, 2023
f8ee95d
Remove encryption algorithm and bitsize from serializer
lkleisa Jul 12, 2023
f3dd06e
Clean up used attr handling in encryptable model
mtnstar Jul 18, 2023
439ddeb
Update Version and Changelog
mtnstar Jul 18, 2023
6611d59
Remove unused getter in encryptable model
mtnstar Jul 18, 2023
4e15aea
Make rubocop happy
mtnstar Jul 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.1
5.2
28 changes: 27 additions & 1 deletion app/controllers/api/teams_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
75 changes: 57 additions & 18 deletions app/models/encryptable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
class Encryptable < ApplicationRecord
serialize :encrypted_data, ::EncryptedData

class_attribute :used_encrypted_attrs

attr_readonly :type
validates :type, presence: true

Expand All @@ -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)
present_cleartext_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
Expand Down Expand Up @@ -75,30 +90,33 @@ def used_encrypted_data_attrs

private

def encrypt_attr(attr, team_password)
def present_cleartext_attrs
used_encrypted_attrs.select do |a|
send(:"cleartext_#{a}").present?
end
end

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
Expand All @@ -108,6 +126,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')
Expand Down
21 changes: 2 additions & 19 deletions app/models/encryptable/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
12 changes: 2 additions & 10 deletions app/models/encryptable/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions app/models/encrypted_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 34 additions & 3 deletions app/models/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?
Expand All @@ -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
2 changes: 1 addition & 1 deletion app/models/team/personal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/models/team/shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
10 changes: 4 additions & 6 deletions app/models/teammember.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = human.public_key
encrypted_team_password = Crypto::Rsa.encrypt(new_team_password, public_key)
self.encrypted_team_password = encrypted_team_password
save!
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/user/human.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion app/serializers/team_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -31,4 +32,8 @@ def deletable
def personal_team
object.personal_team?
end

def password_bitsize
object.encryption_class.password_bitsize
end
end
15 changes: 14 additions & 1 deletion app/utils/crypto/symmetric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Loading