line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WWW::KeePassHttp;
|
2
|
8
|
|
|
8
|
|
453932
|
use 5.012; # //, strict, s//r
|
|
8
|
|
|
|
|
68
|
|
3
|
8
|
|
|
8
|
|
39
|
use warnings;
|
|
8
|
|
|
|
|
11
|
|
|
8
|
|
|
|
|
211
|
|
4
|
|
|
|
|
|
|
|
5
|
8
|
|
|
8
|
|
832
|
use MIME::Base64;
|
|
8
|
|
|
|
|
1263
|
|
|
8
|
|
|
|
|
357
|
|
6
|
8
|
|
|
8
|
|
2945
|
use Crypt::Mode::CBC;
|
|
8
|
|
|
|
|
72904
|
|
|
8
|
|
|
|
|
211
|
|
7
|
8
|
|
|
8
|
|
3061
|
use HTTP::Tiny;
|
|
8
|
|
|
|
|
211485
|
|
|
8
|
|
|
|
|
229
|
|
8
|
8
|
|
|
8
|
|
51
|
use JSON; # will use JSON::XS when available
|
|
8
|
|
|
|
|
15
|
|
|
8
|
|
|
|
|
49
|
|
9
|
8
|
|
|
8
|
|
5541
|
use Time::HiRes qw/gettimeofday sleep/;
|
|
8
|
|
|
|
|
9398
|
|
|
8
|
|
|
|
|
29
|
|
10
|
8
|
|
|
8
|
|
1140
|
use MIME::Base64;
|
|
8
|
|
|
|
|
18
|
|
|
8
|
|
|
|
|
328
|
|
11
|
8
|
|
|
8
|
|
48
|
use Carp;
|
|
8
|
|
|
|
|
15
|
|
|
8
|
|
|
|
|
303
|
|
12
|
8
|
|
|
8
|
|
3132
|
use WWW::KeePassHttp::Entry;
|
|
8
|
|
|
|
|
17
|
|
|
8
|
|
|
|
|
543
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '0.020'; # rrr.mmmsss : rrr is major revision; mmm is minor revision; sss is sub-revision (new feature path or bugfix); optionally use _sss instead, for alpha sub-releases
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my $dumpfn;
|
17
|
|
|
|
|
|
|
BEGIN {
|
18
|
7
|
|
|
|
|
160
|
$dumpfn = sub { JSON->new->utf8->pretty->encode($_[0]) } # hidden from podcheckers and external namespace
|
19
|
8
|
|
|
8
|
|
9964
|
}
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=pod
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 NAME
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
WWW::KeePassHttp - Interface with KeePass PasswordSafe through the KeePassHttp plugin
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use WWW::KeePassHttp;
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
my $kph = WWW::KeePassHttp->new(Key => $key);
|
32
|
|
|
|
|
|
|
$kph->associate() unless $kph->test_associate();
|
33
|
|
|
|
|
|
|
my @entries = @${ $kph->get_logins($search_string) };
|
34
|
|
|
|
|
|
|
print $entry[0]->url;
|
35
|
|
|
|
|
|
|
print $entry[0]->login;
|
36
|
|
|
|
|
|
|
print $entry[0]->password;
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
Interface with KeePass PasswordSafe through the KeePassHttp plugin. Allows reading entries based on URL or TITLE, and creating a new entry as well.
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head2 REQUIREMENTS
|
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
You need to have KeePass (or compatible) on your system, with the KeePassHttp plugin installed.
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 INTERFACE
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=head2 CONSTRUCTOR AND CONFIGURATION
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=over
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=item new
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
my $kph = WWW::KeePassHttp->new( Key => $key, %options);
|
55
|
|
|
|
|
|
|
my $kph = WWW::KeePassHttp->new( Key => $key, keep_alive => 0, %options);
|
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Creates a new KeePassHttp connection, and sets up the AES encryption.
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
The C $key> is required; pass in a string of 32 octets that
|
60
|
|
|
|
|
|
|
represent a 256-bit key value. If you have your key as 64 hex nibbles,
|
61
|
|
|
|
|
|
|
then use C<$key = pack 'H*', $hexnibbles;> to convert it to the value.
|
62
|
|
|
|
|
|
|
If you have your key as a Base64 string, use
|
63
|
|
|
|
|
|
|
C<$key = decode_base64($base64string);> to convert it to the value.
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
There is also a C option, which will tell the HTTP user
|
66
|
|
|
|
|
|
|
agent to keep the connection alive when the option is set to C<1> (or
|
67
|
|
|
|
|
|
|
when it's not specified); setting the option to a C<0> will disable
|
68
|
|
|
|
|
|
|
that feature of the user agent.
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
The C<%options> share the same name and purposes with the
|
71
|
|
|
|
|
|
|
configuration methods that follow, and can be individually specified in
|
72
|
|
|
|
|
|
|
the constructor as key/value pairs, or passing in an C<%options> hash.
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=cut
|
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub new
|
77
|
|
|
|
|
|
|
{
|
78
|
21
|
|
|
21
|
1
|
11225
|
my ($class, %opts) = @_;
|
79
|
21
|
|
|
|
|
48
|
my $self = bless {}, $class;
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# user agent and URL
|
82
|
21
|
|
100
|
|
|
102
|
$opts{keep_alive} //= 1; # default to keep_alive
|
83
|
21
|
|
|
|
|
81
|
$self->{ua} = HTTP::Tiny->new(keep_alive => $opts{keep_alive} );
|
84
|
|
|
|
|
|
|
|
85
|
21
|
|
100
|
|
|
1320
|
$self->{request_base} = $opts{request_base} // 'http://localhost'; # default to localhost
|
86
|
21
|
|
100
|
|
|
62
|
$self->{request_port} = $opts{request_port} // 19455; # default to 19455
|
87
|
21
|
|
|
|
|
61
|
$self->{request_url} = $self->{request_base} . ':' . $self->{request_port};
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# encryption object
|
90
|
21
|
|
|
|
|
324
|
$self->{cbc} = Crypt::Mode::CBC->new('AES');
|
91
|
21
|
|
|
|
|
49
|
$self->{key} = $opts{Key};
|
92
|
21
|
|
|
|
|
70
|
for($self->{key}) {
|
93
|
21
|
100
|
|
|
|
75
|
croak "256-bit AES key is required" unless defined $_;
|
94
|
19
|
100
|
|
|
|
53
|
last if length($_) == 32; # a 32-octet string is assumed to be a valid key
|
95
|
5
|
|
|
|
|
8
|
chomp;
|
96
|
5
|
100
|
|
|
|
32
|
croak "256-bit AES key must be in octets, not hex nibbles"
|
97
|
|
|
|
|
|
|
if /^(0x)?[[:xdigit:]]{64}$/;
|
98
|
3
|
100
|
|
|
|
23
|
croak "256-bit AES key must be in octets, not in base64"
|
99
|
|
|
|
|
|
|
if length($_) == 44;
|
100
|
1
|
|
|
|
|
7
|
croak "Key not recognized as 256-bit AES";
|
101
|
|
|
|
|
|
|
}
|
102
|
14
|
|
|
|
|
54
|
$self->{key64} = encode_base64($self->{key}, '');
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# appid
|
105
|
14
|
|
100
|
|
|
58
|
$self->{appid} = $opts{appid} // 'WWW::KeePassHttp';
|
106
|
|
|
|
|
|
|
|
107
|
14
|
|
|
|
|
87
|
return $self;
|
108
|
|
|
|
|
|
|
}
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=item appid
|
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
%options = ( ..., appid => 'name of your app', ... );
|
113
|
|
|
|
|
|
|
or
|
114
|
|
|
|
|
|
|
$kph->appid('name of your app');
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Changes the appid, which is the name that is used to map your
|
117
|
|
|
|
|
|
|
application with the stored key in the KeePassHttp settings in
|
118
|
|
|
|
|
|
|
KeePass.
|
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
If not defined in the initial options or via this method,
|
121
|
|
|
|
|
|
|
the module will use a default appid of C.
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=cut
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub appid
|
126
|
|
|
|
|
|
|
{
|
127
|
2
|
|
|
2
|
1
|
9
|
my ($self, $val) = @_;
|
128
|
2
|
100
|
|
|
|
6
|
$self->{appid} = $val if defined $val;
|
129
|
2
|
|
|
|
|
6
|
return $self->{appid};
|
130
|
|
|
|
|
|
|
}
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=item request_base
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
%options = ( ..., request_base => 'localhost', ... );
|
135
|
|
|
|
|
|
|
or
|
136
|
|
|
|
|
|
|
$kph->request_base('127.0.0.1');
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Changes the protocol and host: the KeePassHttp plugin defaults to C, but can be configured differently, so you will need to make your object match your plugin settings.
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=cut
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub request_base
|
143
|
|
|
|
|
|
|
{
|
144
|
2
|
|
|
2
|
1
|
8
|
my ($self, $val) = @_;
|
145
|
2
|
100
|
|
|
|
5
|
$self->{request_base} = $val if defined $val;
|
146
|
2
|
|
|
|
|
4
|
$self->{request_url} = $self->{request_base} . ':' . $self->{request_port};
|
147
|
2
|
|
|
|
|
5
|
return $self->{request_base};
|
148
|
|
|
|
|
|
|
}
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item request_port
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
%options = ( ..., request_port => 19455, ... );
|
153
|
|
|
|
|
|
|
or
|
154
|
|
|
|
|
|
|
$kph->request_port(19455);
|
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Changes the port: the KeePassHttp plugin defaults to port 19455, but can be configured differently, so you will need to make your object match your plugin settings.
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=cut
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub request_port
|
161
|
|
|
|
|
|
|
{
|
162
|
2
|
|
|
2
|
1
|
8
|
my ($self, $val) = @_;
|
163
|
2
|
100
|
|
|
|
6
|
$self->{request_port} = $val if defined $val;
|
164
|
2
|
|
|
|
|
5
|
$self->{request_url} = $self->{request_base} . ':' . $self->{request_port};
|
165
|
2
|
|
|
|
|
5
|
return $self->{request_port};
|
166
|
|
|
|
|
|
|
}
|
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=back
|
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=for comment END OF CONSTRUCTOR AND CONFIGURATION
|
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 USER INTERFACE
|
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
These methods implement the L, with one method for each RequestType.
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=over
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=item test_associate
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
$kph->associate unless $kph->test_associate();
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Sends the C request to the KeePassHttp server,
|
183
|
|
|
|
|
|
|
which is used to see whether or not your application has been
|
184
|
|
|
|
|
|
|
associated with the KeePassHttp plugin or not. Returns a true
|
185
|
|
|
|
|
|
|
value if your application is already associated, or a false
|
186
|
|
|
|
|
|
|
value otherwise.
|
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=cut
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub test_associate
|
191
|
|
|
|
|
|
|
{
|
192
|
2
|
|
|
2
|
1
|
1089
|
my ($self, %args) = @_;
|
193
|
2
|
|
|
|
|
6
|
my $content = $self->request('test-associate', %args);
|
194
|
2
|
|
|
|
|
7
|
return $content->{Success};
|
195
|
|
|
|
|
|
|
}
|
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item associate
|
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
$kph->associate unless $kph->test_associate();
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Sends the C request to the KeePassHttp server,
|
202
|
|
|
|
|
|
|
which is used to give your application's key to the KeePassHttp
|
203
|
|
|
|
|
|
|
plugin.
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
When this request is received, KeePass will pop up a dialog
|
206
|
|
|
|
|
|
|
asking for a name -- this name should match the C value
|
207
|
|
|
|
|
|
|
that you defined for the C<$kph> instance. All requests sent
|
208
|
|
|
|
|
|
|
to the plugin will include this C so that KeePassHttp can
|
209
|
|
|
|
|
|
|
look up your application's key, so it must match exactly.
|
210
|
|
|
|
|
|
|
As per the C,
|
211
|
|
|
|
|
|
|
the server saves your application's key in the C
|
212
|
|
|
|
|
|
|
entry, in the B String Fields> with a name of
|
213
|
|
|
|
|
|
|
C, where C is the name you type in the dialog
|
214
|
|
|
|
|
|
|
box (which needs to match your C).
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
B: this C communication is insecure,
|
217
|
|
|
|
|
|
|
since KeePassHttp plugin is not using HTTPS. Every other
|
218
|
|
|
|
|
|
|
communication between your application and the plugin uses the
|
219
|
|
|
|
|
|
|
key (which both your application and the plugin know) to
|
220
|
|
|
|
|
|
|
encrypt the critical data (usernames, passwords, titles, etc),
|
221
|
|
|
|
|
|
|
and is thus secure;
|
222
|
|
|
|
|
|
|
but the C interaction, because it happens before
|
223
|
|
|
|
|
|
|
the plugin has your key, by its nature cannot be encrypted by
|
224
|
|
|
|
|
|
|
that key, so it sends the encoded key I. If this
|
225
|
|
|
|
|
|
|
worries you, I suggest that you manually insert the key: do an
|
226
|
|
|
|
|
|
|
C once with a dummy key, then manually overwrite the
|
227
|
|
|
|
|
|
|
encoded key that it stores with the encoded version of your real
|
228
|
|
|
|
|
|
|
key. (This limitation is due to the design of the KeePassHttp
|
229
|
|
|
|
|
|
|
plugin and its protocol for the C command, not due
|
230
|
|
|
|
|
|
|
to the wrapper around that protocol that this module implements.)
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub associate
|
235
|
|
|
|
|
|
|
{
|
236
|
3
|
|
|
3
|
1
|
946
|
my ($self, %args) = @_;
|
237
|
3
|
|
|
|
|
12
|
my $content = $self->request('associate', Key64 => $self->{key64}, %args);
|
238
|
3
|
100
|
100
|
|
|
16
|
croak ("Wrong ID: ", $dumpfn->( { wrong_id => $content } )) unless $self->{appid} eq ($content->{Id}//'');
|
239
|
1
|
|
|
|
|
2
|
return $content;
|
240
|
|
|
|
|
|
|
}
|
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=item get_logins
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
my @entries = @${ $kph->get_logins($search_string) };
|
245
|
|
|
|
|
|
|
print $entry[0]->url;
|
246
|
|
|
|
|
|
|
print $entry[0]->login;
|
247
|
|
|
|
|
|
|
print $entry[0]->password;
|
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Sends the C request, which returns the Name,
|
250
|
|
|
|
|
|
|
Login, and Password for each of the matching entries.
|
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
C<$entries> is an array reference containing
|
253
|
|
|
|
|
|
|
L objects, from which you can
|
254
|
|
|
|
|
|
|
extract the url/name, login, and password for each matched
|
255
|
|
|
|
|
|
|
entry.
|
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
The rules for the matching of the search string are defined in the
|
258
|
|
|
|
|
|
|
L.
|
259
|
|
|
|
|
|
|
But, in brief, it will do a fuzzy match on the URL, and an exact match
|
260
|
|
|
|
|
|
|
on the entry title. (The plugin was designed to be used for browser plugins
|
261
|
|
|
|
|
|
|
to request passwords for URLs from KeePass, hence its focus on URLs.)
|
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=cut
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub get_logins
|
266
|
|
|
|
|
|
|
{
|
267
|
3
|
|
|
3
|
1
|
5390
|
my ($self, $search_term, %args) = @_;
|
268
|
3
|
|
|
|
|
6
|
$args{Url} = $search_term;
|
269
|
3
|
100
|
|
|
|
11
|
$args{SubmitUrl} = $self->{appid} unless exists $args{SubmitUrl}; # "SubmitUrl" is actually the name of the requestor; in the browser->keePassHttp interface, the requestor is the website requesting the password; but here, I am using it as the app identifier
|
270
|
3
|
|
|
|
|
13
|
my $content = $self->request('get-logins', Url => $search_term, %args);
|
271
|
3
|
100
|
|
|
|
12
|
return [] unless $content->{Count};
|
272
|
2
|
|
|
|
|
3
|
my $entries = $content->{Entries};
|
273
|
2
|
|
|
|
|
7
|
for my $entry ( @$entries ) {
|
274
|
2
|
|
|
|
|
10
|
for my $k ( sort keys %$entry ) {
|
275
|
8
|
|
|
|
|
82
|
$entry->{$k} = $self->{cbc}->decrypt( decode_base64($entry->{$k}), $self->{key}, decode_base64($content->{Nonce}));
|
276
|
|
|
|
|
|
|
}
|
277
|
2
|
|
|
|
|
23
|
$entry->{Url} = $entry->{Name};
|
278
|
2
|
|
|
|
|
14
|
$entry = WWW::KeePassHttp::Entry->new( %$entry );
|
279
|
|
|
|
|
|
|
}
|
280
|
|
|
|
|
|
|
#$dumpfn->( { Entries => $entries } );
|
281
|
2
|
|
|
|
|
11
|
return $entries;
|
282
|
|
|
|
|
|
|
}
|
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=item get_logins_count
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
my $count = $kph->get_logins_count($search_string);
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
Sends the C request, which returns a count of
|
289
|
|
|
|
|
|
|
the number of matches for the search string.
|
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
The rules for the matching of the search string are defined in the
|
292
|
|
|
|
|
|
|
L.
|
293
|
|
|
|
|
|
|
But, in brief, it will do a fuzzy match on the URL, and an exact match
|
294
|
|
|
|
|
|
|
on the entry title. (The plugin was designed to be used for browser plugins
|
295
|
|
|
|
|
|
|
to request passwords for URLs from KeePass, hence its focus on URLs.)
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
This method is useful when the fuzzy-URL-match might match a large
|
298
|
|
|
|
|
|
|
number of entries in the database; if after seeing this count, you
|
299
|
|
|
|
|
|
|
would rather refine your search instead of requesting that many entries,
|
300
|
|
|
|
|
|
|
this method enables knowing that right away, rather than after you
|
301
|
|
|
|
|
|
|
accidentally matched virtually every entry in your database by searching
|
302
|
|
|
|
|
|
|
for C.
|
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=cut
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub get_logins_count
|
307
|
|
|
|
|
|
|
{
|
308
|
2
|
|
|
2
|
1
|
3550
|
my ($self, $search_term, %args) = @_;
|
309
|
2
|
|
|
|
|
5
|
$args{Url} = $search_term;
|
310
|
2
|
100
|
|
|
|
8
|
$args{SubmitUrl} = $self->{appid} unless exists $args{SubmitUrl}; # "SubmitUrl" is actually the name of the requestor; in the browser->keePassHttp interface, the requestor is the website requesting the password; but here, I am using it as the app identifier
|
311
|
2
|
|
|
|
|
8
|
my $content = $self->request('get-logins-count', Url => $search_term, %args);
|
312
|
2
|
|
|
|
|
8
|
return $content->{Count};
|
313
|
|
|
|
|
|
|
}
|
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=item set_login
|
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
$kph->set_login( Login => $username, Url => $url_and_title, Password => $password );
|
319
|
|
|
|
|
|
|
# or
|
320
|
|
|
|
|
|
|
$kph->set_login( $entry );
|
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
Sends the C request, which adds a new entry to your
|
323
|
|
|
|
|
|
|
KeePass database, in the "KeePassHttp Passwords" group (folder).
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
As far as I know, the plugin doesn't allow choosing a different group
|
326
|
|
|
|
|
|
|
for your entry. The plugin uses the URL that you supply as both the
|
327
|
|
|
|
|
|
|
entry title and the URL field in that entry. (Once again, the plugin
|
328
|
|
|
|
|
|
|
was designed around browser password needs, and thus is URL-focused).
|
329
|
|
|
|
|
|
|
I don't know if that's a deficiency in the plugin's implementation,
|
330
|
|
|
|
|
|
|
or just its documentation, or my interpretation of that documentation.
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
The arguments to the method define the C (username), C (for
|
333
|
|
|
|
|
|
|
entry title and URL field), and C (secret value) for the new
|
334
|
|
|
|
|
|
|
entry. All three of those parameters are required by the protocol, and
|
335
|
|
|
|
|
|
|
thus by this method. Alernately, you can just pass it a L
|
336
|
|
|
|
|
|
|
object as the single argument.
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
If you would prefer not to give one or more of those parameters a value,
|
339
|
|
|
|
|
|
|
just pass an empty string. You could afterword then manually access
|
340
|
|
|
|
|
|
|
your KeePass database and edit the entry yourself.
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=cut
|
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub set_login
|
345
|
|
|
|
|
|
|
{
|
346
|
5
|
|
|
5
|
1
|
4509
|
my ($self, @rest) = @_;
|
347
|
5
|
100
|
|
|
|
33
|
my %args = UNIVERSAL::isa($rest[0], 'WWW::KeePassHttp::Entry') ? (%{$rest[0]}) : (@rest);
|
|
1
|
|
|
|
|
5
|
|
348
|
5
|
100
|
|
|
|
25
|
croak "set_login(): missing Login parameter" unless defined $args{Login};
|
349
|
4
|
100
|
|
|
|
15
|
croak "set_login(): missing Url parameter" unless defined $args{Url};
|
350
|
3
|
100
|
|
|
|
16
|
croak "set_login(): missing Password parameter" unless defined $args{Password};
|
351
|
2
|
|
|
|
|
7
|
my $content = $self->request('set-login', %args);
|
352
|
2
|
|
|
|
|
16
|
return $content->{Success};
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=item request
|
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
my $results = $kph->request( $type, %options );
|
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
This is the generic method for making a request of the
|
361
|
|
|
|
|
|
|
KeePassHttp plugin. In general, other methods should handle
|
362
|
|
|
|
|
|
|
most requests. However, maybe a new method has been exposed
|
363
|
|
|
|
|
|
|
in the plugin but not yet implemented here, so you can use
|
364
|
|
|
|
|
|
|
this method for handling that.
|
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
The C<$type> indicates the RequestType, which include
|
367
|
|
|
|
|
|
|
C, C, C,
|
368
|
|
|
|
|
|
|
C, and C.
|
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
This method automatically fills out the RequestType, TriggerUnlock, Id, Nonce, and Verifier parameters. If your RequestType requires
|
371
|
|
|
|
|
|
|
any other parameters, add them to the C<%options>.
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
It then encodes the request into the JSON payload, and
|
374
|
|
|
|
|
|
|
sends that request to the KeePassHttp plugin, and gets the response,
|
375
|
|
|
|
|
|
|
decoding the JSON content back into a Perl hashref. It verifies that
|
376
|
|
|
|
|
|
|
the response's Nonce and Verifier parameters are appropriate for the
|
377
|
|
|
|
|
|
|
communication channel, to make sure communications from the plugin
|
378
|
|
|
|
|
|
|
are properly encrypted.
|
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
Returns the hashref decoded from the JSON
|
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=cut
|
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub request {
|
385
|
19
|
|
|
19
|
1
|
3411
|
my ($self, $type, %params) = @_;
|
386
|
19
|
|
|
|
|
44
|
my ($iv, $nonce) = generate_nonce();
|
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
#print STDERR "request($type):\n";
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# these are required in every request
|
391
|
|
|
|
|
|
|
my %request = (
|
392
|
|
|
|
|
|
|
RequestType => $type,
|
393
|
|
|
|
|
|
|
TriggerUnlock => JSON::true, # was intended for TRUE to request that KeePass unlock, but that doesn't actually happen
|
394
|
|
|
|
|
|
|
Id => $self->{appid},
|
395
|
|
|
|
|
|
|
Nonce => $nonce,
|
396
|
19
|
|
|
|
|
53
|
Verifier => encode_base64($self->{cbc}->encrypt($nonce, $self->{key}, $iv), ''),
|
397
|
|
|
|
|
|
|
);
|
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# don't want to encrypt the key during an association request
|
400
|
19
|
|
|
|
|
525
|
delete $params{Key}; # only allow Key64
|
401
|
19
|
100
|
|
|
|
48
|
$request{Key} = delete $params{Key64} if( exists $params{Key64} );
|
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# encrypt all remaining parameter values
|
404
|
19
|
|
|
|
|
58
|
while(my ($k,$v) = each %params) {
|
405
|
17
|
|
|
|
|
182
|
$request{$k} = encode_base64($self->{cbc}->encrypt($v, $self->{key}, $iv), '');
|
406
|
|
|
|
|
|
|
}
|
407
|
|
|
|
|
|
|
#$dumpfn->({final_request => \%request});
|
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# send the request
|
410
|
19
|
|
|
|
|
275
|
my $response = $self->{ua}->get($self->{request_url}, {content=> encode_json \%request});
|
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# error checking
|
413
|
19
|
100
|
|
|
|
1221
|
croak $dumpfn->( { request_error => $response } ) unless $response->{success};
|
414
|
18
|
100
|
|
|
|
39
|
croak $dumpfn->( { no_json => $response } ) unless exists $response->{content};
|
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# get the JSON
|
417
|
17
|
|
|
|
|
144
|
my $content = decode_json $response->{content};
|
418
|
|
|
|
|
|
|
#$dumpfn->( { their_response => $response, their_content => $content } );
|
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# verification before returning the content -- if their verifier doesn't match their nonce,
|
421
|
|
|
|
|
|
|
# then we don't have secure communication
|
422
|
|
|
|
|
|
|
# Don't need to check on test-associate/associate if verifier is missing, because there can
|
423
|
|
|
|
|
|
|
# reasonably be no verifier on those (ie, when test-associate returns false, or when the associate fails)
|
424
|
16
|
100
|
100
|
|
|
62
|
if(exists $content->{Verifier} or ($type ne 'test-associate' and $type ne 'associate')) {
|
|
|
|
100
|
|
|
|
|
425
|
14
|
100
|
100
|
|
|
55
|
croak $dumpfn->( { missing_verifier => $content } ) unless exists $content->{Nonce} and exists $content->{Verifier};
|
426
|
12
|
|
|
|
|
44
|
my $their_iv = decode_base64($content->{Nonce});
|
427
|
12
|
|
|
|
|
48
|
my $decode_their_verifier = $self->{cbc}->decrypt( decode_base64($content->{Verifier}), $self->{key}, $their_iv );
|
428
|
12
|
100
|
|
|
|
200
|
if( $decode_their_verifier ne $content->{Nonce} ) {
|
429
|
1
|
|
|
|
|
4
|
croak $dumpfn->( { "Decoded Verifier $decode_their_verifier" => $content } );
|
430
|
|
|
|
|
|
|
}
|
431
|
|
|
|
|
|
|
}
|
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# If it made it to here, it's safe to return the content
|
434
|
13
|
|
|
|
|
45
|
return $content;
|
435
|
|
|
|
|
|
|
}
|
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=back
|
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
=for comment END OF USER INTERFACE
|
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=head2 HELPER METHODS
|
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
In general, most users won't need these. But maybe I will.
|
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=over
|
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=item generate_nonce
|
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
my ($iv, $base64) = $kph->generate_nonce();
|
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
This is used by the L method to generate the IV nonce
|
452
|
|
|
|
|
|
|
for communication. I don't think you need to use it yourself, but
|
453
|
|
|
|
|
|
|
it's available to you, if you find a need for it.
|
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
The C<$iv> is the string of octets (the actual 128 IV nonce value).
|
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
The C<$base64> is the base64 representation of the C<$iv>.
|
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=cut
|
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
sub generate_nonce
|
462
|
|
|
|
|
|
|
{
|
463
|
|
|
|
|
|
|
# generate 10 bytes of random numbers, 2 bytes of microsecond time, and 4 bytes of seconds
|
464
|
|
|
|
|
|
|
# this gives randomness from two sources (rand and usecond),
|
465
|
|
|
|
|
|
|
# plus a deterministic counter that won't repeat for 2^31 seconds (almost 70 years)
|
466
|
|
|
|
|
|
|
# so as long as you aren't using the same key for 70 years, the nonce should be unique
|
467
|
22
|
|
|
22
|
1
|
2003240
|
my $hex = '';
|
468
|
22
|
|
|
|
|
397
|
$hex .= sprintf '%02X', rand(256) for 1..10;
|
469
|
22
|
|
|
|
|
90
|
my ($s,$us) = gettimeofday();
|
470
|
22
|
|
|
|
|
72
|
$hex .= sprintf '%04X%08X', $us&0xFFFF, $s&0xFFFFFFFF;
|
471
|
22
|
|
|
|
|
104
|
my $iv = pack 'H*', $hex;
|
472
|
22
|
|
|
|
|
103
|
my $nonce = encode_base64($iv, '');
|
473
|
22
|
100
|
|
|
|
113
|
return wantarray ? ($iv, $nonce) : $iv;
|
474
|
|
|
|
|
|
|
}
|
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=back
|
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=for comment END OF HELPER METHODS
|
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=head1 SEE ALSO
|
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=over
|
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=item * L
|
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=item * L
|
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=item * L = A similar interface which uses the KeePassRest plugin to interface with KeePass
|
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=back
|
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS
|
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Thank you to L for providing a free
|
496
|
|
|
|
|
|
|
password manager with plugin capability.
|
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
Thank you to the L
|
499
|
|
|
|
|
|
|
for providing a free and open source plugin which allows for easy
|
500
|
|
|
|
|
|
|
communication between an external application and the KeePass application,
|
501
|
|
|
|
|
|
|
enabling the existence of this module (and the ability for it to give
|
502
|
|
|
|
|
|
|
applications access to the passwords stored in KeePass).
|
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
This module and author are not affiliated with either KeePass or KeePassHttp
|
505
|
|
|
|
|
|
|
except as a user of those fine products.
|
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head1 TODO
|
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
The entries should be full-fledged objects, with method-based access to
|
510
|
|
|
|
|
|
|
the underlying Login, Url, and Password values.
|
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=head1 AUTHOR
|
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Peter C. Jones Cpetercj AT cpan DOT orgE>
|
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
Please report any bugs or feature requests
|
517
|
|
|
|
|
|
|
thru the repository's interface at L.
|
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=begin html
|
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=end html
|
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=head1 COPYRIGHT
|
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
Copyright (C) 2021 Peter C. Jones
|
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=head1 LICENSE
|
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
537
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published
|
538
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License.
|
539
|
|
|
|
|
|
|
See L for more information.
|
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
=cut
|
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
1;
|