From c83725c4fb7ee84b234d3faeffb03ba7c94c2e11 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Mon, 7 Feb 2011 18:52:22 +0100 Subject: Initial commit --- .gitignore | 3 + Build.PL | 26 +++++ bin/raps2 | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++++ t/00-compile.t | 8 ++ t/10-pod-coverage.t | 8 ++ 5 files changed, 339 insertions(+) create mode 100644 .gitignore create mode 100644 Build.PL create mode 100755 bin/raps2 create mode 100644 t/00-compile.t create mode 100644 t/10-pod-coverage.t diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..201c581 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/_build +/Build +/blib diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..0186dbc --- /dev/null +++ b/Build.PL @@ -0,0 +1,26 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Module::Build; + +my $build = Module::Build->new( + build_requires => { + 'Test::More' => 0, + 'Test::Compile' => 0, + 'Test::Pod' => 0, + 'Test::Command' => 0, + }, + dist_name => 'raps2', + dist_version_from => 'bin/raps2', + license => 'unrestricted', + requires => { + 'perl' => '5.10.0', + 'autodie' => 0, + 'Crypt::CBC' => 0, + 'Crypt::Eksblowfish' => 0, + 'POSIX' => 0, + }, + script_files => 'bin/', +); +$build->create_build_script(); diff --git a/bin/raps2 b/bin/raps2 new file mode 100755 index 0000000..68df161 --- /dev/null +++ b/bin/raps2 @@ -0,0 +1,294 @@ +#!/usr/bin/env perl +## Copyright © 2011 by Daniel Friesel +## License: WTFPL: +## 0. You just DO WHAT THE FUCK YOU WANT TO. +use strict; +use warnings; +use 5.010; +use autodie; + +use Crypt::CBC; +use Crypt::Eksblowfish; +use Crypt::Eksblowfish::Bcrypt qw(bcrypt_hash en_base64 de_base64); +use File::Path qw(make_path); +use File::Slurp qw(slurp write_file); +use Getopt::Std; +use POSIX; + +my $VERSION = '0.1'; + +my %state = ( + cost => 12, + salt => new_salt(), +); + +my ($action, @args) = @ARGV; + +my %opt; + +sub cmd_add { + my ($name) = @_; + my $init = get_xdg_config_home() . '/init'; + my $store = get_xdg_data_home() . "/${name}"; + my $pass; + + if (-e $store) { + die("This password name already exists\n"); + } + + my $cipher; + my $password; + + $password = get_password(); + undef %state; + load_state_from($init); + $state{'salt'} = new_salt(); + $state{'cost'} //= 12; + + $state{'url'} = read_input( + prefix => 'URL' + ); + $state{'login'} = read_input( + prefix => 'Login' + ); + $pass = read_input( + prefix => 'Password', + invisible => 1, + verify => 1, + ); + + $cipher = setup_cipher($password); + + $state{'hash'} = $cipher->encrypt_hex($pass); + + save_state_to($store); +} + +sub cmd_get { + my ($name) = @_; + my $store = get_xdg_data_home() . "/${name}"; + my $password; + my $cipher; + + if (not -e $store) { + die("No such password\n"); + } + + $password = get_password(); + load_state_from($store); + + $cipher = setup_cipher($password); + + printf( + "URL : %s\nLogin : %s\nPassword: %s\n", + $state{'url'}, + $state{'login'}, + $cipher->decrypt_hex($state{'hash'}), + ); +} + +sub create_dot_dirs { + make_path(get_xdg_config_home()); + make_path(get_xdg_data_home()); +} + +sub create_pass { + my ($passfile) = @_; + my $pass; + my $hash; + + say "raps2 was never run before. Please set master password first."; + $pass = read_input( + prefix => 'Password', + invisible => 1, + verify => 1, + ); + + $hash = en_base64(bcrypt_hash({ + key_nul => 1, + cost => $state{'cost'}, + salt => $state{'salt'}, + }, $pass)); + write_file( + $passfile, + "cost $state{cost}\n", + "salt $state{salt}\n", + "hash $hash\n", + ); + + return $pass; +} + +sub get_password { + my $pass; + my $passfile = get_xdg_config_home() . '/password'; + + if (not -e $passfile) { + return create_pass($passfile); + } + + load_state_from($passfile); + $pass = read_input( + prefix => 'Master password', + invisible => 1, + ); + if (en_base64(bcrypt_hash({ + key_nul => 1, + cost => $state{'cost'}, + salt => $state{'salt'}, + }, $pass)) ne $state{'hash'}) { + die("Invalid passphrase\n"); + } + + return $pass; +} + +sub get_xdg_config_home { + my $env = $ENV{'XDG_CONFIG_HOME'}; + my $home = $ENV{'HOME'}; + + if ($env) { + return "${env}/raps2"; + } + else { + return "${home}/.config/raps2"; + } +} + +sub get_xdg_data_home { + my $env = $ENV{'XDG_DATA_HOME'}; + my $home = $ENV{'HOME'}; + + if ($env) { + return "${env}/raps2"; + } + else { + return "${home}/.local/share/raps2"; + } +} + +sub load_state_from { + my ($file) = @_; + + if (not -e $file) { + return; + } + + for my $line (slurp($file)) { + my ($key, $value) = split(qr{\s+}, $line); + + if (not ($key and $value)) { + next; + } + + $state{$key} = $value; + } + return; +} + +sub new_salt { + my $salt = q{}; + + for (1 .. 16) { + $salt .= chr(0x21 + int(rand(90))); + } + + return $salt; +} + +sub save_state_to { + my ($file) = @_; + my $raw = q{}; + + while (my ($key, $value) = each(%state)) { + $raw .= "${key} ${value}\n"; + } + + write_file($file, $raw); +} + +sub setup_cipher { + my ($password) = @_; + + my $eksblowfish = Crypt::Eksblowfish->new( + $state{'cost'}, + $state{'salt'}, + $password, + ); + return Crypt::CBC->new(-cipher => $eksblowfish); +} + +sub read_input { + my %opts = @_; + my ($prefix, $invisible, $verify) + = @opts{'prefix', 'invisible', 'verify'}; + my $term = POSIX::Termios->new(); + my ($input1, $input2); + + if ($invisible) { + $term->getattr(0); + $term->setlflag($term->getlflag() & ~POSIX::ECHO); + $term->setattr(0, POSIX::TCSANOW); + } + + print "${prefix}: "; + $input1 = readline(STDIN); + + if ($invisible) { + print "\n"; + } + + if ($verify) { + print "Verify: "; + $input2 = readline(STDIN); + + if ($invisible) { + print "\n"; + } + } + + if ($invisible) { + $term->setlflag($term->getlflag() | POSIX::ECHO); + $term->setattr(0, POSIX::TCSANOW); + } + + if ($verify and $input1 ne $input2) { + die("Lines do not match\n"); + } + + chomp $input1; + return $input1; +} + +create_dot_dirs(); + +given ($action) { + when ('add') { cmd_add(@args) } + when ('get') { cmd_get(@args) } +} + +__END__ + +=head1 NAME + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 OPTIONS + +=head1 EXIT STATUS + +=head1 CONFIGURATION + +=head1 DEPENDENCIES + +=head1 BUGS AND LIMITATIONS + +=head1 AUTHOR + +Copyright (C) 2011 by Daniel Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/t/00-compile.t b/t/00-compile.t new file mode 100644 index 0000000..abcaf94 --- /dev/null +++ b/t/00-compile.t @@ -0,0 +1,8 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.010; +use Test::More; +use Test::Compile; + +all_pl_files_ok('bin/raps2'); diff --git a/t/10-pod-coverage.t b/t/10-pod-coverage.t new file mode 100644 index 0000000..3f6ae1a --- /dev/null +++ b/t/10-pod-coverage.t @@ -0,0 +1,8 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.010; +use Test::More; +use Test::Pod; + +all_pod_files_ok('bin/raps2'); -- cgit v1.2.3