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