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

refactor #41

Merged
merged 5 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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