minisign 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8dd11c61143149fd612a6c08a084a4e5831ec66f860c6a706edea18fc53bec00
4
- data.tar.gz: f7b6013996e7e72b35ad8c500e2bf5d24ebc2a9abe0d1d09bda86cfaca2d4ba8
3
+ metadata.gz: 5fff86c56cc4091b4517b01304262f097451c801847f4593d1bb99df4bef760f
4
+ data.tar.gz: 4c3ffd93d6dd729a25b6eabd9773d3a226e27c1066329972132d87ab5b5d49cc
5
5
  SHA512:
6
- metadata.gz: 615740c7d8fde14c2de494b2f7f9d28ebfaff2cb583210e29ee18573b97180d2a3d1ff3631085cdb53803d35a08cd31d01457c321a45d6d8b684849bcf69cb08
7
- data.tar.gz: d89f2cace36de94f4909420161a120888a929b3a097b00cbfa33e7081f7dcb15bacd93ca21be52df9f36009c5b3f1455b75dd49c29c99c3299d6130c8561a8b7
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,95 +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
- # @param str [String] The contents of the .minisig file
13
- # @example
14
- # Minisign::Signature.new(File.read('test/example.txt.minisig'))
15
- def initialize(str)
16
- @lines = str.split("\n")
17
- end
18
-
19
- # @return [String] the key id
20
- # @example
21
- # Minisign::Signature.new(File.read('test/example.txt.minisig')).key_id
22
- # #=> "E86FECED695E8E0"
23
- def key_id
24
- encoded_signature[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase
25
- end
26
-
27
- # @return [String] the trusted comment
28
- # @example
29
- # Minisign::Signature.new(File.read('test/example.txt.minisig')).trusted_comment
30
- # #=> "timestamp:1653934067\tfile:example.txt\thashed"
31
- def trusted_comment
32
- @lines[2].split('trusted comment: ')[1]
33
- end
34
-
35
- def trusted_comment_signature
36
- Base64.decode64(@lines[3])
37
- end
38
-
39
- # @return [String] the signature
40
- def signature
41
- encoded_signature[10..]
42
- end
43
-
44
- private
45
-
46
- def encoded_signature
47
- Base64.decode64(@lines[1])
48
- end
49
- end
50
-
51
- # Parse ed25519 verify key from minisign public key
52
- class PublicKey
53
- # Parse the ed25519 verify key from the minisign public key
54
- #
55
- # @param str [String] The minisign public key
56
- # @example
57
- # Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM')
58
- def initialize(str)
59
- @decoded = Base64.strict_decode64(str)
60
- @public_key = @decoded[10..]
61
- @verify_key = Ed25519::VerifyKey.new(@public_key)
62
- end
63
-
64
- # @return [String] the key id
65
- # @example
66
- # Minisign::PublicKey.new('RWTg6JXWzv6GDtDphRQ/x7eg0LaWBcTxPZ7i49xEeiqXVcR+r79OZRWM').key_id
67
- # #=> "E86FECED695E8E0"
68
- def key_id
69
- @decoded[2..9].bytes.map { |c| c.to_s(16) }.reverse.join.upcase
70
- end
71
-
72
- # Verify a message's signature
73
- #
74
- # @param sig [Minisign::Signature]
75
- # @param message [String] the content that was signed
76
- # @return [String] the trusted comment
77
- # @raise Ed25519::VerifyError on invalid signatures
78
- # @raise RuntimeError on tampered trusted comments
79
- def verify(sig, message)
80
- blake = OpenSSL::Digest.new('BLAKE2b512')
81
- ensure_matching_key_ids(sig.key_id, key_id)
82
- @verify_key.verify(sig.signature, blake.digest(message))
83
- begin
84
- @verify_key.verify(sig.trusted_comment_signature, sig.signature + sig.trusted_comment)
85
- rescue Ed25519::VerifyError
86
- raise 'Comment signature verification failed'
87
- end
88
- "Signature and comment signature verified\nTrusted comment: #{sig.trusted_comment}"
89
- end
90
-
91
- private
92
-
93
- def ensure_matching_key_ids(key_id1, key_id2)
94
- raise "Signature key id is #{key_id1}\nbut the key id in the public key is #{key_id2}" unless key_id1 == key_id2
95
- end
96
- end
97
- 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.7
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-06-22 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,6 +45,10 @@ 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
@@ -44,14 +62,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
62
  requirements:
45
63
  - - ">="
46
64
  - !ruby/object:Gem::Version
47
- version: 2.6.0
65
+ version: '2.7'
48
66
  required_rubygems_version: !ruby/object:Gem::Requirement
49
67
  requirements:
50
68
  - - ">="
51
69
  - !ruby/object:Gem::Version
52
70
  version: '0'
53
71
  requirements: []
54
- rubygems_version: 3.0.3.1
72
+ rubygems_version: 3.1.6
55
73
  signing_key:
56
74
  specification_version: 4
57
75
  summary: Minisign, in Ruby!