minisign 0.0.5 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43e641548c51311a548098b0102d122dfd770db04384fc905d8fb133e0d90feb
4
- data.tar.gz: 157e8e96644b65392e1f4f20f06976ec093ab275c2c2b83a2e4ec85b57282884
3
+ metadata.gz: 5fff86c56cc4091b4517b01304262f097451c801847f4593d1bb99df4bef760f
4
+ data.tar.gz: 4c3ffd93d6dd729a25b6eabd9773d3a226e27c1066329972132d87ab5b5d49cc
5
5
  SHA512:
6
- metadata.gz: 3fda3b616d567b60fbd35e9fdb825bf6ac659a0cd93c80379a8c61004d7d6e3a02dbb05cddc76db7eef046224bad82a10e335c979d574f971ea7d89c945a65be
7
- data.tar.gz: 45605deb3b08e44a9f49ffd7f64aea6c511c7e24dc6fef9951bb816ce08480ab8a6ef4041b1634cd910933f2b4b84e272207249b0a0d238e9ce17d7bd82da020
6
+ metadata.gz: 2bf06fbf6e88b3a003d5b62d245e8085f749a24a36022b66b4fa6d7f7901b5e2eececc0c107dd0ee1969cb4fa09dd28212695f401f1b8f4e5e2c8cc6adf85671
7
+ data.tar.gz: cf5da0f974244ea824bd8ffd5a3f41dd875eac858c213429918a1b0da1445b35a9a479ac8ed1131b33eaaab7ad609dd6cd6dc98852b2e29afff83cb389327acd
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minisign
4
+ # Parse ed25519 signing key from minisign private key
5
+ class PrivateKey
6
+ include Utils
7
+ attr_reader :signature_algorithm, :kdf_algorithm, :cksum_algorithm, :kdf_salt, :kdf_opslimit, :kdf_memlimit,
8
+ :key_id, :public_key, :secret_key, :checksum
9
+
10
+ # rubocop:disable Metrics/AbcSize
11
+ # rubocop:disable Layout/LineLength
12
+
13
+ # Parse signing information from the minisign private key
14
+ #
15
+ # @param str [String] The minisign private key
16
+ # @example
17
+ # Minisign::PrivateKey.new('RWRTY0IyEf+yYa5eAX38PgdrI3TMxwy+3sgzpgcZWQXhOKqdf9sAAAACAAAAAAAAAEAAAAAAHe8Olzttgk6k5pZyT3CyCTcTAV0bLN3kq5CUqhLjqSdYZ6oEWs/S7ztaephS+/jwnuOElLBKkg3Sd56jzyvMwL4qStNUTyPDqckNjniw2SlowmHN8n5NnR47gvqjo96E+vakpw8v5PE=', 'password')
18
+ def initialize(str, password = nil)
19
+ contents = str.split("\n")
20
+ bytes = Base64.decode64(contents.last).bytes
21
+ @signature_algorithm, @kdf_algorithm, @cksum_algorithm =
22
+ [bytes[0..1], bytes[2..3], bytes[4..5]].map { |a| a.pack('U*') }
23
+ @kdf_salt = bytes[6..37]
24
+ @kdf_opslimit = bytes[38..45].pack('V*').unpack('N*').sum
25
+ @kdf_memlimit = bytes[46..53].pack('V*').unpack('N*').sum
26
+ kdf_output = derive_key(password, @kdf_salt, @kdf_opslimit, @kdf_memlimit)
27
+ @key_id, @secret_key, @public_key, @checksum = xor(kdf_output, bytes[54..157])
28
+ end
29
+ # rubocop:enable Layout/LineLength
30
+ # rubocop:enable Metrics/AbcSize
31
+
32
+ # @return [String] the <kdf_output> used to xor the ed25519 keys
33
+ def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
34
+ RbNaCl::PasswordHash.scrypt(
35
+ password,
36
+ kdf_salt.pack('C*'),
37
+ kdf_opslimit,
38
+ kdf_memlimit,
39
+ 104
40
+ ).bytes
41
+ end
42
+
43
+ # rubocop:disable Layout/LineLength
44
+
45
+ # @return [Array<32 bit unsigned ints>] the byte array containing the key id, the secret and public ed25519 keys, and the checksum
46
+ def xor(kdf_output, contents)
47
+ # rubocop:enable Layout/LineLength
48
+ xored = kdf_output.each_with_index.map do |b, i|
49
+ contents[i] ^ b
50
+ end
51
+ [xored[0..7], xored[8..39], xored[40..71], xored[72..103]]
52
+ end
53
+
54
+ # @return [Ed25519::SigningKey] the ed25519 signing key
55
+ def ed25519_signing_key
56
+ Ed25519::SigningKey.new(@secret_key.pack('C*'))
57
+ end
58
+
59
+ # @return [String] the signature in the .minisig format that can be written to a file.
60
+ def sign(filename, message)
61
+ signature = ed25519_signing_key.sign(blake2b512(message))
62
+ trusted_comment = "timestamp:#{Time.now.to_i}\tfile:#{filename}\thashed"
63
+ global_signature = ed25519_signing_key.sign("#{signature}#{trusted_comment}")
64
+ [
65
+ 'untrusted comment: <arbitrary text>',
66
+ Base64.strict_encode64("ED#{@key_id.pack('C*')}#{signature}"),
67
+ "trusted comment: #{trusted_comment}",
68
+ Base64.strict_encode64(global_signature),
69
+ ''
70
+ ].join("\n")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minisign
4
+ # Parse ed25519 verify key from minisign public key
5
+ class PublicKey
6
+ include Utils
7
+ # Parse the ed25519 verify key from the minisign public key
8
+ #
9
+ # @param str [String] The minisign public key
10
+ # @example
11
+ # Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
12
+ def initialize(str)
13
+ @decoded = Base64.strict_decode64(str)
14
+ @public_key = @decoded[10..]
15
+ @verify_key = Ed25519::VerifyKey.new(@public_key)
16
+ end
17
+
18
+ # @return [String] the key id
19
+ # @example
20
+ # Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM').key_id
21
+ # #=> "E86FECED695E8E0"
22
+ def key_id
23
+ @decoded[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase
24
+ end
25
+
26
+ # Verify a message's signature
27
+ #
28
+ # @param sig [Minisign::Signature]
29
+ # @param message [String] the content that was signed
30
+ # @return [String] the trusted comment
31
+ # @raise Ed25519::VerifyError on invalid signatures
32
+ # @raise RuntimeError on tampered trusted comments
33
+ def verify(sig, message)
34
+ ensure_matching_key_ids(sig.key_id, key_id)
35
+ @verify_key.verify(sig.signature, blake2b512(message))
36
+ begin
37
+ @verify_key.verify(sig.trusted_comment_signature, sig.signature + sig.trusted_comment)
38
+ rescue Ed25519::VerifyError
39
+ raise 'Comment signature verification failed'
40
+ end
41
+ "Signature and comment signature verified\nTrusted comment: #{sig.trusted_comment}"
42
+ end
43
+
44
+ private
45
+
46
+ def ensure_matching_key_ids(key_id1, key_id2)
47
+ raise "Signature key id is #{key_id1}\nbut the key id in the public key is #{key_id2}" unless key_id1 == key_id2
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minisign
4
+ # Parse a .minisig file's contents
5
+ class Signature
6
+ # @param str [String] The contents of the .minisig file
7
+ # @example
8
+ # Minisign::Signature.new(File.read('test/example.txt.minisig'))
9
+ def initialize(str)
10
+ @lines = str.split("\n")
11
+ end
12
+
13
+ # @return [String] the key id
14
+ # @example
15
+ # Minisign::Signature.new(File.read('test/example.txt.minisig')).key_id
16
+ # #=> "E86FECED695E8E0"
17
+ def key_id
18
+ encoded_signature[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase
19
+ end
20
+
21
+ # @return [String] the trusted comment
22
+ # @example
23
+ # Minisign::Signature.new(File.read('test/example.txt.minisig')).trusted_comment
24
+ # #=> "timestamp:1653934067\tfile:example.txt\thashed"
25
+ def trusted_comment
26
+ @lines[2].split('trusted comment: ')[1]
27
+ end
28
+
29
+ def trusted_comment_signature
30
+ Base64.decode64(@lines[3])
31
+ end
32
+
33
+ # @return [String] the signature
34
+ def signature
35
+ encoded_signature[10..]
36
+ end
37
+
38
+ private
39
+
40
+ def encoded_signature
41
+ Base64.decode64(@lines[1])
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minisign
4
+ # Helpers used in multiple classes
5
+ module Utils
6
+ def blake2b512(message)
7
+ OpenSSL::Digest.new('BLAKE2b512').digest(message)
8
+ end
9
+ end
10
+ end
data/lib/minisign.rb CHANGED
@@ -3,60 +3,9 @@
3
3
  require 'ed25519'
4
4
  require 'base64'
5
5
  require 'openssl'
6
+ require 'rbnacl'
6
7
 
7
- # `minisign` is a rubygem for verifying {https://jedisct1.github.io/minisign minisign} signatures.
8
- # @author Jesse Shawl
9
- module Minisign
10
- # Parse a .minisig file's contents
11
- class Signature
12
- attr_reader :signature, :comment, :comment_signature
13
-
14
- # @!attribute [r] signature
15
- # @return [String] the ed25519 verify key
16
- # @!attribute [r] comment_signature
17
- # @return [String] the signature for the trusted comment
18
- # @!attribute [r] comment
19
- # @return [String] the trusted comment
20
-
21
- # @param str [String] The contents of the .minisig file
22
- # @example
23
- # Minisign::Signature.new(File.read('test/example.txt.minisig'))
24
- def initialize(str)
25
- lines = str.split("\n")
26
- @signature = Base64.decode64(lines[1])[10..]
27
- @comment = lines[2].split('trusted comment: ')[1]
28
- @comment_signature = Base64.decode64(lines[3])
29
- end
30
- end
31
-
32
- # Parse ed25519 verify key from minisign public key
33
- class PublicKey
34
- # Parse the ed25519 verify key from the minisign public key
35
- #
36
- # @param str [String] The minisign public key
37
- # @example
38
- # Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
39
- def initialize(str)
40
- @public_key = Base64.strict_decode64(str)[10..]
41
- @verify_key = Ed25519::VerifyKey.new(@public_key)
42
- end
43
-
44
- # Verify a message's signature
45
- #
46
- # @param sig [Minisign::Signature]
47
- # @param message [String] the content that was signed
48
- # @return [String] the trusted comment
49
- # @raise Ed25519::VerifyError on invalid signatures
50
- # @raise RuntimeError on tampered trusted comments
51
- def verify(sig, message)
52
- blake = OpenSSL::Digest.new('BLAKE2b512')
53
- @verify_key.verify(sig.signature, blake.digest(message))
54
- begin
55
- @verify_key.verify(sig.comment_signature, sig.signature + sig.comment)
56
- rescue Ed25519::VerifyError
57
- raise 'Comment signature verification failed'
58
- end
59
- "Signature and comment signature verified\nTrusted comment: #{sig.comment}"
60
- end
61
- end
62
- end
8
+ require 'minisign/utils'
9
+ require 'minisign/public_key'
10
+ require 'minisign/signature'
11
+ require 'minisign/private_key'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minisign
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Shawl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-30 00:00:00.000000000 Z
11
+ date: 2024-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ed25519
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rbnacl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.1'
27
41
  description: Verify minisign signatures
28
42
  email: jesse@jesse.sh
29
43
  executables: []
@@ -31,10 +45,15 @@ extensions: []
31
45
  extra_rdoc_files: []
32
46
  files:
33
47
  - lib/minisign.rb
48
+ - lib/minisign/private_key.rb
49
+ - lib/minisign/public_key.rb
50
+ - lib/minisign/signature.rb
51
+ - lib/minisign/utils.rb
34
52
  homepage: https://rubygems.org/gems/minisign
35
53
  licenses:
36
54
  - MIT
37
- metadata: {}
55
+ metadata:
56
+ rubygems_mfa_required: 'true'
38
57
  post_install_message:
39
58
  rdoc_options: []
40
59
  require_paths:
@@ -43,14 +62,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
62
  requirements:
44
63
  - - ">="
45
64
  - !ruby/object:Gem::Version
46
- version: 2.6.0
65
+ version: '2.7'
47
66
  required_rubygems_version: !ruby/object:Gem::Requirement
48
67
  requirements:
49
68
  - - ">="
50
69
  - !ruby/object:Gem::Version
51
70
  version: '0'
52
71
  requirements: []
53
- rubygems_version: 3.0.3.1
72
+ rubygems_version: 3.1.6
54
73
  signing_key:
55
74
  specification_version: 4
56
75
  summary: Minisign, in Ruby!