match/lib/match/encrypt.rb in fastlane-2.81.0.beta.20180206010002 vs match/lib/match/encrypt.rb in fastlane-2.81.0.beta.20180207010002
- old
+ new
@@ -1,13 +1,15 @@
require_relative 'module'
require_relative 'change_password'
module Match
class Encrypt
+ require 'base64'
+ require 'openssl'
+ require 'securerandom'
require 'security'
require 'shellwords'
- require 'open3'
def server_name(git_url)
["match", git_url].join("_")
end
@@ -44,24 +46,22 @@
Security::InternetPassword.delete(server: server_name(git_url))
end
def encrypt_repo(path: nil, git_url: nil)
iterate(path) do |current|
- crypt(path: current,
- password: password(git_url),
- encrypt: true)
+ encrypt(path: current,
+ password: password(git_url))
UI.success("🔒 Encrypted '#{File.basename(current)}'") if FastlaneCore::Globals.verbose?
end
UI.success("🔒 Successfully encrypted certificates repo")
end
def decrypt_repo(path: nil, git_url: nil, manual_password: nil)
iterate(path) do |current|
begin
- crypt(path: current,
- password: manual_password || password(git_url),
- encrypt: false)
+ decrypt(path: current,
+ password: manual_password || password(git_url))
rescue
UI.error("Couldn't decrypt the repo, please make sure you enter the right password!")
UI.user_error!("Invalid password passed via 'MATCH_PASSWORD'") if ENV["MATCH_PASSWORD"]
clear_password(git_url)
decrypt_repo(path: path, git_url: git_url)
@@ -79,51 +79,55 @@
next if File.directory?(path)
yield(path)
end
end
- def crypt(path: nil, password: nil, encrypt: true)
- if password.to_s.strip.length == 0 && encrypt
- UI.user_error!("No password supplied")
- end
+ # We encrypt with MD5 because that was the most common default value in older fastlane versions which used the local OpenSSL installation
+ # A more secure key and IV generation is needed in the future
+ # IV should be randomly generated and provided unencrypted
+ # salt should be randomly generated and provided unencrypted (like in the current implementation)
+ # key should be generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters
+ # Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550
+ def encrypt(path: nil, password: nil)
+ UI.user_error!("No password supplied") if password.to_s.strip.length == 0
- tmpfile = File.join(Dir.mktmpdir, "temporary")
- command = ["openssl aes-256-cbc"]
- command << "-k #{password.shellescape}"
- command << "-in #{path.shellescape}"
- command << "-out #{tmpfile.shellescape}"
- command << "-a"
- command << "-d" unless encrypt
+ data_to_encrypt = File.read(path)
+ salt = SecureRandom.random_bytes(8)
- _out, err, st = Open3.capture3(command.join(' '))
- success = st.success?
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
+ cipher.encrypt
+ cipher.pkcs5_keyivgen(password, salt, 1, "MD5")
+ encrypted_data = "Salted__" + salt + cipher.update(data_to_encrypt) + cipher.final
- unless err.to_s.empty?
- # to show an error message if something goes wrong
- if FastlaneCore::Globals.verbose?
- UI.error("`openssl` failed with an error:")
- UI.error(err)
- end
+ File.write(path, Base64.encode64(encrypted_data))
+ rescue FastlaneCore::Interface::FastlaneError
+ raise
+ rescue => error
+ UI.error(error.to_s)
+ UI.crash!("Error encrypting '#{path}'")
+ end
- # Ubuntu `openssl` does not fail on failure
- # but at least outputs an error message -
- # so we use that as indication of failure
- success = false
- end
+ # The encryption parameters in this implementations reflect the old behaviour which depended on the users' local OpenSSL version
+ # 1.0.x OpenSSL and earlier versions use MD5, 1.1.0c and newer uses SHA256, we try both before giving an error
+ def decrypt(path: nil, password: nil, hash_algorithm: "MD5")
+ stored_data = Base64.decode64(File.read(path))
+ salt = stored_data[8..15]
+ data_to_decrypt = stored_data[16..-1]
- UI.crash!("Error decrypting '#{path}'") unless success
+ decipher = OpenSSL::Cipher.new('AES-256-CBC')
+ decipher.decrypt
+ decipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)
- # On non-Mac systems (more specific Ubuntu Linux) it might take some time for the file to actually be there (see #11182).
- # To try to circumvent this flakyness (in tests), we wait a bit until the file appears (max 2s) (usually only 0.1 is actually waited)
- unless FastlaneCore::Helper.mac?
- count = 0
- # sleep until file exists or 20*0.1s (=2s) passed
- until File.exist?(tmpfile) || count == 20
- sleep(0.1)
- count += 1
- end
- end
+ decrypted_data = decipher.update(data_to_decrypt) + decipher.final
- FileUtils.mv(tmpfile, path)
+ File.write(path, decrypted_data)
+ rescue => error
+ fallback_hash_algorithm = "SHA256"
+ if hash_algorithm != fallback_hash_algorithm
+ decrypt(path, password, fallback_hash_algorithm)
+ else
+ UI.error(error.to_s)
+ UI.crash!("Error decrypting '#{path}'")
+ end
end
end
end