diff options
Diffstat (limited to 'lib/App/Raps2.pm')
-rw-r--r-- | lib/App/Raps2.pm | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/lib/App/Raps2.pm b/lib/App/Raps2.pm new file mode 100644 index 0000000..4746e5a --- /dev/null +++ b/lib/App/Raps2.pm @@ -0,0 +1,193 @@ +package App::Raps2; + + + + +use strict; +use warnings; +use autodie; +use 5.010; + +use base 'Exporter'; + +use App::Raps2::Password; +use App::Raps2::UI; +use File::Path qw(make_path); +use File::Slurp qw(slurp write_file); + +our @EXPORT_OK = (); +our $VERSION = '0.1'; + +sub create_salt { + my $salt = q{}; + + for (1 .. 16) { + $salt .= chr(0x21 + int(rand(90))); + } + + return $salt; +} + +sub file_to_hash { + my ($file) = @_; + my %ret; + + for my $line (slurp($file)) { + my ($key, $value) = split(qr{\s+}, $line); + + if (not ($key and $value)) { + next; + } + + $ret{$key} = $value; + } + return %ret; +} + +sub new { + my ($obj, %conf) = @_; + my $ref = {}; + + $ref->{'xdg_conf'} = $ENV{'XDG_CONFIG_HOME'} // "$ENV{HOME}/.config/raps2"; + $ref->{'xdg_data'} = $ENV{'XDG_DATA_HOME'} // + "$ENV{HOME}/.local/share/raps2"; + + $ref->{'ui'} = App::Raps2::UI->new(); + + $ref->{'default'} = \%conf; + + return bless($ref, $obj); +} + +sub sanity_check { + my ($self) = @_; + + make_path($self->{'xdg_conf'}); + make_path($self->{'xdg_data'}); + + if (not -e $self->{'xdg_conf'} . '/password') { + $self->create_config(); + } + + return; +} + +sub get_master_password { + my ($self) = @_; + my $pass = $self->{'ui'}->read_pw('Master Password', 0); + + $self->{'pass'} = App::Raps2::Password->new( + cost => $self->{'default'}->{'cost'}, + salt => $self->{'master_salt'}, + passphrase => $pass, + ); + + if (not $self->{'pass'}->verify($self->{'master_hash'})) { + return undef; + } +} + +sub create_config { + my ($self) = @_; + my $cost = 12; + my $salt = create_salt(); + my $pass = $self->{'ui'}->read_pw('Master Password', 1); + + $self->{'pass'} = App::Raps2::Password->new( + cost => $cost, + salt => $salt, + passphrase => $pass, + ); + my $hash = $self->{'pass'}->crypt(); + + write_file( + $self->{'xdg_conf'} . '/password', + "cost ${cost}\n", + "salt ${salt}\n", + "hash ${hash}\n", + ); +} + +sub load_config { + my ($self) = @_; + my %cfg = file_to_hash($self->{'xdg_conf'} . '/password'); + $self->{'master_hash'} = $cfg{'hash'}; + $self->{'master_salt'} = $cfg{'salt'}; + $self->{'default'}->{'cost'} //= $cfg{'cost'}; +} + +sub cmd_add { + my ($self, $name) = @_; + my $pwfile = $self->{'xdg_data'} . "/${name}"; + my $ui = $self->{'ui'}; + + if (-e $pwfile) { + return undef; + } + + $self->get_master_password(); + + my $salt = create_salt(); + my $url = $ui->read_line('URL'); + my $login = $ui->read_line('Login'); + my $pass = $ui->read_pw('Password', 1); + my $extra = $ui->read_multiline('Additional content'); + + $self->{'pass'}->salt($salt); + my $pass_hash = $self->{'pass'}->encrypt($pass); + my $extra_hash = ( + $extra ? + $self->{'pass'}->encrypt($extra) : + q{} + ); + + + write_file( + $pwfile, + "url ${url}\n", + "login ${login}\n", + "salt ${salt}\n", + "hash ${pass_hash}\n", + "extra ${extra_hash}\n", + ); +} + +sub cmd_dump { + my ($self, $name) = @_; + my $pwfile = $self->{'xdg_data'} . "/${name}"; + + if (not -e $pwfile) { + return undef; + } + + my %key = file_to_hash($pwfile); + + $self->get_master_password(); + + $self->{'pass'}->salt($key{'salt'}); + + $self->{'ui'}->output( + ['URL', $key{'url'}], + ['Login', $key{'login'}], + ['Password', $self->{'pass'}->decrypt($key{'hash'})], + ); + if ($key{'extra'}) { + say $self->{'pass'}->decrypt($key{'extra'}); + } +} + + +sub cmd_info { + my ($self, $name) = @_; + my $pwfile = $self->{'xdg_data'} . "/${name}"; + + if (not -e $pwfile) { + return undef; + } + + my %key = file_to_hash($pwfile); + $self->{'ui'}->output( + ['URL', $key{'url'}], + ['Login', $key{'login'}], + ); +} |