main
1module TFA
2 class SecureStorage
3 def initialize(original, passphrase_request)
4 @original = original
5 @passphrase_request = passphrase_request
6 end
7
8 def encrypt!(algorithm = "AES-256-CBC")
9 cipher = OpenSSL::Cipher.new(algorithm)
10 cipher.encrypt
11 cipher.key = digest
12 cipher.iv = iv = cipher.random_iv
13 plain_text = IO.read(@original.path)
14 json = JSON.generate(
15 algorithm: algorithm,
16 iv: Base64.encode64(iv),
17 cipher_text: Base64.encode64(cipher.update(plain_text) + cipher.final),
18 )
19 IO.write(@original.path, json)
20 end
21
22 def decrypt!
23 data = JSON.parse(IO.read(@original.path), symbolize_names: true)
24 decipher = OpenSSL::Cipher.new(data[:algorithm])
25 decipher.decrypt
26 decipher.key = digest
27 decipher.iv = Base64.decode64(data[:iv])
28 plain_text = decipher.update(Base64.decode64(data[:cipher_text]))
29 IO.write(@original.path, plain_text + decipher.final)
30 end
31
32 def encrypted?
33 return false unless File.exist?(@original.path)
34 JSON.parse(IO.read(@original.path))
35 true
36 rescue JSON::ParserError
37 false
38 end
39
40 private
41
42 def method_missing(name, *args, &block)
43 super unless @original.respond_to?(name)
44
45 was_encrypted = encrypted?
46 if was_encrypted
47 encrypted_content = IO.read(@original.path)
48 decrypt!
49 original_sha256 = Digest::SHA256.file(@original.path)
50 end
51 result = @original.public_send(name, *args, &block)
52 if was_encrypted
53 new_sha256 = Digest::SHA256.file(@original.path)
54
55 if original_sha256 == new_sha256
56 IO.write(@original.path, encrypted_content)
57 else
58 encrypt!
59 end
60 end
61 result
62 end
63
64 def respond_to_missing?(method, *)
65 @original.respond_to?(method)
66 end
67
68 def digest
69 @digest ||= Digest::SHA256.digest(@passphrase_request.call)
70 end
71 end
72end