Skip to content

Commit

Permalink
Unencrypted keys (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
jshawl authored Feb 4, 2024
1 parent bd272aa commit 527e7d3
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Support signing with unencrypted keys

## [0.0.8] - 2024-02-03

### Added
Expand Down
25 changes: 21 additions & 4 deletions lib/minisign/private_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class PrivateKey

# rubocop:disable Metrics/AbcSize
# rubocop:disable Layout/LineLength
# rubocop:disable Metrics/MethodLength

# Parse signing information from the minisign private key
#
Expand All @@ -20,14 +21,31 @@ def initialize(str, password = nil)
bytes = Base64.decode64(contents.last).bytes
@signature_algorithm, @kdf_algorithm, @cksum_algorithm =
[bytes[0..1], bytes[2..3], bytes[4..5]].map { |a| a.pack('U*') }
raise 'Missing password for encrypted key' if @kdf_algorithm.bytes.sum != 0 && password.nil?

@kdf_salt = bytes[6..37]
@kdf_opslimit = bytes[38..45].pack('V*').unpack('N*').sum
@kdf_memlimit = bytes[46..53].pack('V*').unpack('N*').sum
kdf_output = derive_key(password, @kdf_salt, @kdf_opslimit, @kdf_memlimit)
@key_id, @secret_key, @public_key, @checksum = xor(kdf_output, bytes[54..157])
key_data_bytes = if password
kdf_output = derive_key(password, @kdf_salt, @kdf_opslimit, @kdf_memlimit)
xor(kdf_output, bytes[54..157])
else
bytes[54..157]
end
@key_id, @secret_key, @public_key, @checksum = key_data(key_data_bytes)
assert_keypair_match!
end
# rubocop:enable Layout/LineLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength

def assert_keypair_match!
raise 'Wrong password for that key' if @public_key != ed25519_signing_key.verify_key.to_bytes.bytes
end

def key_data(bytes)
[bytes[0..7], bytes[8..39], bytes[40..71], bytes[72..103]]
end

# @return [String] the <kdf_output> used to xor the ed25519 keys
def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
Expand All @@ -45,10 +63,9 @@ def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
# @return [Array<32 bit unsigned ints>] the byte array containing the key id, the secret and public ed25519 keys, and the checksum
def xor(kdf_output, contents)
# rubocop:enable Layout/LineLength
xored = kdf_output.each_with_index.map do |b, i|
kdf_output.each_with_index.map do |b, i|
contents[i] ^ b
end
[xored[0..7], xored[8..39], xored[40..71], xored[72..103]]
end

# @return [Ed25519::SigningKey] the ed25519 signing key
Expand Down
36 changes: 29 additions & 7 deletions spec/minisign/private_key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
expect(@private_key.kdf_algorithm).to eq('Sc')
end

it 'raises if the private key requires a password but is not supplied' do
expect do
Minisign::PrivateKey.new(File.read('test/minisign.key'))
end.to raise_error('Missing password for encrypted key')
end

it 'raises if the password is incorrect for the private key' do
expect do
Minisign::PrivateKey.new(File.read('test/minisign.key'), 'not the right password')
end.to raise_error('Wrong password for that key')
end

it 'parses the cksum_algorithm' do
expect(@private_key.cksum_algorithm).to eq('B2')
end
Expand Down Expand Up @@ -53,15 +65,25 @@

describe 'sign' do
it 'signs a file' do
Dir.glob('test/generated/*').each { |file| File.delete(file) }
filename = "#{SecureRandom.uuid}.txt"
message = SecureRandom.uuid
File.write("test/generated/#{filename}", message)
signature = @private_key.sign(filename, message)
File.write("test/generated/#{filename}.minisig", signature)
@filename = 'encrypted-key.txt'
@message = SecureRandom.uuid
File.write("test/generated/#{@filename}", @message)
signature = @private_key.sign(@filename, @message)
File.write("test/generated/#{@filename}.minisig", signature)
@signature = Minisign::Signature.new(signature)
@public_key = Minisign::PublicKey.new('RWSmKaOrT6m3TGwjwBovgOmlhSbyBUw3hyhnSOYruHXbJa36xHr8rq2M')
expect(@public_key.verify(@signature, message)).to match('Signature and comment signature verified')
expect(@public_key.verify(@signature, @message)).to match('Signature and comment signature verified')
end
it 'signs a file with an unencrypted key' do
@filename = 'unencrypted-key.txt'
@message = SecureRandom.uuid
File.write("test/generated/#{@filename}", @message)
@unencrypted_private_key = Minisign::PrivateKey.new(File.read('test/unencrypted.key'))
signature = @unencrypted_private_key.sign(@filename, @message)
File.write("test/generated/#{@filename}.minisig", signature)
@signature = Minisign::Signature.new(signature)
@public_key = Minisign::PublicKey.new('RWT/N/MXaBIWRAPzfdEKqVRq9txskjf5qh7EbqMLVHjkNTGFazO3zMw2')
expect(@public_key.verify(@signature, @message)).to match('Signature and comment signature verified')
end
end
end
6 changes: 4 additions & 2 deletions spec/verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
url="https://github.com/jedisct1/minisign/releases/download/0.11/minisign-0.11-macos.zip"
curl -sL $url -o test/generated/minisign.zip
unzip -o test/generated/minisign.zip -d test/generated
test/generated/minisign -Vm test/generated/*.txt -p test/minisign.pub
else
url="https://github.com/jedisct1/minisign/releases/download/0.11/minisign-0.11-linux.tar.gz"
curl -sL $url -o test/generated/minisign.tar.gz
tar -xvzf test/generated/minisign.tar.gz -C test/generated
test/generated/minisign-linux/x86_64/minisign -Vm test/generated/*.txt -p test/minisign.pub
mv test/generated/minisign-linux/x86_64/minisign test/generated/minisign
fi

test/generated/minisign -Vm test/generated/encrypted-key.txt -p test/minisign.pub || exit 1
test/generated/minisign -Vm test/generated/unencrypted-key.txt -p test/unencrypted.pub || exit 1
2 changes: 2 additions & 0 deletions test/unencrypted.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: minisign encrypted secret key
RWQAAEIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zfzF2gSFkTKpvJz4Zf4HGG/b6OCfwWzUBt6qubTzn3j1XSgZwRHwQPzfdEKqVRq9txskjf5qh7EbqMLVHjkNTGFazO3zMw2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
2 changes: 2 additions & 0 deletions test/unencrypted.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: minisign public key 4416126817F337FF
RWT/N/MXaBIWRAPzfdEKqVRq9txskjf5qh7EbqMLVHjkNTGFazO3zMw2

0 comments on commit 527e7d3

Please sign in to comment.