minisign 0.0.7 → 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: 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!