Passwd-Keyring-PWSafe3

 view release on metacpan or  search on metacpan

lib/Passwd/Keyring/PWSafe3.pm  view on Meta::CPAN


=head1 DATA MAPPING

Group name is mapped to Password Safe folder.

Realm is mapped as password title.

Username and password are ... well, used as username and password.

=head1 SUBROUTINES/METHODS

=head2 new(app=>'app name', group=>'passwords folder', file=>'pwsafe3 file', master_password=>'secret or callback', lazy_save=>1)

Initializes the processing. Croaks if Crypt::PWSafe3 is not installed or
master password is invalid. May create password file if it is missing.

Handled parameters:

=over 4

=item app

Symbolic application name (used in password notes)

=item group

Name for the password group (used as folder name)

=item file

Location of .pwsafe3 file. If not given, C<passwd-keyring.pwsafe3> in
user home directory is used. Will be created if does not exist. Note:
absolute path is required, relative paths are very error prone.

=item master_password 

Password required to unlock the file. Can be given as string, or
as callback returning a string (usually some way of interactively
asking user for the password).  The callback gets two parameters: app
and file.

If this param is missing, module will prompt interactively for this
password using console prompt.

=item lazy_save

if given, asks not to save the file after every change (saving is
fairly time consuming), but only when $keyring->save is called or when
keyring is destroyed.

=back

Note: it of course does not make much sense to keep app passwords in
encrypted storage if master password is saved in plain text. The
module most natural usage is to interactively ask for master password
(and use it to protect noticeable number of application-specific
passwords).

Ideas of how to workaround this obstacle are welcome. I loosely
consider either caching master password per desktop session
(implementing sht. similar to ssh-agent/gpg-agent or using one of
those somehow), or integrating the tool with PAM to use actual system
password, or both - but while it seems doable on Linux, cross platform
solution is not so easy.

=cut

sub new {
    my ($cls, %args) = @_;

    my $self = {};
    $self->{app} = $args{app} || 'Passwd::Keyring::PWSafe3';
    $self->{group} = $args{group} || 'Passwd::Keyring';
    $self->{lazy_save} = $args{lazy_save};
    my $file = $args{file} || File::Spec->catfile(File::HomeDir->users_data, "passwd-keyring.pwsafe3");

    unless(File::Spec->file_name_is_absolute($file)) {
        croak("Absolute path to .pwsafe3 file is required, but relative path '$file' given");
    }
    my $parent_dir = dirname($file);
    unless(-d $parent_dir) {
        croak("Directory $parent_dir (parent directory of file $file) does not exist");
    }

    # TODO: escape group (note that . are used for hierarchy!)
    # TODO: some locking or maybe detect gui

    bless $self;

    my $master = $args{master_password} || \&_prompt_for_password;
    if(ref($master) eq 'CODE') {
        $master = $master->($self->{app}, $file);
    }

    $self->{vault} = Crypt::PWSafe3->new(file=>$file, password=>$master);

    return $self;
}

sub DESTROY {
    my $self = shift;
    $self->save();
}

sub _prompt_for_password {
    my ($app, $file) = @_;
    print "* The applicaton $app is requesting to access\n";
    print "* the Password Safe file $file\n";
    if (-f $file) {
        print "* Enter master password necessary to unlock this file.\n";
    } else {
        print "* (the file does not exist and will be created on first password save)\n";
        print "* Enter master password which will protect this file.\n";
    }
    while(1) {
        print "  Master password: ";
        ReadMode 'noecho';
        my $password = ReadLine 0; chomp($password);
        ReadMode 'normal';
        print "\n";
        return $password if $password;



( run in 0.802 second using v1.01-cache-2.11-cpan-df04353d9ac )