App-locket
view release on metacpan or search on metacpan
lib/App/locket.pm view on Meta::CPAN
package App::locket;
BEGIN {
$App::locket::VERSION = '0.0022';
}
# ABSTRACT: Copy secrets from a YAML/JSON cipherstore into the clipboard (pbcopy, xsel, xclip)
use strict;
use warnings;
BEGIN {
# Safe path
$ENV{ PATH } = '/bin:/usr/bin';
}
use Term::ReadKey;
END {
ReadMode 0;
}
use File::HomeDir;
use Path::Class;
use JSON; my $JSON = JSON->new->pretty;
use YAML::XS();
use File::Temp;
use Term::EditorEdit;
use Try::Tiny;
use String::Util qw/ trim /;
my $usage;
BEGIN {
$usage = <<_END_;
Usage: locket [options] setup|edit|<query>
--copy Copy value to clipboard using pbcopy, xsel, or xclip
--delay <delay> Keep value in clipboard for <delay> seconds
If value is still in the clipboard at the end of
<delay> then it will be automatically wiped from
the clipboard
--unsafe Turn the safety off. This will disable prompting
before emitting any sensitive information in
plaintext. There will be no opportunity to
abort (via CTRL-C)
--cfg <file> Use <file> for configuration
setup Setup a new or edit an existing user configuration
file (~/.locket/cfg)
edit Edit the cipherstore
The configuration must have an "edit" value, e.g.:
/usr/bin/vim -n ~/.locket.gpg
/<query> Search the cipherstore for <query> and emit the
resulting secret
The configuration must have a "read" value to
tell it how to read the cipherstore. Only piped
commands are supported today, and they should
be something like:
</usr/local/bin/gpg -q --no-tty -d ~/.locket.gpg'
If the found key in the cipherstore is of the format
"<username>@<site>" then the username will be emitted
first before the secret (which is assumed to be a password/passphrase)
Example YAML cipherstore:
%YAML 1.1
---
# A GMail identity
alice\@gmail: p455w0rd
# Some frequently used credit card information
cc4123: |
4123412341234123
01/23
123
_END_
}
use Getopt::Usaginator $usage;
use Digest::SHA qw/ sha1_hex sha512_hex /;
use List::MoreUtils qw/ :all /;
use Hash::Dispatch;
use App::locket::Locket;
use App::locket::TextRandomart;
use App::locket::Util;
use App::locket::Moose;
my %n2k = (
( map { $_ => $_ + 1 } 0 .. 8 ),
9 => 0,
);
my %k2n = map { trim $_ } reverse %n2k;
my %default_options = (
delay => 45,
);
has locket => qw/ reader locket writer _locket isa App::locket::Locket lazy_build 1 /, handles =>
[qw/
cfg plaincfg write_cfg reload_cfg can_read read passphrase require_passphrase
store
/];
sub _build_locket {
my $self = shift;
my $locket = App::locket::Locket->open( $self->cfg_file );
return $locket;
}
has home => qw/ is ro lazy_build 1 /;
sub _build_home {
my $self = shift;
my $home = File::HomeDir->my_data;
if ( defined $home ) {
$home = dir $home, '.locket';
}
return $home;
}
lib/App/locket.pm view on Meta::CPAN
next unless defined $line;
next unless length $line;
$self->dispatch( $line );
}
},
'?' => sub {
my ( $self, $method ) = @_;
my $cfg_file = $self->cfg_file;
my $cfg_file_size = -f $cfg_file && -s _;
defined && length or $_ = '-1' for $cfg_file_size;
{
my ( $read, $edit, $copy, $paste ) =
map { defined $_ ? $_ : '~' } @{ $self->cfg }{qw/ read edit copy paste /};
my $randomart = App::locket::TextRandomart->randomart( sha1_hex $self->plaincfg );
my @randomart = split "\n", $randomart;
@randomart = map { " $_ " } @randomart;
$randomart[ 1 ] .= "$cfg_file ($cfg_file_size)";
$randomart[ 3 ] .= " $read";
$randomart[ 4 ] .= " $edit";
$randomart[ 5 ] .= " $copy";
$randomart[ 6 ] .= " $paste";
$randomart = join "\n", @randomart;
$self->stdout( <<_END_ );
App::locket @{[ $App::locket::VERSION || '0.0' ]}
$randomart
_END_
$self->say_stdout;
}
return;
my $stash = $self->stash;
my $query = $self->query;
if ( @$query ) {
$self->say_stdout( sprintf " /: %s", join '/', @$query );
}
my ( $target, $entry ) = @$stash{qw/ target entry /};
if ( defined $target ) {
$self->say_stdout( sprintf " =: %s", $target );
}
},
'setup' => sub {
my ( $self, $method ) = @_;
my $cfg_file = $self->cfg_file;
my $plaincfg = $self->plaincfg;
if ( ! defined $plaincfg || $plaincfg =~ m/^\S*$/ ) {
$plaincfg = <<_END_;
%YAML 1.1
---
#read: '</usr/bin/gpg -d <file>'
#read: '</usr/bin/openssl des3 -d -in <file>'
#edit: '/usr/bin/vim -n <file>'
#copy: -
#paste: -
_END_
}
my $file = File::Temp->new( template => '.locket.cfg.XXXXXXX', dir => '.', unlink => 1 ); # TODO A better dir?
my $plaincfg_edit = Term::EditorEdit->edit( file => $file, document => $plaincfg );
if ( length $plaincfg_edit ) {
$self->write_cfg( $plaincfg_edit );
$self->stdout_clear;
$self->say_stdout( "# Reload\n---\n" );
$self->reload_cfg;
$self->dispatch( '?' );
}
else {
}
},
'cfg' => 'setup',
'config' => 'setup',
qr/^q(?:u(?:i?)?)?$/ => 'quit',
'quit' => sub {
my ( $self, $method ) = @_;
exit 0;
},
qr/^h(?:e(?:l?)?)?$/ => 'help',
'help' => sub {
my ( $self, $method ) = @_;
my $edit = $self->cfg->{ edit };
my $read = $self->cfg->{ read };
my $cfg_file = $self->locket->cfg_file;
$self->stdout( <<_END_ );
/<query> Search the store for <query> and emit the
resulting secret
Alternatively, append a term to the last query
and re-search
. Redisplay the results of the last query
.. Pop the last term off the last query (if any)
and re-search
//<query> Search the store for <query>, ignoring
any previous query
list List all the entries in the store
edit Edit the store (via $edit)
read Show the plainstore through \$PAGER/sensible-pager (via $read)
cfg Configure locket ($cfg_file)
lib/App/locket.pm view on Meta::CPAN
my $self = shift;
my $cmd = shift;
my $value = shift;
open my $pipe, '-|', $cmd or die $!;
return join '', <$pipe>;
}
1;
=pod
=head1 NAME
App::locket - Copy secrets from a YAML/JSON cipherstore into the clipboard (pbcopy, xsel, xclip)
=head1 VERSION
version 0.0022
=head1 SYNOPSIS
# Setup the configuration file for the cipherstore:
# (How to read the cipherstore, how to edit the cipherstore, etc.)
$ locket setup
# Add or change data in the cipherstore:
$ locket edit
# List all the entries in the cipherstore:
$ locket /
# Show a secret from the cipherstore:
$ locket /alice@gmail
=head1 DESCRIPTION
App::locket is a tool for querying a simple YAML/JSON-based cipherstore
It has a simple commandline-based querying method and supports copying into the clipboard
Currently, encryption and decryption is performed via external tools (e.g. GnuPG, OpenSSL, etc.)
App::locket is best used with:
* gnupg.vim L<http://www.vim.org/scripts/script.php?script_id=661>
* openssl.vim L<http://www.vim.org/scripts/script.php?script_id=2012>
* EasyPG L<http://www.emacswiki.org/emacs/AutoEncryption>
=head1 SECURITY
=head2 Encryption/decryption
App::locket defers actual encryption/decryption to external tools. The choice of the actual
cipher/encryption method is left up to you
If you're using GnuPG, then you could use C<gpg-agent> for passphrase prompting and limited retention
=head2 In-memory encryption
App::locket does not perform any in-memory encryption; once the cipherstore is loaded it is exposed in memory
In addition, if the process is swapped out while running then the plaintextstore could be written to disk
Encrypting swap is one way of mitigating this problem
=head2 Clipboard access
App::locket uses third-party tools for read/write access to the clipboard. It tries to detect if
C<pbcopy>, C<xsel>, or C<xclip> are available. It does this by looking in C</bin> and C</usr/bin>
=head2 Purging the clipboard
By default, App::locket will purge the clipboard of a secret it put there after a set delay. It will try to verify that it is
wiping what it put there in the first place (so it doesn't accidentally erase something else you copied)
If for some reason App::locket cannot read from the clipboard, it will purge it just in case
If you prematurely cancel a secret copying operation via CTRL-C, App::locket will catch the signal and purge the clipboard first
=head2 Attack via configuration
Currently, App::locket does not encrypt/protect the configuration file. This means an attacker can potentially (unknown to you) modify
the reading/editing commands to divert the plaintext elsewhere
There is an option to lock the configuration file, but given the ease of code injection you're probably better off installing and using App::locket in a dedicated VM
=head2 Resetting $PATH
C<$PATH> is reset to C</bin:/usr/bin>
=head1 INSTALL
$ cpanm -i App::locket
=head1 INSTALL cpanm
L<http://search.cpan.org/perldoc?App::cpanminus#INSTALLATION>
=head1 USAGE
locket [options] setup|edit|<query>
--delay <delay> Keep value in clipboard for <delay> seconds
If value is still in the clipboard at the end of
<delay> then it will be automatically wiped from
the clipboard
--unsafe Turn the safety off. This will disable prompting
before emitting any sensitive information in
plaintext. There will be no opportunity to
abort (via CTRL-C)
setup Setup a new or edit an existing user configuration
file (~/.locket/cfg)
edit Edit the cipherstore
The configuration must have an "edit" value, e.g.:
/usr/bin/vim -n ~/.locket.gpg
/<query> Search the cipherstore for <query> and emit the
resulting secret
The configuration must have a "read" value to
tell it how to read the cipherstore. Only piped
commands are supported today, and they should
be something like:
</usr/local/bin/gpg -q --no-tty -d ~/.locket.gpg'
If the found key in the cipherstore is of the format
"<username>@<site>" then the username will be emitted
first before the secret (which is assumed to be a password/passphrase)
Type <help> in-process for additional usage
=head1 Example YAML cipherstore
%YAML 1.1
---
# A GMail identity
alice@gmail: p455w0rd
# Some frequently used credit card information
cc4123: |
4123412341234123
01/23
123
=head1 Example configuration file
%YAML 1.1
---
read: '</usr/local/bin/gpg --no-tty --decrypt --quiet ~/.locket.gpg'
edit: '/usr/bin/vim -n ~/.locket.gpg'
=head1 AUTHOR
Robert Krimen <robertkrimen@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2011 by Robert Krimen.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
__END__
( run in 1.629 second using v1.01-cache-2.11-cpan-e1769b4cff6 )