minisign 0.1.0 → 0.2.0
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 +4 -4
- data/bin/minisign +38 -0
- data/lib/minisign/cli.rb +159 -0
- data/lib/minisign/error.rb +12 -0
- data/lib/minisign/key_pair.rb +4 -0
- data/lib/minisign/private_key.rb +21 -8
- data/lib/minisign/public_key.rb +18 -7
- data/lib/minisign.rb +2 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59267c0797e4539c136803dd3ac5333f14384019bb211abb50c078b14d9cb1c8
|
4
|
+
data.tar.gz: 7771c5a5b4227d1030d78b60fa584ed5158270a568617cc4ac7f4760230ef1d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a713e970fb762efaaabcd4572ea664d69cf5d558aa1219b3a27daf4c1fe4c111e98d0a4d90f4cc9892f0ec776b102bd0ab081d498a05a29639b0668a3decaa1
|
7
|
+
data.tar.gz: 603bb8180811b3923c1b5782e76d049fefe125ed5338f164dd0ed787d26ffae251932f6b67eea7ef6a15aaddd1076725a44fa397902eb8ea56c80f134dc77923
|
data/bin/minisign
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'io/console'
|
5
|
+
require 'minisign'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
Signal.trap('INT') { exit }
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
op = OptionParser.new do |opts|
|
12
|
+
boolean_opts = %w[G R C W S V Q f q o]
|
13
|
+
argument_opts = %w[t m x s p]
|
14
|
+
boolean_opts.each do |o|
|
15
|
+
opts.on("-#{o}") do |boolean|
|
16
|
+
options[o.to_sym] = boolean
|
17
|
+
end
|
18
|
+
end
|
19
|
+
argument_opts.each do |o|
|
20
|
+
opts.on("-#{o}#{o.upcase}") do |value|
|
21
|
+
options[o.to_sym] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
op.parse!
|
28
|
+
raise OptionParser::InvalidOption if options.keys.empty?
|
29
|
+
rescue OptionParser::InvalidOption
|
30
|
+
Minisign::CLI.usage
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
Minisign::CLI.generate(options) if options[:G]
|
35
|
+
Minisign::CLI.recreate(options) if options[:R]
|
36
|
+
Minisign::CLI.change_password(options) if options[:C]
|
37
|
+
Minisign::CLI.sign(options) if options[:S]
|
38
|
+
Minisign::CLI.verify(options) if options[:V]
|
data/lib/minisign/cli.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
6
|
+
module Minisign
|
7
|
+
# The command line interface.
|
8
|
+
# This module is _not_ intended for library usage and is subject to
|
9
|
+
# breaking changes.
|
10
|
+
module CLI
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
12
|
+
# rubocop:disable Metrics/MethodLength
|
13
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
14
|
+
|
15
|
+
# Command line usage
|
16
|
+
def self.usage
|
17
|
+
puts 'Usage:'
|
18
|
+
puts 'minisign -G [-f] [-p pubkey_file] [-s seckey_file] [-W]'
|
19
|
+
puts 'minisign -R [-s seckey_file] [-p pubkey_file]'
|
20
|
+
puts 'minisign -C [-s seckey_file] [-W]'
|
21
|
+
puts 'minisign -S [-l] [-x sig_file] [-s seckey_file] [-c untrusted_comment]'
|
22
|
+
puts ' [-t trusted_comment] -m file [file ...]'
|
23
|
+
puts 'minisign -V [-H] [-x sig_file] [-p pubkey_file | -P pubkey] [-o] [-q] -m file'
|
24
|
+
puts ''
|
25
|
+
puts '-G generate a new key pair'
|
26
|
+
puts '-R recreate a public key file from a secret key file'
|
27
|
+
puts '-C change/remove the password of the secret key'
|
28
|
+
puts '-S sign files'
|
29
|
+
puts '-V verify that a signature is valid for a given file'
|
30
|
+
puts '-m <file> file to sign/verify'
|
31
|
+
puts '-o combined with -V, output the file content after verification'
|
32
|
+
puts '-p <pubkey_file> public key file (default: ./minisign.pub)'
|
33
|
+
puts '-P <pubkey> public key, as a base64 string'
|
34
|
+
puts '-s <seckey_file> secret key file (default: ~/.minisign/minisign.key)'
|
35
|
+
puts '-W do not encrypt/decrypt the secret key with a password'
|
36
|
+
puts '-x <sigfile> signature file (default: <file>.minisig)'
|
37
|
+
puts '-c <comment> add a one-line untrusted comment'
|
38
|
+
puts '-t <comment> add a one-line trusted comment'
|
39
|
+
puts '-q quiet mode, suppress output'
|
40
|
+
puts '-Q pretty quiet mode, only print the trusted comment'
|
41
|
+
puts '-f force. Combined with -G, overwrite a previous key pair'
|
42
|
+
puts '-v display version number'
|
43
|
+
puts ''
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.prompt
|
47
|
+
$stdin.tty? ? $stdin.noecho(&:gets).chomp : $stdin.gets.chomp
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.prevent_overwrite!(file)
|
51
|
+
return unless File.exist? file
|
52
|
+
|
53
|
+
puts 'Key generation aborted:'
|
54
|
+
puts "#{file} already exists."
|
55
|
+
puts ''
|
56
|
+
puts 'If you really want to overwrite the existing key pair, add the -f switch to'
|
57
|
+
puts 'force this operation.'
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.generate(options)
|
62
|
+
secret_key = options[:s] || "#{Dir.home}/.minisign/minisign.key"
|
63
|
+
public_key = options[:p] || './minisign.pub'
|
64
|
+
prevent_overwrite!(public_key) unless options[:f]
|
65
|
+
prevent_overwrite!(secret_key) unless options[:f]
|
66
|
+
|
67
|
+
if options[:W]
|
68
|
+
keypair = Minisign::KeyPair.new
|
69
|
+
File.write(secret_key, keypair.private_key)
|
70
|
+
File.write(public_key, keypair.public_key)
|
71
|
+
else
|
72
|
+
print 'Password: '
|
73
|
+
password = prompt
|
74
|
+
print "\nPassword (one more time): "
|
75
|
+
password_confirmation = prompt
|
76
|
+
if password != password_confirmation
|
77
|
+
puts "\nPasswords don't match"
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
print "\nDeriving a key from the password in order to encrypt the secret key..."
|
81
|
+
keypair = Minisign::KeyPair.new(password)
|
82
|
+
File.write(secret_key, keypair.private_key)
|
83
|
+
print " done\n"
|
84
|
+
puts "The secret key was saved as #{options[:s]} - Keep it secret!"
|
85
|
+
File.write(public_key, keypair.public_key)
|
86
|
+
puts "The public key was saved as #{options[:p]} - That one can be public."
|
87
|
+
pubkey = keypair.public_key.to_s.split("\n").pop
|
88
|
+
puts "minisign -Vm <file> -P #{pubkey}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.recreate(options)
|
93
|
+
secret_key = options[:s] || "#{Dir.home}/.minisign/minisign.key"
|
94
|
+
public_key = options[:p] || './minisign.pub'
|
95
|
+
private_key_contents = File.read(secret_key)
|
96
|
+
begin
|
97
|
+
# try without a password first
|
98
|
+
private_key = Minisign::PrivateKey.new(private_key_contents)
|
99
|
+
rescue Minisign::PasswordMissingError
|
100
|
+
print 'Password: '
|
101
|
+
private_key = Minisign::PrivateKey.new(private_key_contents, prompt)
|
102
|
+
end
|
103
|
+
File.write(public_key, private_key.public_key)
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.change_password(options)
|
107
|
+
options[:s] ||= "#{Dir.home}/.minisign/minisign.key"
|
108
|
+
private_key = begin
|
109
|
+
Minisign::PrivateKey.new(File.read(options[:s]))
|
110
|
+
rescue Minisign::PasswordMissingError
|
111
|
+
print 'Password: '
|
112
|
+
Minisign::PrivateKey.new(File.read(options[:s]), prompt)
|
113
|
+
end
|
114
|
+
print 'New Password: '
|
115
|
+
new_password = options[:W] ? nil : prompt
|
116
|
+
private_key.change_password! new_password
|
117
|
+
File.write(options[:s], private_key)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.sign(options)
|
121
|
+
# TODO: multiple files
|
122
|
+
options[:x] ||= "#{options[:m]}.minisig"
|
123
|
+
options[:s] ||= "#{Dir.home}/.minisign/minisign.key"
|
124
|
+
private_key = begin
|
125
|
+
Minisign::PrivateKey.new(File.read(options[:s]))
|
126
|
+
rescue Minisign::PasswordMissingError
|
127
|
+
print 'Password: '
|
128
|
+
Minisign::PrivateKey.new(File.read(options[:s]), prompt)
|
129
|
+
end
|
130
|
+
signature = private_key.sign(options[:m], File.read(options[:m]), options[:t], options[:c])
|
131
|
+
File.write(options[:x], signature)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.verify(options)
|
135
|
+
options[:x] ||= "#{options[:m]}.minisig"
|
136
|
+
options[:p] ||= './minisign.pub'
|
137
|
+
options[:P] ||= File.read(options[:p])
|
138
|
+
public_key = Minisign::PublicKey.new(options[:P])
|
139
|
+
message = File.read(options[:m])
|
140
|
+
signature = Minisign::Signature.new(File.read(options[:x]))
|
141
|
+
begin
|
142
|
+
verification = public_key.verify(signature, message)
|
143
|
+
rescue StandardError
|
144
|
+
puts 'Signature verification failed'
|
145
|
+
exit 1
|
146
|
+
end
|
147
|
+
return if options[:q]
|
148
|
+
return puts message if options[:o]
|
149
|
+
|
150
|
+
puts options[:Q] ? signature.trusted_comment : verification
|
151
|
+
end
|
152
|
+
|
153
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
154
|
+
# rubocop:enable Metrics/AbcSize
|
155
|
+
# rubocop:enable Metrics/MethodLength
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# rubocop:enable Metrics/ModuleLength
|
data/lib/minisign/key_pair.rb
CHANGED
@@ -5,6 +5,10 @@ module Minisign
|
|
5
5
|
class KeyPair
|
6
6
|
include Minisign::Utils
|
7
7
|
|
8
|
+
# Create a new key pair
|
9
|
+
# @param password [String] The password used to encrypt the private key
|
10
|
+
# @example
|
11
|
+
# Minisign::KeyPair.new("53cr3t P4s5w0rd")
|
8
12
|
def initialize(password = nil)
|
9
13
|
@password = password
|
10
14
|
@key_id = SecureRandom.bytes(8)
|
data/lib/minisign/private_key.rb
CHANGED
@@ -35,18 +35,19 @@ module Minisign
|
|
35
35
|
#
|
36
36
|
# @param filename [String] The filename to be used in the trusted comment section
|
37
37
|
# @param message [String] The file's contents
|
38
|
-
# @param
|
38
|
+
# @param trusted_comment [String] An optional trusted comment to be included in the signature
|
39
|
+
# @param untrusted_comment [String] An optional untrusted comment
|
39
40
|
# @return [Minisign::Signature]
|
40
|
-
def sign(filename, message,
|
41
|
+
def sign(filename, message, trusted_comment = nil, untrusted_comment = nil)
|
41
42
|
signature = ed25519_signing_key.sign(blake2b512(message))
|
42
|
-
trusted_comment
|
43
|
+
trusted_comment ||= "timestamp:#{Time.now.to_i}\tfile:#{filename}\thashed"
|
44
|
+
untrusted_comment ||= 'signature from minisign secret key'
|
43
45
|
global_signature = ed25519_signing_key.sign("#{signature}#{trusted_comment}")
|
44
46
|
Minisign::Signature.new([
|
45
|
-
|
47
|
+
"untrusted comment: #{untrusted_comment}",
|
46
48
|
Base64.strict_encode64("ED#{@key_id.pack('C*')}#{signature}"),
|
47
49
|
"trusted comment: #{trusted_comment}",
|
48
|
-
Base64.strict_encode64(global_signature)
|
49
|
-
''
|
50
|
+
"#{Base64.strict_encode64(global_signature)}\n"
|
50
51
|
].join("\n"))
|
51
52
|
end
|
52
53
|
|
@@ -61,6 +62,14 @@ module Minisign
|
|
61
62
|
"untrusted comment: #{@untrusted_comment}\n#{Base64.strict_encode64(data)}\n"
|
62
63
|
end
|
63
64
|
|
65
|
+
# Change or remove a password
|
66
|
+
#
|
67
|
+
# @param new_password [String]
|
68
|
+
def change_password!(new_password)
|
69
|
+
@password = new_password
|
70
|
+
@bytes[2..3] = [0, 0] if new_password.nil? # kdf_algorithm
|
71
|
+
end
|
72
|
+
|
64
73
|
private
|
65
74
|
|
66
75
|
def signature_algorithm
|
@@ -81,8 +90,12 @@ module Minisign
|
|
81
90
|
|
82
91
|
# @raise [RuntimeError] if the extracted public key does not match the derived public key
|
83
92
|
def assert_valid_key!
|
84
|
-
|
85
|
-
|
93
|
+
if kdf_algorithm.bytes.sum != 0 && @password.nil?
|
94
|
+
raise Minisign::PasswordMissingError, 'Missing password for encrypted key'
|
95
|
+
end
|
96
|
+
return unless @ed25519_public_key_bytes != ed25519_signing_key.verify_key.to_bytes.bytes
|
97
|
+
|
98
|
+
raise Minisign::PasswordIncorrectError, 'Wrong password for that key'
|
86
99
|
end
|
87
100
|
|
88
101
|
def key_data(password, bytes)
|
data/lib/minisign/public_key.rb
CHANGED
@@ -34,12 +34,8 @@ module Minisign
|
|
34
34
|
# @raise RuntimeError on mismatching key ids
|
35
35
|
def verify(signature, message)
|
36
36
|
assert_matching_key_ids!(signature.key_id, key_id)
|
37
|
-
|
38
|
-
|
39
|
-
ed25519_verify_key.verify(signature.trusted_comment_signature, signature.signature + signature.trusted_comment)
|
40
|
-
rescue Ed25519::VerifyError
|
41
|
-
raise 'Comment signature verification failed'
|
42
|
-
end
|
37
|
+
verify_message_signature(signature.signature, message)
|
38
|
+
verify_comment_signature(signature.trusted_comment_signature, signature.signature + signature.trusted_comment)
|
43
39
|
"Signature and comment signature verified\nTrusted comment: #{signature.trusted_comment}"
|
44
40
|
end
|
45
41
|
|
@@ -50,6 +46,18 @@ module Minisign
|
|
50
46
|
|
51
47
|
private
|
52
48
|
|
49
|
+
def verify_comment_signature(signature, comment)
|
50
|
+
ed25519_verify_key.verify(signature, comment)
|
51
|
+
rescue Ed25519::VerifyError
|
52
|
+
raise Minisign::SignatureVerificationError, 'Comment signature verification failed'
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_message_signature(signature, message)
|
56
|
+
ed25519_verify_key.verify(signature, blake2b512(message))
|
57
|
+
rescue Ed25519::VerifyError => e
|
58
|
+
raise Minisign::SignatureVerificationError, e
|
59
|
+
end
|
60
|
+
|
53
61
|
def untrusted_comment
|
54
62
|
if @lines.length == 1
|
55
63
|
"minisign public key #{key_id}"
|
@@ -75,7 +83,10 @@ module Minisign
|
|
75
83
|
end
|
76
84
|
|
77
85
|
def assert_matching_key_ids!(key_id1, key_id2)
|
78
|
-
|
86
|
+
return if key_id1 == key_id2
|
87
|
+
|
88
|
+
raise Minisign::SignatureVerificationError,
|
89
|
+
"Signature key id is #{key_id1}\nbut the key id in the public key is #{key_id2}"
|
79
90
|
end
|
80
91
|
end
|
81
92
|
end
|
data/lib/minisign.rb
CHANGED
@@ -4,8 +4,10 @@ require 'ed25519'
|
|
4
4
|
require 'base64'
|
5
5
|
require 'rbnacl'
|
6
6
|
|
7
|
+
require 'minisign/cli'
|
7
8
|
require 'minisign/utils'
|
8
9
|
require 'minisign/public_key'
|
9
10
|
require 'minisign/signature'
|
10
11
|
require 'minisign/private_key'
|
11
12
|
require 'minisign/key_pair'
|
13
|
+
require 'minisign/error'
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesse Shawl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ed25519
|
@@ -40,17 +40,21 @@ dependencies:
|
|
40
40
|
version: '7.1'
|
41
41
|
description: Verify minisign signatures
|
42
42
|
email: jesse@jesse.sh
|
43
|
-
executables:
|
43
|
+
executables:
|
44
|
+
- minisign
|
44
45
|
extensions: []
|
45
46
|
extra_rdoc_files: []
|
46
47
|
files:
|
48
|
+
- bin/minisign
|
47
49
|
- lib/minisign.rb
|
50
|
+
- lib/minisign/cli.rb
|
51
|
+
- lib/minisign/error.rb
|
48
52
|
- lib/minisign/key_pair.rb
|
49
53
|
- lib/minisign/private_key.rb
|
50
54
|
- lib/minisign/public_key.rb
|
51
55
|
- lib/minisign/signature.rb
|
52
56
|
- lib/minisign/utils.rb
|
53
|
-
homepage: https://
|
57
|
+
homepage: https://github.com/jshawl/minisign
|
54
58
|
licenses:
|
55
59
|
- MIT
|
56
60
|
metadata:
|