1 package App
::GroupSecret
;
2 # ABSTRACT: A simple tool for maintaining a shared group secret
8 our $VERSION = '0.304'; # VERSION
10 use App
::GroupSecret
::Crypt
qw(generate_secure_random_bytes read_openssh_key_fingerprint);
11 use App
::GroupSecret
::File
;
12 use Getopt
::Long
2.38 qw(GetOptionsFromArray);
20 return bless {}, $class;
34 # Parse options using pass_through so that we can pick out the global
35 # options, wherever they are in the arg list, and leave the rest to be
36 # parsed by each individual command.
37 Getopt
::Long
::Configure
('pass_through');
40 'file|f=s' => \
$filepath,
42 'manual|man' => \
$man,
43 'private-key|k=s' => \
$private_key,
44 'version|v' => \
$version,
46 Getopt
::Long
::Configure
('default');
48 pod2usage
(-exitval
=> 1, -verbose
=> 99, -sections
=> [qw(SYNOPSIS OPTIONS COMMANDS)]) if $help;
49 pod2usage
(-verbose
=> 2) if $man;
50 return print "groupsecret ${VERSION}\n" if $version;
52 $self->{private_key
} = $private_key if $private_key;
53 $self->{filepath
} = $filepath if $filepath;
57 add_keys
=> 'add_key',
58 change_secret
=> 'set_secret',
59 delete_key
=> 'delete_key',
60 delete_keys
=> 'delete_key',
61 list_keys
=> 'list_keys',
62 print => 'print_secret',
63 print_secret
=> 'print_secret',
64 remove_key
=> 'delete_key',
65 remove_keys
=> 'delete_key',
66 set_secret
=> 'set_secret',
67 show_secret
=> 'print_secret',
68 update_secret
=> 'set_secret',
71 unshift @args, 'print' if !@args || $args[0] =~ /^-/;
73 my $command = shift @args;
74 my $lookup = $command;
76 my $method = '_action_' . ($commands{$lookup} || '');
78 if (!$self->can($method)) {
79 warn "Unknown command: $command\n";
83 $self->$method(@args);
88 shift-
>{filepath
} ||= $ENV{GROUPSECRET_KEYFILE
} || 'groupsecret.yml';
94 return $self->{file
} ||= App
::GroupSecret
::File-
>new($self->filepath);
99 shift-
>{private_key
} ||= $ENV{GROUPSECRET_PRIVATE_KEY
} || "$ENV{HOME}/.ssh/id_rsa";
102 sub _action_print_secret
{
108 'decrypt!' => \
$decrypt,
111 my $file = $self->file;
112 my $filepath = $file->filepath;
113 die "No keyfile '$filepath' exists -- use the \`add-key' command to create one.\n"
114 unless -e
$filepath && !-d
$filepath;
115 die "No secret in keyfile '$filepath' exists -- use the \`set-secret' command to set one.\n"
119 my $private_key = $self->private_key;
120 my $secret = $file->decrypt_secret(private_key
=> $private_key) or die "No secret.\n";
128 sub _action_set_secret
{
131 my $keep_passphrase = 0;
134 'keep-passphrase!' => \
$keep_passphrase,
137 my $secret_spec = shift;
139 warn "You must specify a secret to set.\n";
146 if ($secret_spec =~ /^rand:(\d+)$/i) {
147 my $rand = encode_base64
(generate_secure_random_bytes
($1), '');
150 elsif ($secret_spec eq '-') {
151 my $in = do { local $/; <STDIN
> };
154 elsif ($secret_spec =~ /^file:(.*)$/i) {
158 $secret = $secret_spec;
161 my $file = $self->file;
163 if ($keep_passphrase) {
164 my $private_key = $self->private_key;
165 $passphrase = $file->decrypt_secret_passphrase($private_key);
166 $file->encrypt_secret($secret, $passphrase);
169 $passphrase = generate_secure_random_bytes
(32);
170 $file->encrypt_secret($secret, $passphrase);
171 $file->encrypt_secret_passphrase($passphrase);
177 sub _action_add_key
{
185 'update|u' => \
$update,
188 my $file = $self->file;
189 my $keys = $file->keys;
191 my $opts = {embed
=> $embed};
193 for my $public_key (@_) {
194 my $info = read_openssh_key_fingerprint
($public_key);
196 if ($keys->{$info->{fingerprint
}} && !$update) {
197 my $formatted_key = $file->format_key($info);
198 print "SKIP\t$formatted_key\n";
202 if ($file->secret && !$opts->{passphrase
}) {
203 my $private_key = $self->private_key;
204 my $passphrase = $file->decrypt_secret_passphrase($private_key);
205 $opts->{passphrase
} = $passphrase;
208 local $opts->{fingerprint_info
} = $info;
209 my ($fingerprint, $key) = $file->add_key($public_key, $opts);
211 local $key->{fingerprint
} = $fingerprint;
212 my $formatted_key = $file->format_key($key);
213 print "ADD\t$formatted_key\n";
219 sub _action_delete_key
{
222 my $file = $self->file;
224 for my $fingerprint (@_) {
225 if ($fingerprint =~ s/^(?:MD5|SHA1|SHA256)://) {
226 $fingerprint =~ s/://g;
229 my $info = read_openssh_key_fingerprint
($fingerprint);
230 $fingerprint = $info->{fingerprint
};
233 my $key = $file->keys->{$fingerprint};
234 $file->delete_key($fingerprint) if $key;
236 local $key->{fingerprint
} = $fingerprint;
237 my $formatted_key = $file->format_key($key);
238 print "DELETE\t$formatted_key\n";
244 sub _action_list_keys
{
247 my $file = $self->file;
248 my $keys = $file->keys;
250 while (my ($fingerprint, $key) = each %$keys) {
251 local $key->{fingerprint
} = $fingerprint;
252 my $formatted_key = $file->format_key($key);
253 print "$formatted_key\n";
267 App::GroupSecret - A simple tool for maintaining a shared group secret
275 This module is part of the command-line interface for managing keyfiles.
277 See L<groupsecret> for documentation.
283 $script = App::GroupSecret->new;
285 Construct a new script object.
289 $script->main(@ARGV);
291 Run a command with the given command-line arguments.
295 $filepath = $script->filepath;
297 Get the path to the keyfile.
301 $file = $script->file;
303 Get the L<App::GroupSecret::File> instance for the keyfile.
307 $filepath = $script->private_key;
309 Get the path to a private key used to decrypt the keyfile.
313 Please report any bugs or feature requests on the bugtracker website
314 L<https://github.com/chazmcgarvey/groupsecret/issues>
316 When submitting a bug or request, please include a test-file or a
317 patch to an existing test-file that illustrates the bug or desired
322 Charles McGarvey <chazmcgarvey@brokenzipper.com>
324 =head1 COPYRIGHT AND LICENSE
326 This software is Copyright (c) 2017 by Charles McGarvey.
328 This is free software, licensed under:
330 The MIT (X11) License