line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Dancer2::Plugin::Passphrase::Core; |
2
|
11
|
|
|
11
|
|
68
|
use strict; |
|
11
|
|
|
|
|
30
|
|
|
11
|
|
|
|
|
289
|
|
3
|
11
|
|
|
11
|
|
54
|
use warnings; |
|
11
|
|
|
|
|
20
|
|
|
11
|
|
|
|
|
291
|
|
4
|
11
|
|
|
11
|
|
58
|
use Carp qw(croak); |
|
11
|
|
|
|
|
21
|
|
|
11
|
|
|
|
|
581
|
|
5
|
11
|
|
|
11
|
|
5231
|
use Digest; |
|
11
|
|
|
|
|
5835
|
|
|
11
|
|
|
|
|
351
|
|
6
|
11
|
|
|
11
|
|
4702
|
use MIME::Base64 qw(decode_base64 encode_base64); |
|
11
|
|
|
|
|
6752
|
|
|
11
|
|
|
|
|
725
|
|
7
|
11
|
|
|
11
|
|
5072
|
use Data::Entropy::Algorithms qw(rand_bits rand_int); |
|
11
|
|
|
|
|
145868
|
|
|
11
|
|
|
|
|
14209
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# ABSTRACT: Passphrases and Passwords as objects for Dancer2 |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Dancer2::Plugin::Passphrase::Core - Core package for Dancer2::Plugin::Passphrase. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 DESCRIPTION |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
B |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 AUTHOR |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Maintainer: Henk van Oers |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
This software is copyright (c) 2012 by James Aitken. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
28
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=cut |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub new { |
33
|
98
|
|
|
98
|
0
|
2481
|
my $class = shift; |
34
|
98
|
|
|
|
|
242
|
my @args = @_; |
35
|
98
|
50
|
|
|
|
657
|
return bless { @args == 1 ? %{$args[0]} : @args }, $class; |
|
0
|
|
|
|
|
0
|
|
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# { algorithm => '...', this => '...' } |
39
|
|
|
|
|
|
|
sub _merge_options { |
40
|
26
|
|
|
26
|
|
46
|
my $self = shift; |
41
|
26
|
|
|
|
|
41
|
my $options = shift; |
42
|
26
|
|
|
|
|
64
|
my $algorithm = $self->{'algorithm'}; |
43
|
26
|
|
|
|
|
52
|
my $settings = {}; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# if we got options |
46
|
26
|
100
|
|
|
|
74
|
if ($options) { |
47
|
24
|
|
|
|
|
55
|
$algorithm = delete $options->{'algorithm'}; |
48
|
|
|
|
|
|
|
$settings = |
49
|
|
|
|
|
|
|
defined $options->{$algorithm} |
50
|
|
|
|
|
|
|
? $options->{$algorithm} |
51
|
24
|
50
|
|
|
|
75
|
: $self->{$algorithm}; |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Specify empty string to get an unsalted hash |
55
|
|
|
|
|
|
|
# Leaving it undefs results in 128 random bits being used as salt |
56
|
|
|
|
|
|
|
# bcrypt requires this amount, and is reasonable for other algorithms |
57
|
|
|
|
|
|
|
$settings->{'salt'} = rand_bits(128) |
58
|
26
|
50
|
|
|
|
130
|
unless defined $settings->{'salt'}; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# RFC 2307 scheme is based on the algorithm, with a prefixed 'S' for salted |
61
|
26
|
|
|
|
|
38900
|
$settings->{'scheme'} = join '', $algorithm =~ /[\w]+/g; |
62
|
|
|
|
|
|
|
$settings->{'scheme'} = 'S'. $settings->{'scheme'} |
63
|
26
|
50
|
|
|
|
115
|
if $settings->{'salt'}; |
64
|
|
|
|
|
|
|
|
65
|
26
|
50
|
|
|
|
121
|
if ( $settings->{'scheme'} eq 'SHA1' ) { |
|
|
100
|
|
|
|
|
|
66
|
0
|
|
|
|
|
0
|
$settings->{'scheme'} = 'SHA'; |
67
|
|
|
|
|
|
|
} elsif ( $settings->{'scheme'} eq 'SSHA1' ) { |
68
|
6
|
|
|
|
|
18
|
$settings->{'scheme'} = 'SSHA'; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# Bcrypt requires a cost parameter |
72
|
26
|
100
|
|
|
|
77
|
if ( $algorithm eq 'Bcrypt' ) { |
73
|
5
|
|
|
|
|
15
|
$settings->{'scheme'} = 'CRYPT'; |
74
|
5
|
|
|
|
|
12
|
$settings->{'type'} = '2a'; |
75
|
|
|
|
|
|
|
$settings->{'cost'} = |
76
|
5
|
50
|
|
|
|
22
|
defined $settings->{'cost'} ? $settings->{'cost'} : 4; |
77
|
5
|
50
|
|
|
|
19
|
$settings->{'cost'} = 31 if $settings->{'cost'} > 31; |
78
|
5
|
|
|
|
|
29
|
$settings->{'cost'} = sprintf '%02d', $settings->{'cost'}; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
26
|
|
|
|
|
55
|
$settings->{'algorithm'} = $algorithm; |
82
|
26
|
|
|
|
|
53
|
$settings->{'plaintext'} = $self->{'plaintext'}; |
83
|
|
|
|
|
|
|
|
84
|
26
|
|
|
|
|
57
|
return $settings; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# From Crypt::Eksblowfish::Bcrypt. |
88
|
|
|
|
|
|
|
# Bcrypt uses it's own variation on base64 |
89
|
|
|
|
|
|
|
sub _en_bcrypt_base64 { |
90
|
84
|
|
|
84
|
|
157
|
my ($octets) = @_; |
91
|
84
|
|
|
|
|
214
|
my $text = encode_base64($octets, ''); |
92
|
84
|
|
|
|
|
152
|
$text =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d; |
93
|
84
|
|
|
|
|
175
|
return $text; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# And the decoder of bcrypt's custom base64 |
98
|
|
|
|
|
|
|
sub _de_bcrypt_base64 { |
99
|
38
|
|
|
38
|
|
93
|
my ($text) = @_; |
100
|
38
|
|
|
|
|
76
|
$text =~ tr{./A-Za-z0-9}{A-Za-z0-9+/}; |
101
|
38
|
|
|
|
|
123
|
$text .= "=" x (3 - (length($text) + 3) % 4); |
102
|
38
|
|
|
|
|
130
|
return decode_base64($text); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Extracts the settings from an RFC 2307 string |
106
|
|
|
|
|
|
|
sub _extract_settings { |
107
|
69
|
|
|
69
|
|
118
|
my ($self, $rfc2307_string) = @_; |
108
|
69
|
|
|
|
|
112
|
my $settings = {}; |
109
|
|
|
|
|
|
|
|
110
|
69
|
|
|
|
|
367
|
my ($scheme, $rfc_settings) = ($rfc2307_string =~ m/^{(\w+)}(.*)/s); |
111
|
|
|
|
|
|
|
|
112
|
69
|
100
|
66
|
|
|
327
|
unless ($scheme && $rfc_settings) { |
113
|
1
|
|
|
|
|
119
|
croak "An RFC 2307 compliant string must be passed to matches()"; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
68
|
100
|
|
|
|
171
|
if ($scheme eq 'CRYPT') { |
117
|
39
|
100
|
|
|
|
174
|
if ($rfc_settings =~ m/^\$2(?:a|x|y)\$/) { |
118
|
38
|
|
|
|
|
66
|
$scheme = 'Bcrypt'; |
119
|
38
|
|
|
|
|
113
|
$rfc_settings =~ m{\A\$(2a|2x|2y)\$([0-9]{2})\$([./A-Za-z0-9]{22})}x; |
120
|
|
|
|
|
|
|
|
121
|
38
|
|
|
|
|
91
|
@{$settings}{qw} = ( $1, $2, _de_bcrypt_base64($3) ); |
|
38
|
|
|
|
|
152
|
|
122
|
|
|
|
|
|
|
} else { |
123
|
1
|
|
|
|
|
68
|
croak "Unknown CRYPT format"; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
67
|
|
|
|
|
1026
|
my $scheme_meta = { |
128
|
|
|
|
|
|
|
'MD5' => { algorithm => 'MD5', octets => 128 / 8 }, |
129
|
|
|
|
|
|
|
'SMD5' => { algorithm => 'MD5', octets => 128 / 8 }, |
130
|
|
|
|
|
|
|
'SHA' => { algorithm => 'SHA-1', octets => 160 / 8 }, |
131
|
|
|
|
|
|
|
'SSHA' => { algorithm => 'SHA-1', octets => 160 / 8 }, |
132
|
|
|
|
|
|
|
'SHA224' => { algorithm => 'SHA-224', octets => 224 / 8 }, |
133
|
|
|
|
|
|
|
'SSHA224' => { algorithm => 'SHA-224', octets => 224 / 8 }, |
134
|
|
|
|
|
|
|
'SHA256' => { algorithm => 'SHA-256', octets => 256 / 8 }, |
135
|
|
|
|
|
|
|
'SSHA256' => { algorithm => 'SHA-256', octets => 256 / 8 }, |
136
|
|
|
|
|
|
|
'SHA384' => { algorithm => 'SHA-384', octets => 384 / 8 }, |
137
|
|
|
|
|
|
|
'SSHA384' => { algorithm => 'SHA-384', octets => 384 / 8 }, |
138
|
|
|
|
|
|
|
'SHA512' => { algorithm => 'SHA-512', octets => 512 / 8 }, |
139
|
|
|
|
|
|
|
'SSHA512' => { algorithm => 'SHA-512', octets => 512 / 8 }, |
140
|
|
|
|
|
|
|
'Bcrypt' => { algorithm => 'Bcrypt', octets => 128 / 8 }, |
141
|
|
|
|
|
|
|
}; |
142
|
|
|
|
|
|
|
|
143
|
67
|
|
|
|
|
178
|
$settings->{'scheme'} = $scheme; |
144
|
67
|
|
|
|
|
129
|
$settings->{'algorithm'} = $scheme_meta->{$scheme}{algorithm}; |
145
|
67
|
|
|
|
|
121
|
$settings->{'plaintext'} = $self->{'plaintext'};; |
146
|
|
|
|
|
|
|
|
147
|
67
|
100
|
|
|
|
161
|
if ( !defined $settings->{'salt'} ) { |
148
|
|
|
|
|
|
|
$settings->{'salt'} = substr( |
149
|
|
|
|
|
|
|
decode_base64($rfc_settings), |
150
|
|
|
|
|
|
|
$scheme_meta->{$scheme}{octets}, |
151
|
29
|
|
|
|
|
143
|
); |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
67
|
|
|
|
|
360
|
return $settings; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub _calculate_hash { |
158
|
93
|
|
|
93
|
|
209
|
my ( $self, $settings ) = @_; |
159
|
93
|
|
|
|
|
329
|
my $hasher = Digest->new( $settings->{'algorithm'} ); |
160
|
93
|
|
|
|
|
41593
|
my ( $hash, $rfc2307 ); |
161
|
|
|
|
|
|
|
|
162
|
93
|
100
|
|
|
|
226
|
if ( $settings->{'algorithm'} eq 'Bcrypt' ) { |
163
|
43
|
|
|
|
|
134
|
$hasher->add( $settings->{'plaintext'} ); |
164
|
43
|
|
|
|
|
352
|
$hasher->salt( $settings->{'salt'} ); |
165
|
43
|
|
|
|
|
5008
|
$hasher->cost( $settings->{'cost'} ); |
166
|
|
|
|
|
|
|
|
167
|
43
|
|
|
|
|
873
|
$hash = $hasher->digest; |
168
|
|
|
|
|
|
|
$rfc2307 = '{CRYPT}$' |
169
|
|
|
|
|
|
|
. $settings->{'type'} . '$' |
170
|
|
|
|
|
|
|
. $settings->{'cost'} . '$' |
171
|
42
|
|
|
|
|
80419
|
. _en_bcrypt_base64( $settings->{'salt'} ) |
172
|
|
|
|
|
|
|
. _en_bcrypt_base64($hash); |
173
|
|
|
|
|
|
|
} else { |
174
|
50
|
|
|
|
|
211
|
$hasher->add( $settings->{'plaintext'} ); |
175
|
47
|
|
|
|
|
128
|
$hasher->add( $settings->{'salt'} ); |
176
|
|
|
|
|
|
|
|
177
|
47
|
|
|
|
|
246
|
$hash = $hasher->digest; |
178
|
|
|
|
|
|
|
$rfc2307 = '{' . $settings->{'scheme'} . '}' |
179
|
|
|
|
|
|
|
. encode_base64( |
180
|
47
|
|
|
|
|
244
|
$hash . $settings->{'salt'}, |
181
|
|
|
|
|
|
|
'' |
182
|
|
|
|
|
|
|
); |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
return Dancer2::Plugin::Passphrase::Hashed->new( |
186
|
|
|
|
|
|
|
hash => $hash, |
187
|
|
|
|
|
|
|
rfc2307 => $rfc2307, |
188
|
89
|
|
|
|
|
178
|
%{$settings}, |
|
89
|
|
|
|
|
469
|
|
189
|
|
|
|
|
|
|
); |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub generate { |
193
|
26
|
|
|
26
|
0
|
51
|
my $self = shift; |
194
|
26
|
|
|
|
|
41
|
my $options = shift; |
195
|
26
|
|
|
|
|
67
|
my $settings = $self->_merge_options($options); |
196
|
|
|
|
|
|
|
|
197
|
26
|
|
|
|
|
80
|
return $self->_calculate_hash($settings); |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub generate_random { |
201
|
3
|
|
|
3
|
0
|
8
|
my ($self, $options) = @_; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Default is 16 URL-safe base64 chars. Supported everywhere and a reasonable length |
204
|
3
|
|
100
|
|
|
11
|
my $length = $options->{length} || 16; |
205
|
3
|
|
100
|
|
|
26
|
my $charset = $options->{charset} || ['a'..'z', 'A'..'Z', '0'..'9', '-', '_']; |
206
|
|
|
|
|
|
|
|
207
|
3
|
|
|
|
|
9
|
return join '', map { @$charset[rand_int scalar @$charset] } 1..$length; |
|
51
|
|
|
|
|
7668
|
|
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub matches { |
211
|
69
|
|
|
69
|
0
|
148
|
my ($self, $stored_hash) = @_; |
212
|
|
|
|
|
|
|
|
213
|
69
|
|
|
|
|
176
|
my $settings = $self->_extract_settings($stored_hash); |
214
|
67
|
|
|
|
|
158
|
my $new_hash = $self->_calculate_hash($settings)->rfc2307; |
215
|
|
|
|
|
|
|
|
216
|
67
|
100
|
|
|
|
484
|
return ($new_hash eq $stored_hash) ? 1 : undef; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
1; |