diff --git a/lib/minisign/cli.rb b/lib/minisign/cli.rb index 86c0f31..8b2f3fe 100644 --- a/lib/minisign/cli.rb +++ b/lib/minisign/cli.rb @@ -41,6 +41,7 @@ def self.usage puts '-f force. Combined with -G, overwrite a previous key pair' puts '-v display version number' puts '' + exit 1 end def self.prompt @@ -90,44 +91,25 @@ def self.generate(options) end def self.recreate(options) - secret_key = options[:s] || "#{Dir.home}/.minisign/minisign.key" + options[:s] ||= "#{Dir.home}/.minisign/minisign.key" public_key = options[:p] || './minisign.pub' - private_key_contents = File.read(secret_key) - begin - # try without a password first - private_key = Minisign::PrivateKey.new(private_key_contents) - rescue Minisign::PasswordMissingError - print 'Password: ' - private_key = Minisign::PrivateKey.new(private_key_contents, prompt) - end - File.write(public_key, private_key.public_key) + File.write(public_key, private_key(options[:s]).public_key) end def self.change_password(options) options[:s] ||= "#{Dir.home}/.minisign/minisign.key" - private_key = begin - Minisign::PrivateKey.new(File.read(options[:s])) - rescue Minisign::PasswordMissingError - print 'Password: ' - Minisign::PrivateKey.new(File.read(options[:s]), prompt) - end + new_private_key = private_key(options[:s]) print 'New Password: ' new_password = options[:W] ? nil : prompt - private_key.change_password! new_password - File.write(options[:s], private_key) + new_private_key.change_password! new_password + File.write(options[:s], new_private_key) end def self.sign(options) # TODO: multiple files options[:x] ||= "#{options[:m]}.minisig" options[:s] ||= "#{Dir.home}/.minisign/minisign.key" - private_key = begin - Minisign::PrivateKey.new(File.read(options[:s])) - rescue Minisign::PasswordMissingError - print 'Password: ' - Minisign::PrivateKey.new(File.read(options[:s]), prompt) - end - signature = private_key.sign(options[:m], File.read(options[:m]), options[:t], options[:c]) + signature = private_key(options[:s]).sign(options[:m], File.read(options[:m]), options[:t], options[:c]) File.write(options[:x], signature) end @@ -140,8 +122,8 @@ def self.verify(options) signature = Minisign::Signature.new(File.read(options[:x])) begin verification = public_key.verify(signature, message) - rescue StandardError - puts 'Signature verification failed' + rescue Minisign::SignatureVerificationError => e + puts e.message exit 1 end return if options[:q] @@ -150,6 +132,16 @@ def self.verify(options) puts options[:Q] ? signature.trusted_comment : verification end + def self.private_key(seckey_file) + seckey_file_contents = File.read(seckey_file) + begin + Minisign::PrivateKey.new(seckey_file_contents) + rescue Minisign::PasswordMissingError + print 'Password: ' + Minisign::PrivateKey.new(seckey_file_contents, prompt) + end + end + # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength diff --git a/lib/minisign/public_key.rb b/lib/minisign/public_key.rb index 310e6d4..1aa93ff 100644 --- a/lib/minisign/public_key.rb +++ b/lib/minisign/public_key.rb @@ -21,7 +21,7 @@ def initialize(str) # public_key.key_id # #=> "E86FECED695E8E0" def key_id - key_id_binary_string.bytes.map { |c| c.to_s(16) }.reverse.join.upcase + hex key_id_binary_string.bytes end # Verify a message's signature @@ -29,9 +29,9 @@ def key_id # @param signature [Minisign::Signature] # @param message [String] the content that was signed # @return [String] the trusted comment - # @raise Ed25519::VerifyError on invalid signatures - # @raise RuntimeError on tampered trusted comments - # @raise RuntimeError on mismatching key ids + # @raise Minisign::SignatureVerificationError on invalid signatures + # @raise Minisign::SignatureVerificationError on tampered trusted comments + # @raise Minisign::SignatureVerificationError on mismatching key ids def verify(signature, message) assert_matching_key_ids!(signature.key_id, key_id) verify_message_signature(signature.signature, message) @@ -54,8 +54,8 @@ def verify_comment_signature(signature, comment) def verify_message_signature(signature, message) ed25519_verify_key.verify(signature, blake2b512(message)) - rescue Ed25519::VerifyError => e - raise Minisign::SignatureVerificationError, e + rescue Ed25519::VerifyError + raise Minisign::SignatureVerificationError, 'Signature verification failed' end def untrusted_comment diff --git a/lib/minisign/signature.rb b/lib/minisign/signature.rb index 6787832..d224efb 100644 --- a/lib/minisign/signature.rb +++ b/lib/minisign/signature.rb @@ -3,11 +3,13 @@ module Minisign # Parse a .minisig file's contents class Signature + include Utils # @param str [String] The contents of the .minisig file # @example # Minisign::Signature.new(File.read('test/example.txt.minisig')) def initialize(str) @lines = str.split("\n") + @decoded = Base64.strict_decode64(@lines[1]) end # @return [String] the key id @@ -15,7 +17,7 @@ def initialize(str) # Minisign::Signature.new(File.read('test/example.txt.minisig')).key_id # #=> "E86FECED695E8E0" def key_id - encoded_signature[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase + hex @decoded[2..9].bytes end # @return [String] the trusted comment @@ -33,18 +35,12 @@ def trusted_comment_signature # @return [String] the global signature def signature - encoded_signature[10..] + @decoded[10..] end # @return [String] The signature that can be written to a file def to_s "#{@lines.join("\n")}\n" end - - private - - def encoded_signature - Base64.decode64(@lines[1]) - end end end diff --git a/lib/minisign/utils.rb b/lib/minisign/utils.rb index 8ca056c..74258d8 100644 --- a/lib/minisign/utils.rb +++ b/lib/minisign/utils.rb @@ -18,6 +18,11 @@ def xor(kdf_output, contents) end end + # @return [String] bytes as little endian hexadecimal + def hex(bytes) + bytes.map { |c| c.to_s(16) }.reverse.join.upcase + end + # @return [String] the used to xor the ed25519 keys def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit) RbNaCl::PasswordHash.scrypt( diff --git a/spec/minisign/cli_spec.rb b/spec/minisign/cli_spec.rb index ff0dc20..3f4eb2a 100644 --- a/spec/minisign/cli_spec.rb +++ b/spec/minisign/cli_spec.rb @@ -1,6 +1,13 @@ # frozen_string_literal: true describe Minisign::CLI do + describe '.usage' do + it 'prints usage info and exits 1' do + expect do + Minisign::CLI.usage + end.to raise_error(SystemExit) + end + end describe '.generate' do before do @options = { @@ -17,7 +24,6 @@ end it 'does not prompt for a password if -W' do keyname = SecureRandom.uuid - SecureRandom.uuid options = { p: "test/generated/cli/#{keyname}.pub", s: "test/generated/cli/#{keyname}.key", @@ -26,6 +32,19 @@ expect(Minisign::CLI).not_to receive(:prompt) Minisign::CLI.generate(options) end + it 'prints an error message if the passwords dont match' do + password = SecureRandom.uuid + password_confirmation = SecureRandom.uuid + keyname = SecureRandom.uuid + options = { + p: "test/generated/cli/#{keyname}.pub", + s: "test/generated/cli/#{keyname}.key" + } + allow(Minisign::CLI).to receive(:prompt).and_return(password, password_confirmation) + expect do + Minisign::CLI.generate(options) + end.to raise_error(SystemExit) + end it 'writes the key files' do keyname = SecureRandom.uuid password = SecureRandom.uuid @@ -144,6 +163,16 @@ Minisign::CLI.verify(options) end.not_to raise_error end + it 'prints an error message' do + options = { + p: 'test/minisign.pub', + m: 'test/example.txt', + x: 'test/example.txt.minisig.tampered' + } + expect do + Minisign::CLI.verify(options) + end.to raise_error(SystemExit) + end it 'outputs the message' do options = { p: 'test/minisign.pub', diff --git a/spec/minisign/e2e_spec.rb b/spec/minisign/e2e_spec.rb index 9ffda20..4aaea00 100644 --- a/spec/minisign/e2e_spec.rb +++ b/spec/minisign/e2e_spec.rb @@ -48,6 +48,6 @@ command = 'minisign -Vm test/example.txt -x test/example.txt.minisig.unverifiable -p test/minisign.pub' expect(`#{command}`).to match(/Signature verification failed/) command = 'minisign -Vm test/example.txt -x test/example.txt.minisig.tampered -p test/minisign.pub' - expect(`#{command}`).to match(/Signature verification failed/) + expect(`#{command}`).to match(/Comment signature verification failed/) end end diff --git a/spec/minisign/public_key_spec.rb b/spec/minisign/public_key_spec.rb index 1149229..dc5add6 100644 --- a/spec/minisign/public_key_spec.rb +++ b/spec/minisign/public_key_spec.rb @@ -13,7 +13,7 @@ @signature = Minisign::Signature.new(File.read('test/example.txt.minisig.unverifiable')) expect do @pk.verify(@signature, @message) - end.to raise_error(Minisign::SignatureVerificationError, 'signature verification failed!') + end.to raise_error(Minisign::SignatureVerificationError, 'Signature verification failed') end it 'verifies trusted comments' do @signature = Minisign::Signature.new(File.read('test/example.txt.minisig.tampered'))