File Coverage

blib/lib/Passwd/Keyring/PWSafe3.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Passwd::Keyring::PWSafe3;
2              
3 1     1   17432 use warnings;
  1         2  
  1         30  
4 1     1   5 use strict;
  1         1  
  1         29  
5             #use parent 'Keyring';
6 1     1   216 use Crypt::PWSafe3;
  0            
  0            
7             use File::Spec;
8             use File::Basename;
9             use Term::ReadKey; # For secure password prompt
10             use Carp;
11              
12             =head1 NAME
13              
14             Passwd::Keyring::PWSafe3 - Password storage based on Password Safe encrypted files
15              
16             =head1 VERSION
17              
18             Version 0.21
19              
20             =cut
21              
22             our $VERSION = '0.21';
23              
24             our $APP_NAME = "Passwd::Keyring";
25             our $FOLDER_NAME = "Perl-Passwd-Keyring";
26              
27             =head1 SYNOPSIS
28              
29             Password Safe implementation of L. Passwords are
30             stored in the L
31             encrypted file.
32              
33             use Passwd::Keyring::PWSafe3;
34              
35             my $keyring = Passwd::Keyring::PWSafe3->new(
36             app=>"blahblah scraper",
37             group=>"Johnny web scrapers",
38             file=>"/home/joe/secrets.pwsafe3", # ~/passwd-keyring.pwsafe3 by default
39             master_password=>"very secret password", # Or callback. See below
40             );
41              
42             my $username = "John"; # or get from .ini, or from .argv...
43              
44             my $password = $keyring->get_password($username, "blahblah.com");
45             unless( $password ) {
46             $password = ;
47              
48             # securely save password for future use
49             $keyring->set_password($username, $password, "blahblah.com");
50             }
51              
52             login_somewhere_using($username, $password);
53             if( password_was_wrong ) {
54             $keyring->clear_password($username, "blahblah.com");
55             }
56              
57             =head1 DESCRIPTION
58              
59             This module does not require Password Safe to be installed, and can be
60             used as generic "store many passwords in file encrypted with single
61             master password" storage. Password Safe GUI, if installed, may help
62             the user to review, modify, or delete saved passwords.
63              
64             =over 4
65              
66             Official GUIs can be freely downloaded from L
67             site|http://passwordsafe.sourceforge.net> - both Windows and (beta)
68             Linux versions are available. Apart from them there exist
69             various L,
70             for example or
71             .
72              
73             =back
74              
75             Actual handling of Password Safe format is based on L
76             module. Passwd::Keyring::PWSafe3 just wraps it into the interface
77             compatible with other Passwd::Keyring backends.
78              
79             See L for detailed comments
80             on keyring methods (this document is installed with
81             C package).
82              
83             =head1 CAVEATS
84              
85             Underlying module (L) in fact rewrites the whole file
86             on every save and keeps all passwords cached in memory while active.
87             This means, that any attempts to use the file paralelly from a few
88             programs, or from a few objects within one program, are doomed to
89             cause lost updates. Also, all passwords from the file are kept in
90             (unprotected) memory while keyring object is active. Therefore, it is
91             recommended to use separate .psafe3 file for Passwd::Keyring::PWSafe3,
92             not mixing it with normal Password Safe database, and to keep keyring
93             object for a short time only, especially if modifications happen.
94              
95             There are some limitations in L handling of Password
96             Safe format. Passwords are read and saved properly and it is possible
97             to alternate using them from perl, and via Password Safe GUI, but some
98             less important aspects of the format, like password expiraton policy,
99             may be ignored. Refer to L docs for more details.
100              
101             =head1 DATA MAPPING
102              
103             Group name is mapped to Password Safe folder.
104              
105             Realm is mapped as password title.
106              
107             Username and password are ... well, used as username and password.
108              
109             =head1 SUBROUTINES/METHODS
110              
111             =head2 new(app=>'app name', group=>'passwords folder', file=>'pwsafe3 file', master_password=>'secret or callback', lazy_save=>1)
112              
113             Initializes the processing. Croaks if Crypt::PWSafe3 is not installed or
114             master password is invalid. May create password file if it is missing.
115              
116             Handled parameters:
117              
118             =over 4
119              
120             =item app
121              
122             Symbolic application name (used in password notes)
123              
124             =item group
125              
126             Name for the password group (used as folder name)
127              
128             =item file
129              
130             Location of .pwsafe3 file. If not given, C in
131             user home directory is used. Will be created if does not exist. Note:
132             absolute path is required, relative paths are very error prone.
133              
134             =item master_password
135              
136             Password required to unlock the file. Can be given as string, or
137             as callback returning a string (usually some way of interactively
138             asking user for the password). The callback gets two parameters: app
139             and file.
140              
141             If this param is missing, module will prompt interactively for this
142             password using console prompt.
143              
144             =item lazy_save
145              
146             if given, asks not to save the file after every change (saving is
147             fairly time consuming), but only when $keyring->save is called or when
148             keyring is destroyed.
149              
150             =back
151              
152             Note: it of course does not make much sense to keep app passwords in
153             encrypted storage if master password is saved in plain text. The
154             module most natural usage is to interactively ask for master password
155             (and use it to protect noticeable number of application-specific
156             passwords).
157              
158             Ideas of how to workaround this obstacle are welcome. I loosely
159             consider either caching master password per desktop session
160             (implementing sht. similar to ssh-agent/gpg-agent or using one of
161             those somehow), or integrating the tool with PAM to use actual system
162             password, or both - but while it seems doable on Linux, cross platform
163             solution is not so easy.
164              
165             =cut
166              
167             sub new {
168             my ($cls, %args) = @_;
169              
170             my $self = {};
171             $self->{app} = $args{app} || 'Passwd::Keyring::PWSafe3';
172             $self->{group} = $args{group} || 'Passwd::Keyring';
173             $self->{lazy_save} = $args{lazy_save};
174             my $file = $args{file} || File::Spec->catfile(File::HomeDir->users_data, "passwd-keyring.pwsafe3");
175              
176             unless(File::Spec->file_name_is_absolute($file)) {
177             croak("Absolute path to .pwsafe3 file is required, but relative path '$file' given");
178             }
179             my $parent_dir = dirname($file);
180             unless(-d $parent_dir) {
181             croak("Directory $parent_dir (parent directory of file $file) does not exist");
182             }
183              
184             # TODO: escape group (note that . are used for hierarchy!)
185             # TODO: some locking or maybe detect gui
186              
187             bless $self;
188              
189             my $master = $args{master_password} || \&_prompt_for_password;
190             if(ref($master) eq 'CODE') {
191             $master = $master->($self->{app}, $file);
192             }
193              
194             $self->{vault} = Crypt::PWSafe3->new(file=>$file, password=>$master);
195              
196             return $self;
197             }
198              
199             sub DESTROY {
200             my $self = shift;
201             $self->save();
202             }
203              
204             sub _prompt_for_password {
205             my ($app, $file) = @_;
206             print "* The applicaton $app is requesting to access\n";
207             print "* the Password Safe file $file\n";
208             if (-f $file) {
209             print "* Enter master password necessary to unlock this file.\n";
210             } else {
211             print "* (the file does not exist and will be created on first password save)\n";
212             print "* Enter master password which will protect this file.\n";
213             }
214             while(1) {
215             print " Master password: ";
216             ReadMode 'noecho';
217             my $password = ReadLine 0; chomp($password);
218             ReadMode 'normal';
219             print "\n";
220             return $password if $password;
221             }
222             }
223              
224             # Find data record for given params, or undef if not found
225             sub _find_record {
226             my ($self, $username, $realm) = @_;
227             my $group = $self->{group};
228             foreach my $record($self->{vault}->getrecords()) {
229             if( ($record->group || '') eq $group
230             && ($record->user || '') eq $username
231             && ($record->title || '') eq $realm) {
232             return $record;
233             }
234             }
235             return undef;
236             }
237              
238             =head2 set_password(username, password, realm)
239              
240             Sets (stores) password identified by given realm for given user
241              
242             =cut
243              
244             sub set_password {
245             my ($self, $user_name, $user_password, $realm) = @_;
246              
247             my $rec = $self->_find_record($user_name, $realm);
248             if($rec) {
249             $self->{vault}->modifyrecord(
250             $rec->uuid,
251             passwd => $user_password,
252             notes => "Saved by $self->{app}",
253             );
254             } else {
255             $self->{vault}->newrecord(
256             group => $self->{group},
257             title => $realm,
258             user => $user_name,
259             passwd => $user_password,
260             notes => "Saved by $self->{app}",
261             );
262             }
263             $self->save() unless $self->{lazy_save};
264             }
265              
266             =head2 get_password($user_name, $realm)
267              
268             Reads previously stored password for given user in given app.
269             If such password can not be found, returns undef.
270              
271             =cut
272              
273             sub get_password {
274             my ($self, $user_name, $realm) = @_;
275              
276             my $rec = $self->_find_record($user_name, $realm);
277             if($rec) {
278             return $rec->passwd;
279             }
280             return undef;
281             }
282              
283             =head2 clear_password($user_name, $realm)
284              
285             Removes given password (if present)
286              
287             =cut
288              
289             sub clear_password {
290             my ($self, $user_name, $realm) = @_;
291              
292             my $rec = $self->_find_record($user_name, $realm);
293             if($rec) {
294             $self->{vault}->deleterecord($rec->uuid);
295             $self->save() unless $self->{lazy_save};
296             return 1;
297             } else {
298             return 0;
299             }
300             }
301              
302             =head2 save
303              
304             Saves unsaved changes, if any are present.
305              
306             Important only when lazy_save was given in constructor.
307              
308             =cut
309              
310             sub save {
311             my ($self) = @_;
312             # Crypt::PWSafe3 internally keeps track of changes presence,
313             # and makes this noop if there are no changes. So just call it.
314             $self->{vault}->save();
315             }
316              
317             =head2 is_persistent
318              
319             Returns info, whether this keyring actually saves passwords persistently.
320              
321             (true in this case)
322              
323             =cut
324              
325             sub is_persistent {
326             my ($self) = @_;
327             return 1;
328             }
329              
330             =head1 AUTHOR
331              
332             Marcin Kasperski
333              
334             =head1 BUGS
335              
336             Please report any bugs or feature requests to
337             issue tracker at L.
338              
339             =head1 SUPPORT
340              
341             You can find documentation for this module with the perldoc command.
342              
343             perldoc Passwd::Keyring::PWSafe3
344              
345             You can also look for information at:
346              
347             L
348              
349             Source code is tracked at:
350              
351             L
352              
353             =head1 LICENSE AND COPYRIGHT
354              
355             Copyright 2012 Marcin Kasperski.
356              
357             This program is free software; you can redistribute it and/or modify it
358             under the terms of either: the GNU General Public License as published
359             by the Free Software Foundation; or the Artistic License.
360              
361             See http://dev.perl.org/licenses/ for more information.
362              
363             =cut
364              
365              
366             1; # End of Passwd::Keyring::PWSafe3
367              
368