Skip to content

Commit

Permalink
Verify Key ID (#2)
Browse files Browse the repository at this point in the history
* parse out key id from signature

* rubocop fixes

* add some documentation

* wip

* fix rubocop issues

* rubocop fixes

* keep ensure private
  • Loading branch information
jshawl authored Jun 22, 2022
1 parent d698be3 commit 96fbc4a
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 19 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Metrics/BlockLength:
IgnoredMethods: ['describe', 'context']
AllCops:
NewCops: enable
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,30 @@ A rubygem for verifying [Minisign](http://jedisct1.github.io/minisign/) signatur
gem install minisign
```

### Verify a signature

```rb
require 'minisign'
pk = Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
signature = Minisign::Signature.new(File.read("test/example.txt.minisig"))
public_key = Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
message = File.read("test/example.txt")
pk.verify(signature, message)
signature = Minisign::Signature.new(File.read("test/example.txt.minisig"))
public_key.verify(signature, message)
```

The above is equivalent to:

```
minisign -Vm test/example.txt -P RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM
```

## Local Development

```
irb -Ilib -rminisign
```

## Local Documentation

```
yard server --reload
```
67 changes: 51 additions & 16 deletions lib/minisign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,42 @@
module Minisign
# Parse a .minisig file's contents
class Signature
attr_reader :signature, :comment, :comment_signature

# @!attribute [r] signature
# @return [String] the ed25519 verify key
# @!attribute [r] comment_signature
# @return [String] the signature for the trusted comment
# @!attribute [r] comment
# @return [String] the trusted comment

# @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")
@signature = Base64.decode64(lines[1])[10..]
@comment = lines[2].split('trusted comment: ')[1]
@comment_signature = Base64.decode64(lines[3])
@lines = str.split("\n")
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
end

# @return [String] the trusted comment
# @example
# Minisign::Signature.new(File.read('test/example.txt.minisig')).trusted_comment
# #=> "timestamp:1653934067\tfile:example.txt\thashed"
def trusted_comment
@lines[2].split('trusted comment: ')[1]
end

def trusted_comment_signature
Base64.decode64(@lines[3])
end

# @return [String] the signature
def signature
encoded_signature[10..]
end

private

def encoded_signature
Base64.decode64(@lines[1])
end
end

Expand All @@ -37,10 +56,19 @@ class PublicKey
# @example
# Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
def initialize(str)
@public_key = Base64.strict_decode64(str)[10..]
@decoded = Base64.strict_decode64(str)
@public_key = @decoded[10..]
@verify_key = Ed25519::VerifyKey.new(@public_key)
end

# @return [String] the key id
# @example
# Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM').key_id
# #=> "E86FECED695E8E0"
def key_id
@decoded[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase
end

# Verify a message's signature
#
# @param sig [Minisign::Signature]
Expand All @@ -50,13 +78,20 @@ def initialize(str)
# @raise RuntimeError on tampered trusted comments
def verify(sig, message)
blake = OpenSSL::Digest.new('BLAKE2b512')
ensure_matching_key_ids(sig.key_id, key_id)
@verify_key.verify(sig.signature, blake.digest(message))
begin
@verify_key.verify(sig.comment_signature, sig.signature + sig.comment)
@verify_key.verify(sig.trusted_comment_signature, sig.signature + sig.trusted_comment)
rescue Ed25519::VerifyError
raise 'Comment signature verification failed'
end
"Signature and comment signature verified\nTrusted comment: #{sig.comment}"
"Signature and comment signature verified\nTrusted comment: #{sig.trusted_comment}"
end

private

def ensure_matching_key_ids(key_id1, key_id2)
raise "Signature key id is #{key_id1}\nbut the key id in the public key is #{key_id2}" unless key_id1 == key_id2
end
end
end
1 change: 1 addition & 0 deletions minisign.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Gem::Specification.new do |s|
s.license = 'MIT'
s.add_runtime_dependency 'ed25519', '~> 1.3'
s.required_ruby_version = '>= 2.6.0'
s.metadata['rubygems_mfa_required'] = 'true'
end
17 changes: 17 additions & 0 deletions spec/minisign_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,21 @@
@signature = Minisign::Signature.new(File.read('test/example.txt.minisig.tampered'))
expect { @pk.verify(@signature, @message) }.to raise_error('Comment signature verification failed')
end
it 'has a key_id' do
expect(@pk.key_id).to eq('E86FECED695E8E0')
end
it 'raises errors on key id mismatch' do
@pk = Minisign::PublicKey.new('RWQIoBiLxWlf8dGe/DM+igVgetlwOuhWW3abyI1z8eS1RHJVc4o+1sCI')
@signature = Minisign::Signature.new(File.read('test/example.txt.minisig'))
expect do
@pk.verify(@signature, @message)
end.to raise_error("Signature key id is E86FECED695E8E0\nbut the key id in the public key is F15F69C58B18A08")
end
end

describe Minisign::Signature do
it 'has a key id' do
@signature = Minisign::Signature.new(File.read('test/example.txt.minisig'))
expect(@signature.key_id).to eq('E86FECED695E8E0')
end
end

0 comments on commit 96fbc4a

Please sign in to comment.