Skip to content

Commit

Permalink
refactor (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
jshawl authored Feb 18, 2024
1 parent e97c27d commit f7ac677
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 44 deletions.
46 changes: 19 additions & 27 deletions lib/minisign/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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]
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions lib/minisign/public_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ 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
#
# @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)
Expand All @@ -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
Expand Down
12 changes: 4 additions & 8 deletions lib/minisign/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
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
# @example
# 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
Expand All @@ -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
5 changes: 5 additions & 0 deletions lib/minisign/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 <kdf_output> used to xor the ed25519 keys
def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
RbNaCl::PasswordHash.scrypt(
Expand Down
31 changes: 30 additions & 1 deletion spec/minisign/cli_spec.rb
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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",
Expand All @@ -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
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion spec/minisign/e2e_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion spec/minisign/public_key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down

0 comments on commit f7ac677

Please sign in to comment.