Skip to content

Commit

Permalink
parse private key
Browse files Browse the repository at this point in the history
  • Loading branch information
jshawl committed Feb 3, 2024
1 parent d479305 commit 96ae0c5
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ PATH
specs:
minisign (0.0.7)
ed25519 (~> 1.3)
rbnacl (~> 7.1)

GEM
remote: https://rubygems.org/
Expand All @@ -11,10 +12,13 @@ GEM
diff-lcs (1.5.0)
docile (1.4.0)
ed25519 (1.3.0)
ffi (1.16.3)
parallel (1.22.1)
parser (3.1.2.0)
ast (~> 2.4.1)
rainbow (3.1.1)
rbnacl (7.1.1)
ffi
regexp_parser (2.5.0)
rexml (3.2.5)
rspec (3.11.0)
Expand Down
1 change: 1 addition & 0 deletions lib/minisign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

require 'minisign/public_key'
require 'minisign/signature'
require 'minisign/private_key'
62 changes: 62 additions & 0 deletions lib/minisign/private_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module Minisign
class PrivateKey
attr_reader :signature_algorithm, :kdf_algorithm, :cksum_algorithm, :kdf_salt, :kdf_opslimit, :kdf_memlimit,
:key_id, :public_key, :secret_key, :checksum

def initialize(opts, _password = nil)
@signature_algorithm = opts[:signature_algorithm]
@kdf_algorithm = opts[:kdf_algorithm]
@cksum_algorithm = opts[:cksum_algorithm]
@kdf_salt = opts[:kdf_salt]
@kdf_opslimit = opts[:kdf_opslimit]
@kdf_memlimit = opts[:kdf_memlimit]
@key_id = opts[:key_id]
@secret_key = opts[:secret_key]
@public_key = opts[:public_key]
@checksum = opts[:checksum]
end

def self.from_file(path, password = nil)
contents = File.read(path).split("\n")
bytes = Base64.decode64(contents.last).bytes
signature_algorithm = bytes[0..1].pack('U*')
kdf_algorithm = bytes[2..3].pack('U*')
cksum_algorithm = bytes[4..5].pack('U*')
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 = RbNaCl::PasswordHash.scrypt(
password,
kdf_salt.pack('C*'),
kdf_opslimit,
kdf_memlimit,
104
).bytes

xored = kdf_output.each_with_index.map do |b, i|
bytes[54..157][i] ^ b
end

key_id = xored[0..7]
secret_key = xored[8..39]
public_key = xored[40..71]
checksum = xored[72..103]

new({
signature_algorithm: signature_algorithm,
kdf_algorithm: kdf_algorithm,
cksum_algorithm: cksum_algorithm,
kdf_salt: kdf_salt,
kdf_opslimit: kdf_opslimit,
kdf_memlimit: kdf_memlimit,
key_id: key_id,
secret_key: secret_key,
public_key: public_key,
checksum: checksum
})
end
end
end
1 change: 1 addition & 0 deletions minisign.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Gem::Specification.new do |s|
'https://rubygems.org/gems/minisign'
s.license = 'MIT'
s.add_runtime_dependency 'ed25519', '~> 1.3'
s.add_runtime_dependency 'rbnacl', '~> 7.1'
s.required_ruby_version = '>= 2.7'
s.metadata['rubygems_mfa_required'] = 'true'
end
54 changes: 54 additions & 0 deletions spec/minisign/private_key_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'rbnacl'
describe Minisign::PrivateKey do
describe '.from_file' do
before(:all) do
@private_key = Minisign::PrivateKey.from_file('test/minisign.key', 'password')
end

it 'parses the signature_algorithm' do
expect(@private_key.signature_algorithm).to eq('Ed')
end

it 'parses the kdf_algorithm' do
expect(@private_key.kdf_algorithm).to eq('Sc')
end

it 'parses the cksum_algorithm' do
expect(@private_key.cksum_algorithm).to eq('B2')
end

it 'parses the kdf_salt' do
expect(@private_key.kdf_salt).to eq([17, 255, 178, 97, 174, 94, 1, 125, 252, 62, 7, 107, 35, 116, 204, 199, 12,
190, 222, 200, 51, 166, 7, 25, 89, 5, 225, 56, 170, 157, 127, 219])
end

it 'parses the kdf_opslimit' do
expect(@private_key.kdf_opslimit).to eq(33_554_432)
end

it 'parses the kdf_memlimit' do
expect(@private_key.kdf_memlimit).to eq(1_073_741_824)
end

it 'parses the key id' do
expect(@private_key.key_id).to eq([166, 41, 163, 171, 79, 169, 183, 76])
end

it 'parses the public key' do
expect(@private_key.public_key).to eq([108, 35, 192, 26, 47, 128, 233, 165, 133, 38, 242, 5, 76, 55, 135, 40,
103, 72, 230, 43, 184, 117, 219, 37, 173, 250, 196, 122, 252, 174, 173, 140])
end

it 'parses the secret key' do
expect(@private_key.secret_key).to eq([65, 87, 110, 33, 168, 130, 118, 100, 249, 200, 160, 167, 47, 59, 141,
122, 156, 38, 80, 199, 139, 1, 21, 18, 116, 110, 204, 131, 199, 202, 181, 87])
end

it 'parses the checksum' do
expect(@private_key.checksum).to eq([19, 146, 239, 121, 33, 164, 216, 219, 8, 104, 111, 52, 198, 78, 21, 236,
113, 255, 174, 47, 39, 216, 61, 198, 233, 161, 233, 143, 84, 246, 255, 150])
end
end
end
2 changes: 0 additions & 2 deletions test/local.key

This file was deleted.

2 changes: 0 additions & 2 deletions test/local.pub

This file was deleted.

2 changes: 2 additions & 0 deletions test/minisign.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: password is "password"
RWRTY0IyEf+yYa5eAX38PgdrI3TMxwy+3sgzpgcZWQXhOKqdf9sAAAACAAAAAAAAAEAAAAAAHe8Olzttgk6k5pZyT3CyCTcTAV0bLN3kq5CUqhLjqSdYZ6oEWs/S7ztaephS+/jwnuOElLBKkg3Sd56jzyvMwL4qStNUTyPDqckNjniw2SlowmHN8n5NnR47gvqjo96E+vakpw8v5PE=
2 changes: 2 additions & 0 deletions test/minisign.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: minisign public key 4CB7A94FABA329A6
RWSmKaOrT6m3TGwjwBovgOmlhSbyBUw3hyhnSOYruHXbJa36xHr8rq2M

0 comments on commit 96ae0c5

Please sign in to comment.