line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Copyright (c) 2003-2009 Graham Barr, Djamel Boudjerda, Paul Connolly, Julian |
2
|
|
|
|
|
|
|
# Onions, Nexor and Yann Kerherve. |
3
|
|
|
|
|
|
|
# All rights reserved. This program is free software; you can redistribute |
4
|
|
|
|
|
|
|
# it and/or modify it under the same terms as Perl itself. |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
# See http://www.ietf.org/rfc/rfc2831.txt for details |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
package Authen::SASL::Perl::DIGEST_MD5; |
9
|
|
|
|
|
|
|
$Authen::SASL::Perl::DIGEST_MD5::VERSION = '2.1700'; # TRIAL |
10
|
5
|
|
|
5
|
|
954
|
use strict; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
164
|
|
11
|
5
|
|
|
5
|
|
27
|
use warnings; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
150
|
|
12
|
5
|
|
|
5
|
|
26
|
use vars qw($VERSION @ISA $CNONCE $NONCE); |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
356
|
|
13
|
5
|
|
|
5
|
|
31
|
use Digest::MD5 qw(md5_hex md5); |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
264
|
|
14
|
5
|
|
|
5
|
|
474
|
use Digest::HMAC_MD5 qw(hmac_md5); |
|
5
|
|
|
|
|
1411
|
|
|
5
|
|
|
|
|
21624
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# TODO: complete qop support in server, should be configurable |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$VERSION = "2.14"; |
19
|
|
|
|
|
|
|
@ISA = qw(Authen::SASL::Perl); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
my %secflags = ( |
22
|
|
|
|
|
|
|
noplaintext => 1, |
23
|
|
|
|
|
|
|
noanonymous => 1, |
24
|
|
|
|
|
|
|
); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# some have to be quoted - some don't - sigh! |
27
|
|
|
|
|
|
|
my (%cqdval, %sqdval); |
28
|
|
|
|
|
|
|
@cqdval{qw( |
29
|
|
|
|
|
|
|
username authzid realm nonce cnonce digest-uri |
30
|
|
|
|
|
|
|
)} = (); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
## ...and server behaves different than client - double sigh! |
33
|
|
|
|
|
|
|
@sqdval{keys %cqdval, qw(qop cipher)} = (); |
34
|
|
|
|
|
|
|
# username authzid realm nonce cnonce digest-uri qop cipher |
35
|
|
|
|
|
|
|
#)} = (); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my %multi; |
38
|
|
|
|
|
|
|
@{$multi{server}}{qw(realm auth-param)} = (); |
39
|
|
|
|
|
|
|
@{$multi{client}}{qw()} = (); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my @server_required = qw(algorithm nonce); |
42
|
|
|
|
|
|
|
my @client_required = qw(username nonce cnonce nc qop response); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# available ciphers |
45
|
|
|
|
|
|
|
my @ourciphers = ( |
46
|
|
|
|
|
|
|
{ |
47
|
|
|
|
|
|
|
name => 'rc4', |
48
|
|
|
|
|
|
|
ssf => 128, |
49
|
|
|
|
|
|
|
bs => 1, |
50
|
|
|
|
|
|
|
ks => 16, |
51
|
|
|
|
|
|
|
pkg => 'Crypt::RC4', |
52
|
|
|
|
|
|
|
key => sub { $_[0] }, |
53
|
|
|
|
|
|
|
iv => sub {}, |
54
|
|
|
|
|
|
|
fixup => sub { |
55
|
|
|
|
|
|
|
# retrofit the Crypt::RC4 module with standard subs |
56
|
|
|
|
|
|
|
*Crypt::RC4::encrypt = *Crypt::RC4::decrypt = |
57
|
|
|
|
|
|
|
sub { goto &Crypt::RC4::RC4; }; |
58
|
|
|
|
|
|
|
*Crypt::RC4::keysize = sub {128}; |
59
|
|
|
|
|
|
|
*Crypt::RC4::blocksize = sub {1}; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
}, |
62
|
|
|
|
|
|
|
{ |
63
|
|
|
|
|
|
|
name => '3des', |
64
|
|
|
|
|
|
|
ssf => 112, |
65
|
|
|
|
|
|
|
bs => 8, |
66
|
|
|
|
|
|
|
ks => 16, |
67
|
|
|
|
|
|
|
pkg => 'Crypt::DES3', |
68
|
|
|
|
|
|
|
key => sub { |
69
|
|
|
|
|
|
|
pack('B8' x 16, |
70
|
|
|
|
|
|
|
map { $_ . '0' } |
71
|
|
|
|
|
|
|
map { unpack('a7' x 16, $_); } |
72
|
|
|
|
|
|
|
unpack('B*', substr($_[0], 0, 14)) ); |
73
|
|
|
|
|
|
|
}, |
74
|
|
|
|
|
|
|
iv => sub { substr($_[0], -8, 8) }, |
75
|
|
|
|
|
|
|
}, |
76
|
|
|
|
|
|
|
{ |
77
|
|
|
|
|
|
|
name => 'des', |
78
|
|
|
|
|
|
|
ssf => 56, |
79
|
|
|
|
|
|
|
bs => 8, |
80
|
|
|
|
|
|
|
ks => 16, |
81
|
|
|
|
|
|
|
pkg => 'Crypt::DES', |
82
|
|
|
|
|
|
|
key => sub { |
83
|
|
|
|
|
|
|
pack('B8' x 8, |
84
|
|
|
|
|
|
|
map { $_ . '0' } |
85
|
|
|
|
|
|
|
map { unpack('a7' x 8, $_); } |
86
|
|
|
|
|
|
|
unpack('B*',substr($_[0], 0, 7)) ); |
87
|
|
|
|
|
|
|
}, |
88
|
|
|
|
|
|
|
iv => sub { substr($_[0], -8, 8) }, |
89
|
|
|
|
|
|
|
}, |
90
|
|
|
|
|
|
|
{ |
91
|
|
|
|
|
|
|
name => 'rc4-56', |
92
|
|
|
|
|
|
|
ssf => 56, |
93
|
|
|
|
|
|
|
bs => 1, |
94
|
|
|
|
|
|
|
ks => 7, |
95
|
|
|
|
|
|
|
pkg => 'Crypt::RC4', |
96
|
|
|
|
|
|
|
key => sub { $_[0] }, |
97
|
|
|
|
|
|
|
iv => sub {}, |
98
|
|
|
|
|
|
|
fixup => sub { |
99
|
|
|
|
|
|
|
*Crypt::RC4::encrypt = *Crypt::RC4::decrypt = |
100
|
|
|
|
|
|
|
sub { goto &Crypt::RC4::RC4; }; |
101
|
|
|
|
|
|
|
*Crypt::RC4::keysize = sub {56}; |
102
|
|
|
|
|
|
|
*Crypt::RC4::blocksize = sub {1}; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
}, |
105
|
|
|
|
|
|
|
{ |
106
|
|
|
|
|
|
|
name => 'rc4-40', |
107
|
|
|
|
|
|
|
ssf => 40, |
108
|
|
|
|
|
|
|
bs => 1, |
109
|
|
|
|
|
|
|
ks => 5, |
110
|
|
|
|
|
|
|
pkg => 'Crypt::RC4', |
111
|
|
|
|
|
|
|
key => sub { $_[0] }, |
112
|
|
|
|
|
|
|
iv => sub {}, |
113
|
|
|
|
|
|
|
fixup => sub { |
114
|
|
|
|
|
|
|
*Crypt::RC4::encrypt = *Crypt::RC4::decrypt = |
115
|
|
|
|
|
|
|
sub { goto &Crypt::RC4::RC4; }; |
116
|
|
|
|
|
|
|
*Crypt::RC4::keysize = sub {40}; |
117
|
|
|
|
|
|
|
*Crypt::RC4::blocksize = sub {1}; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
}, |
120
|
|
|
|
|
|
|
); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
## The system we are on, might not be able to crypt the stream |
123
|
|
|
|
|
|
|
our $NO_CRYPT_AVAILABLE = 1; |
124
|
|
|
|
|
|
|
for (@ourciphers) { |
125
|
|
|
|
|
|
|
eval "require $_->{pkg}"; |
126
|
|
|
|
|
|
|
unless ($@) { |
127
|
|
|
|
|
|
|
$NO_CRYPT_AVAILABLE = 0; |
128
|
|
|
|
|
|
|
last; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
16
|
|
|
16
|
|
31
|
sub _order { 3 } |
133
|
|
|
|
|
|
|
sub _secflags { |
134
|
12
|
|
|
12
|
|
24
|
shift; |
135
|
12
|
|
|
|
|
38
|
scalar grep { $secflags{$_} } @_; |
|
8
|
|
|
|
|
55
|
|
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
9
|
|
|
9
|
0
|
1852
|
sub mechanism { 'DIGEST-MD5' } |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub _init { |
141
|
20
|
|
|
20
|
|
52
|
my ($pkg, $self) = @_; |
142
|
20
|
|
|
|
|
39
|
bless $self, $pkg; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# set default security properties |
145
|
20
|
|
|
|
|
77
|
$self->property('minssf', 0); |
146
|
20
|
|
|
|
|
62
|
$self->property('maxssf', int 2**31 - 1); # XXX - arbitrary "high" value |
147
|
20
|
|
|
|
|
57
|
$self->property('maxbuf', 0xFFFFFF); # maximum supported by GSSAPI mech |
148
|
20
|
|
|
|
|
54
|
$self->property('externalssf', 0); |
149
|
|
|
|
|
|
|
|
150
|
20
|
|
|
|
|
81
|
$self; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub _init_server { |
154
|
8
|
|
|
8
|
|
16
|
my $server = shift; |
155
|
8
|
|
100
|
|
|
29
|
my $options = shift || {}; |
156
|
8
|
50
|
33
|
|
|
74
|
if (!ref $options or ref $options ne 'HASH') { |
157
|
0
|
|
|
|
|
0
|
warn "options for DIGEST_MD5 should be a hashref"; |
158
|
0
|
|
|
|
|
0
|
$options = {}; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
## new server, means new nonce_counts |
162
|
8
|
|
|
|
|
20
|
$server->{nonce_counts} = {}; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
## determine supported qop |
165
|
8
|
|
|
|
|
22
|
my @qop = ('auth'); |
166
|
8
|
100
|
|
|
|
28
|
push @qop, 'auth-int' unless $options->{no_integrity}; |
167
|
|
|
|
|
|
|
push @qop, 'auth-conf' unless $options->{no_integrity} |
168
|
|
|
|
|
|
|
or $options->{no_confidentiality} |
169
|
8
|
50
|
66
|
|
|
46
|
or $NO_CRYPT_AVAILABLE; |
|
|
|
33
|
|
|
|
|
170
|
|
|
|
|
|
|
|
171
|
8
|
|
|
|
|
18
|
$server->{supported_qop} = { map { $_ => 1 } @qop }; |
|
15
|
|
|
|
|
65
|
|
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub init_sec_layer { |
175
|
16
|
|
|
16
|
0
|
42
|
my $self = shift; |
176
|
16
|
|
|
|
|
49
|
$self->{cipher} = undef; |
177
|
16
|
|
|
|
|
30
|
$self->{khc} = undef; |
178
|
16
|
|
|
|
|
27
|
$self->{khs} = undef; |
179
|
16
|
|
|
|
|
28
|
$self->{sndseqnum} = 0; |
180
|
16
|
|
|
|
|
38
|
$self->{rcvseqnum} = 0; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# reset properties for new session |
183
|
16
|
|
|
|
|
50
|
$self->property(maxout => undef); |
184
|
16
|
|
|
|
|
38
|
$self->property(ssf => undef); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# no initial value passed to the server |
188
|
|
|
|
|
|
|
sub client_start { |
189
|
7
|
|
|
7
|
0
|
22
|
my $self = shift; |
190
|
|
|
|
|
|
|
|
191
|
7
|
|
|
|
|
12
|
$self->{need_step} = 1; |
192
|
7
|
|
|
|
|
13
|
$self->{error} = undef; |
193
|
7
|
|
|
|
|
18
|
$self->{state} = 0; |
194
|
7
|
|
|
|
|
20
|
$self->init_sec_layer; |
195
|
7
|
|
|
|
|
19
|
''; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub server_start { |
199
|
9
|
|
|
9
|
0
|
598
|
my $self = shift; |
200
|
9
|
|
|
|
|
15
|
my $challenge = shift; |
201
|
9
|
|
100
|
3
|
|
35
|
my $cb = shift || sub {}; |
202
|
|
|
|
|
|
|
|
203
|
9
|
|
|
|
|
19
|
$self->{need_step} = 1; |
204
|
9
|
|
|
|
|
19
|
$self->{error} = undef; |
205
|
9
|
|
66
|
|
|
132
|
$self->{nonce} = md5_hex($NONCE || join (":", $$, time, rand)); |
206
|
|
|
|
|
|
|
|
207
|
9
|
|
|
|
|
29
|
$self->init_sec_layer; |
208
|
|
|
|
|
|
|
|
209
|
9
|
|
|
|
|
15
|
my $qop = [ sort keys %{$self->{supported_qop}} ]; |
|
9
|
|
|
|
|
45
|
|
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
## get the realm using callbacks but default to the host specified |
212
|
|
|
|
|
|
|
## during the instantiation of the SASL object |
213
|
9
|
|
|
|
|
42
|
my $realm = $self->_call('realm'); |
214
|
9
|
|
33
|
|
|
64
|
$realm ||= $self->host; |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my %response = ( |
217
|
|
|
|
|
|
|
nonce => $self->{nonce}, |
218
|
|
|
|
|
|
|
charset => 'utf-8', |
219
|
|
|
|
|
|
|
algorithm => 'md5-sess', |
220
|
|
|
|
|
|
|
realm => $realm, |
221
|
|
|
|
|
|
|
maxbuf => $self->property('maxbuf'), |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
## IN DRAFT ONLY: |
224
|
|
|
|
|
|
|
# If this directive is present multiple times the client MUST treat |
225
|
|
|
|
|
|
|
# it as if it received a single qop directive containing a comma |
226
|
|
|
|
|
|
|
# separated value from all instances. I.e., |
227
|
|
|
|
|
|
|
# 'qop="auth",qop="auth-int"' is the same as 'qop="auth,auth-int" |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
'qop' => $qop, |
230
|
9
|
|
|
|
|
26
|
'cipher' => [ map { $_->{name} } @ourciphers ], |
|
45
|
|
|
|
|
171
|
|
231
|
|
|
|
|
|
|
); |
232
|
9
|
|
|
|
|
31
|
my $final_response = _response(\%response); |
233
|
9
|
|
|
|
|
37
|
$cb->($final_response); |
234
|
9
|
|
|
|
|
53
|
return; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub client_step { # $self, $server_sasl_credentials |
238
|
11
|
|
|
11
|
0
|
40
|
my ($self, $challenge) = @_; |
239
|
11
|
|
|
|
|
29
|
$self->{server_params} = \my %sparams; |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Parse response parameters |
242
|
|
|
|
|
|
|
$self->_parse_challenge(\$challenge, server => $self->{server_params}) |
243
|
11
|
50
|
|
|
|
32
|
or return $self->set_error("Bad challenge: '$challenge'"); |
244
|
|
|
|
|
|
|
|
245
|
11
|
100
|
|
|
|
29
|
if ($self->{state} == 1) { |
246
|
|
|
|
|
|
|
# check server's `rspauth' response |
247
|
|
|
|
|
|
|
return $self->set_error("Server did not send rspauth in step 2") |
248
|
4
|
50
|
|
|
|
12
|
unless ($sparams{rspauth}); |
249
|
|
|
|
|
|
|
return $self->set_error("Invalid rspauth in step 2") |
250
|
4
|
50
|
|
|
|
17
|
unless ($self->{rspauth} eq $sparams{rspauth}); |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# all is well |
253
|
4
|
|
|
|
|
365
|
$self->set_success; |
254
|
4
|
|
|
|
|
12
|
return ''; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# check required fields in server challenge |
258
|
7
|
50
|
|
|
|
17
|
if (my @missing = grep { !exists $sparams{$_} } @server_required) { |
|
14
|
|
|
|
|
45
|
|
259
|
0
|
|
|
|
|
0
|
return $self->set_error("Server did not provide required field(s): @missing") |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
my %response = ( |
263
|
|
|
|
|
|
|
nonce => $sparams{'nonce'}, |
264
|
|
|
|
|
|
|
cnonce => md5_hex($CNONCE || join (":", $$, time, rand)), |
265
|
|
|
|
|
|
|
'digest-uri' => $self->service . '/' . $self->host, |
266
|
|
|
|
|
|
|
# calc how often the server nonce has been seen; server expects "00000001" |
267
|
|
|
|
|
|
|
nc => sprintf("%08d", ++$self->{nonce_counts}{$sparams{'nonce'}}), |
268
|
7
|
|
66
|
|
|
95
|
charset => $sparams{'charset'}, |
269
|
|
|
|
|
|
|
); |
270
|
|
|
|
|
|
|
|
271
|
7
|
50
|
|
|
|
25
|
return $self->set_error("Server qop too weak (qop = $sparams{'qop'})") |
272
|
|
|
|
|
|
|
unless ($self->_client_layer(\%sparams,\%response)); |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# let caller-provided fields override defaults: authorization ID, service name, realm |
275
|
|
|
|
|
|
|
|
276
|
7
|
|
50
|
|
|
21
|
my $s_realm = $sparams{realm} || []; |
277
|
7
|
|
|
|
|
27
|
my $realm = $self->_call('realm', @$s_realm); |
278
|
7
|
50
|
|
|
|
18
|
unless (defined $realm) { |
279
|
|
|
|
|
|
|
# If the user does not pick a realm, use the first from the server |
280
|
7
|
|
|
|
|
14
|
$realm = $s_realm->[0]; |
281
|
|
|
|
|
|
|
} |
282
|
7
|
50
|
|
|
|
21
|
if (defined $realm) { |
283
|
7
|
|
|
|
|
14
|
$response{realm} = $realm; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
7
|
|
|
|
|
18
|
my $authzid = $self->_call('authname'); |
287
|
7
|
100
|
|
|
|
19
|
if (defined $authzid) { |
288
|
2
|
|
|
|
|
6
|
$response{authzid} = $authzid; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
7
|
|
|
|
|
23
|
my $serv_name = $self->_call('serv'); |
292
|
7
|
50
|
|
|
|
19
|
if (defined $serv_name) { |
293
|
0
|
|
|
|
|
0
|
$response{'digest-uri'} .= '/' . $serv_name; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
7
|
|
|
|
|
16
|
my $user = $self->_call('user'); |
297
|
7
|
50
|
|
|
|
17
|
return $self->set_error("Username is required") |
298
|
|
|
|
|
|
|
unless defined $user; |
299
|
7
|
|
|
|
|
21
|
$response{username} = $user; |
300
|
|
|
|
|
|
|
|
301
|
7
|
|
|
|
|
19
|
my $password = $self->_call('pass'); |
302
|
7
|
50
|
|
|
|
24
|
return $self->set_error("Password is required") |
303
|
|
|
|
|
|
|
unless defined $password; |
304
|
|
|
|
|
|
|
|
305
|
7
|
|
100
|
|
|
31
|
$self->property('maxout', $sparams{maxbuf} || 65536); |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# Generate the response value |
308
|
7
|
|
|
|
|
14
|
$self->{state} = 1; |
309
|
|
|
|
|
|
|
|
310
|
7
|
|
|
|
|
24
|
my ($response, $rspauth) |
311
|
|
|
|
|
|
|
= $self->_compute_digests_and_set_keys($password, \%response); |
312
|
|
|
|
|
|
|
|
313
|
7
|
|
|
|
|
14
|
$response{response} = $response; |
314
|
7
|
|
|
|
|
14
|
$self->{rspauth} = $rspauth; |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# finally, return our response token |
317
|
7
|
|
|
|
|
24
|
return _response(\%response, "is_client"); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub _compute_digests_and_set_keys { |
321
|
12
|
|
|
12
|
|
23
|
my $self = shift; |
322
|
12
|
|
|
|
|
21
|
my $password = shift; |
323
|
12
|
|
|
|
|
18
|
my $params = shift; |
324
|
|
|
|
|
|
|
|
325
|
12
|
50
|
33
|
|
|
70
|
if (defined $params->{realm} and ref $params->{realm} eq 'ARRAY') { |
326
|
0
|
|
|
|
|
0
|
$params->{realm} = $params->{realm}[0]; |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
12
|
|
|
|
|
23
|
my $realm = $params->{realm}; |
330
|
12
|
50
|
|
|
|
30
|
$realm = "" unless defined $realm; |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
my $A1 = join (":", |
333
|
|
|
|
|
|
|
md5(join (":", $params->{username}, $realm, $password)), |
334
|
|
|
|
|
|
|
@$params{defined($params->{authzid}) |
335
|
12
|
100
|
|
|
|
107
|
? qw(nonce cnonce authzid) |
336
|
|
|
|
|
|
|
: qw(nonce cnonce) |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# pre-compute MD5(A1) and HEX(MD5(A1)); these are used multiple times below |
341
|
12
|
|
|
|
|
73
|
my $hdA1 = unpack("H*", (my $dA1 = md5($A1)) ); |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# derive keys for layer encryption / integrity |
344
|
12
|
|
|
|
|
49
|
$self->{kic} = md5($dA1, |
345
|
|
|
|
|
|
|
'Digest session key to client-to-server signing key magic constant'); |
346
|
|
|
|
|
|
|
|
347
|
12
|
|
|
|
|
44
|
$self->{kis} = md5($dA1, |
348
|
|
|
|
|
|
|
'Digest session key to server-to-client signing key magic constant'); |
349
|
|
|
|
|
|
|
|
350
|
12
|
50
|
|
|
|
34
|
if (my $cipher = $self->{cipher}) { |
351
|
0
|
0
|
|
0
|
|
0
|
&{ $cipher->{fixup} || sub{} }; |
|
0
|
|
|
|
|
0
|
|
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# compute keys for encryption |
354
|
0
|
|
|
|
|
0
|
my $ks = $cipher->{ks}; |
355
|
0
|
|
|
|
|
0
|
$self->{kcc} = md5(substr($dA1,0,$ks), |
356
|
|
|
|
|
|
|
'Digest H(A1) to client-to-server sealing key magic constant'); |
357
|
0
|
|
|
|
|
0
|
$self->{kcs} = md5(substr($dA1,0,$ks), |
358
|
|
|
|
|
|
|
'Digest H(A1) to server-to-client sealing key magic constant'); |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# get an encryption and decryption handle for the chosen cipher |
361
|
0
|
|
|
|
|
0
|
$self->{khc} = $cipher->{pkg}->new($cipher->{key}->($self->{kcc})); |
362
|
0
|
|
|
|
|
0
|
$self->{khs} = $cipher->{pkg}->new($cipher->{key}->($self->{kcs})); |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# initialize IVs |
365
|
0
|
|
|
|
|
0
|
$self->{ivc} = $cipher->{iv}->($self->{kcc}); |
366
|
0
|
|
|
|
|
0
|
$self->{ivs} = $cipher->{iv}->($self->{kcs}); |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
12
|
|
|
|
|
27
|
my $A2 = "AUTHENTICATE:" . $params->{'digest-uri'}; |
370
|
12
|
100
|
|
|
|
31
|
$A2 .= ":00000000000000000000000000000000" if ($params->{qop} ne 'auth'); |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
my $response = md5_hex( |
373
|
12
|
|
|
|
|
84
|
join (":", $hdA1, @$params{qw(nonce nc cnonce qop)}, md5_hex($A2)) |
374
|
|
|
|
|
|
|
); |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# calculate server `rspauth' response, so we can check in step 2 |
377
|
|
|
|
|
|
|
# the only difference here is in the A2 string which from which |
378
|
|
|
|
|
|
|
# `AUTHENTICATE' is omitted in the calculation of `rspauth' |
379
|
12
|
|
|
|
|
31
|
$A2 = ":" . $params->{'digest-uri'}; |
380
|
12
|
100
|
|
|
|
31
|
$A2 .= ":00000000000000000000000000000000" if ($params->{qop} ne 'auth'); |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
my $rspauth = md5_hex( |
383
|
12
|
|
|
|
|
67
|
join (":", $hdA1, @$params{qw(nonce nc cnonce qop)}, md5_hex($A2)) |
384
|
|
|
|
|
|
|
); |
385
|
|
|
|
|
|
|
|
386
|
12
|
|
|
|
|
45
|
return ($response, $rspauth); |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
sub server_step { |
390
|
8
|
|
|
8
|
0
|
30
|
my $self = shift; |
391
|
8
|
|
|
|
|
18
|
my $challenge = shift; |
392
|
8
|
|
100
|
2
|
|
26
|
my $cb = shift || sub {}; |
393
|
|
|
|
|
|
|
|
394
|
8
|
|
|
|
|
30
|
$self->{client_params} = \my %cparams; |
395
|
8
|
100
|
|
|
|
26
|
unless ( $self->_parse_challenge(\$challenge, client => $self->{client_params}) ) { |
396
|
1
|
|
|
|
|
5
|
$self->set_error("Bad challenge: '$challenge'"); |
397
|
1
|
|
|
|
|
3
|
return $cb->(); |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# check required fields in server challenge |
401
|
7
|
50
|
|
|
|
18
|
if (my @missing = grep { !exists $cparams{$_} } @client_required) { |
|
42
|
|
|
|
|
131
|
|
402
|
0
|
|
|
|
|
0
|
$self->set_error("Client did not provide required field(s): @missing"); |
403
|
0
|
|
|
|
|
0
|
return $cb->(); |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
7
|
|
50
|
|
|
29
|
my $count = hex ($cparams{'nc'} || 0); |
407
|
7
|
50
|
|
|
|
38
|
unless ($count == ++$self->{nonce_counts}{$cparams{nonce}}) { |
408
|
0
|
|
|
|
|
0
|
$self->set_error("nonce-count doesn't match: $count"); |
409
|
0
|
|
|
|
|
0
|
return $cb->(); |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
7
|
|
50
|
|
|
22
|
my $qop = $cparams{'qop'} || "auth"; |
413
|
7
|
100
|
|
|
|
20
|
unless ($self->is_qop_supported($qop)) { |
414
|
1
|
|
|
|
|
10
|
$self->set_error("Client qop not supported (qop = '$qop')"); |
415
|
1
|
|
|
|
|
3
|
return $cb->(); |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
6
|
|
|
|
|
14
|
my $username = $cparams{'username'}; |
419
|
6
|
50
|
|
|
|
14
|
unless ($username) { |
420
|
0
|
|
|
|
|
0
|
$self->set_error("Client didn't provide a username"); |
421
|
0
|
|
|
|
|
0
|
return $cb->(); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
# "The authzid MUST NOT be an empty string." |
425
|
6
|
50
|
66
|
|
|
23
|
if (exists $cparams{authzid} && $cparams{authzid} eq '') { |
426
|
0
|
|
|
|
|
0
|
$self->set_error("authzid cannot be empty"); |
427
|
0
|
|
|
|
|
0
|
return $cb->(); |
428
|
|
|
|
|
|
|
} |
429
|
6
|
|
|
|
|
19
|
my $authzid = $cparams{authzid}; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# digest-uri: "Servers SHOULD check that the supplied value is correct. |
432
|
|
|
|
|
|
|
# This will detect accidental connection to the incorrect server, as well as |
433
|
|
|
|
|
|
|
# some redirection attacks" |
434
|
6
|
|
|
|
|
11
|
my $digest_uri = $cparams{'digest-uri'}; |
435
|
6
|
|
|
|
|
25
|
my ($cservice, $chost, $cservname) = split '/', $digest_uri, 3; |
436
|
6
|
100
|
66
|
|
|
24
|
if ($cservice ne $self->service or $chost ne $self->host) { |
437
|
|
|
|
|
|
|
# XXX deal with serv_name |
438
|
1
|
|
|
|
|
4
|
$self->set_error("Incorrect digest-uri"); |
439
|
1
|
|
|
|
|
11
|
return $cb->(); |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
5
|
50
|
|
|
|
25
|
unless (defined $self->callback('getsecret')) { |
443
|
0
|
|
|
|
|
0
|
$self->set_error("a getsecret callback MUST be defined"); |
444
|
0
|
|
|
|
|
0
|
$cb->(); |
445
|
0
|
|
|
|
|
0
|
return; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
5
|
|
|
|
|
13
|
my $realm = $self->{client_params}->{'realm'}; |
449
|
|
|
|
|
|
|
my $response_check = sub { |
450
|
5
|
|
|
5
|
|
11
|
my $password = shift; |
451
|
5
|
50
|
|
|
|
15
|
return $self->set_error("Cannot get the passord for $username") |
452
|
|
|
|
|
|
|
unless defined $password; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
## configure the security layer |
455
|
5
|
50
|
|
|
|
14
|
$self->_server_layer($qop) |
456
|
|
|
|
|
|
|
or return $self->set_error("Cannot negociate the security layer"); |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
my ($expected, $rspauth) |
459
|
5
|
|
|
|
|
16
|
= $self->_compute_digests_and_set_keys($password, $self->{client_params}); |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
return $self->set_error("Incorrect response $self->{client_params}->{response} <> $expected") |
462
|
5
|
100
|
|
|
|
27
|
unless $expected eq $self->{client_params}->{response}; |
463
|
|
|
|
|
|
|
|
464
|
3
|
|
|
|
|
10
|
my %response = ( |
465
|
|
|
|
|
|
|
rspauth => $rspauth, |
466
|
|
|
|
|
|
|
); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# I'm not entirely sure of what I am doing |
469
|
3
|
|
|
|
|
19
|
$self->{answer}{$_} = $self->{client_params}->{$_} for qw/username authzid realm serv/; |
470
|
|
|
|
|
|
|
|
471
|
3
|
|
|
|
|
19
|
$self->set_success; |
472
|
3
|
|
|
|
|
8
|
return _response(\%response); |
473
|
5
|
|
|
|
|
25
|
}; |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
$self->callback('getsecret')->( |
476
|
|
|
|
|
|
|
$self, |
477
|
|
|
|
|
|
|
{ user => $username, realm => $realm, authzid => $authzid }, |
478
|
5
|
|
|
5
|
|
1812
|
sub { $cb->( $response_check->( shift ) ) }, |
479
|
5
|
|
|
|
|
32
|
); |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
sub is_qop_supported { |
483
|
7
|
|
|
7
|
0
|
16
|
my $self = shift; |
484
|
7
|
|
|
|
|
9
|
my $qop = shift; |
485
|
7
|
|
|
|
|
22
|
return $self->{supported_qop}{$qop}; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub _response { |
489
|
19
|
|
|
19
|
|
33
|
my $response = shift; |
490
|
19
|
|
|
|
|
29
|
my $is_client = shift; |
491
|
|
|
|
|
|
|
|
492
|
19
|
|
|
|
|
33
|
my @out; |
493
|
19
|
|
|
|
|
126
|
for my $k (sort keys %$response) { |
494
|
131
|
|
66
|
|
|
325
|
my $is_array = ref $response->{$k} && ref $response->{$k} eq 'ARRAY'; |
495
|
131
|
100
|
|
|
|
267
|
my @values = $is_array ? @{$response->{$k}} : ($response->{$k}); |
|
18
|
|
|
|
|
46
|
|
496
|
|
|
|
|
|
|
# Per spec, one way of doing it: multiple k=v |
497
|
|
|
|
|
|
|
#push @out, [$k, $_] for @values; |
498
|
|
|
|
|
|
|
# other way: comma separated list |
499
|
131
|
|
|
|
|
408
|
push @out, [$k, join (',', @values)]; |
500
|
|
|
|
|
|
|
} |
501
|
19
|
|
|
|
|
50
|
return join (",", map { _qdval($_->[0], $_->[1], $is_client) } @out); |
|
131
|
|
|
|
|
256
|
|
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
sub _parse_challenge { |
505
|
19
|
|
|
19
|
|
42
|
my $self = shift; |
506
|
19
|
|
|
|
|
28
|
my $challenge_ref = shift; |
507
|
19
|
|
|
|
|
30
|
my $type = shift; |
508
|
19
|
|
|
|
|
26
|
my $params = shift; |
509
|
|
|
|
|
|
|
|
510
|
19
|
|
|
|
|
193
|
while($$challenge_ref =~ |
511
|
|
|
|
|
|
|
s/^(?:\s*,)*\s* # remaining or crap |
512
|
|
|
|
|
|
|
([\w-]+) # key, eg: qop |
513
|
|
|
|
|
|
|
= |
514
|
|
|
|
|
|
|
("([^\\"]+|\\.)*"|[^,]+) # value, eg: auth-conf or "NoNcE" |
515
|
|
|
|
|
|
|
\s*(?:,\s*)* # remaining |
516
|
|
|
|
|
|
|
//x) { |
517
|
|
|
|
|
|
|
|
518
|
117
|
|
|
|
|
351
|
my ($k, $v) = ($1,$2); |
519
|
117
|
100
|
|
|
|
336
|
if ($v =~ /^"(.*)"$/s) { |
520
|
65
|
|
|
|
|
149
|
($v = $1) =~ s/\\(.)/$1/g; |
521
|
|
|
|
|
|
|
} |
522
|
117
|
100
|
|
|
|
315
|
if (exists $multi{$type}{$k}) { |
|
|
100
|
|
|
|
|
|
523
|
7
|
|
50
|
|
|
35
|
my $aref = $params->{$k} ||= []; |
524
|
7
|
|
|
|
|
38
|
push @$aref, $v; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
elsif (defined $params->{$k}) { |
527
|
1
|
|
|
|
|
7
|
return $self->set_error("Bad challenge: '$$challenge_ref'"); |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
else { |
530
|
109
|
|
|
|
|
668
|
$params->{$k} = $v; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
} |
533
|
18
|
50
|
|
|
|
66
|
return length $$challenge_ref ? 0 : 1; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
sub _qdval { |
537
|
131
|
|
|
131
|
|
244
|
my ($k, $v, $is_client) = @_; |
538
|
|
|
|
|
|
|
|
539
|
131
|
100
|
|
|
|
240
|
my $qdval = $is_client ? \%cqdval : \%sqdval; |
540
|
|
|
|
|
|
|
|
541
|
131
|
50
|
|
|
|
309
|
if (!defined $v) { |
|
|
100
|
|
|
|
|
|
542
|
0
|
|
|
|
|
0
|
return; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
elsif (exists $qdval->{$k}) { |
545
|
73
|
|
|
|
|
161
|
$v =~ s/([\\"])/\\$1/g; |
546
|
73
|
|
|
|
|
301
|
return qq{$k="$v"}; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
58
|
|
|
|
|
194
|
return "$k=$v"; |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
sub _server_layer { |
553
|
5
|
|
|
5
|
|
12
|
my ($self, $auth) = @_; |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
# XXX dupe |
556
|
|
|
|
|
|
|
# construct our qop mask |
557
|
5
|
|
|
|
|
21
|
my $maxssf = $self->property('maxssf') - $self->property('externalssf'); |
558
|
5
|
50
|
|
|
|
17
|
$maxssf = 0 if ($maxssf < 0); |
559
|
5
|
|
|
|
|
13
|
my $minssf = $self->property('minssf') - $self->property('externalssf'); |
560
|
5
|
50
|
|
|
|
23
|
$minssf = 0 if ($minssf < 0); |
561
|
|
|
|
|
|
|
|
562
|
5
|
50
|
|
|
|
13
|
return undef if ($maxssf < $minssf); # sanity check |
563
|
|
|
|
|
|
|
|
564
|
5
|
|
|
|
|
12
|
my $ciphers = [ map { $_->{name} } @ourciphers ]; |
|
25
|
|
|
|
|
50
|
|
565
|
5
|
50
|
33
|
|
|
17
|
if (( $auth eq 'auth-conf') |
566
|
|
|
|
|
|
|
and $self->_select_cipher($minssf, $maxssf, $ciphers )) { |
567
|
0
|
|
|
|
|
0
|
$self->property('ssf', $self->{cipher}->{ssf}); |
568
|
0
|
|
|
|
|
0
|
return 1; |
569
|
|
|
|
|
|
|
} |
570
|
5
|
100
|
|
|
|
12
|
if ($auth eq 'auth-int') { |
571
|
3
|
|
|
|
|
10
|
$self->property('ssf', 1); |
572
|
3
|
|
|
|
|
10
|
return 1; |
573
|
|
|
|
|
|
|
} |
574
|
2
|
50
|
|
|
|
17
|
if ($auth eq 'auth') { |
575
|
2
|
|
|
|
|
8
|
$self->property('ssf', 0); |
576
|
2
|
|
|
|
|
8
|
return 1; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
0
|
|
|
|
|
0
|
return undef; |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
sub _client_layer { |
583
|
7
|
|
|
7
|
|
25
|
my ($self, $sparams, $response) = @_; |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
# construct server qop mask |
586
|
|
|
|
|
|
|
# qop in server challenge is optional: if not there "auth" is assumed |
587
|
7
|
|
|
|
|
14
|
my $smask = 0; |
588
|
|
|
|
|
|
|
map { |
589
|
13
|
100
|
|
|
|
44
|
m/^auth$/ and $smask |= 1; |
590
|
13
|
100
|
|
|
|
41
|
m/^auth-int$/ and $smask |= 2; |
591
|
13
|
50
|
|
|
|
39
|
m/^auth-conf$/ and $smask |= 4; |
592
|
7
|
|
50
|
|
|
29
|
} split(/,/, $sparams->{qop}||'auth'); # XXX I think we might have a bug here bc. of LWS |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# construct our qop mask |
595
|
7
|
|
|
|
|
14
|
my $cmask = 0; |
596
|
7
|
|
|
|
|
26
|
my $maxssf = $self->property('maxssf') - $self->property('externalssf'); |
597
|
7
|
50
|
|
|
|
20
|
$maxssf = 0 if ($maxssf < 0); |
598
|
7
|
|
|
|
|
15
|
my $minssf = $self->property('minssf') - $self->property('externalssf'); |
599
|
7
|
50
|
|
|
|
29
|
$minssf = 0 if ($minssf < 0); |
600
|
|
|
|
|
|
|
|
601
|
7
|
50
|
|
|
|
18
|
return undef if ($maxssf < $minssf); # sanity check |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# ssf values > 1 mean integrity and confidentiality |
604
|
|
|
|
|
|
|
# ssf == 1 means integrity but no confidentiality |
605
|
|
|
|
|
|
|
# ssf < 1 means neither integrity nor confidentiality |
606
|
|
|
|
|
|
|
# no security layer can be had if buffer size is 0 |
607
|
7
|
50
|
|
|
|
22
|
$cmask |= 1 if ($minssf < 1); |
608
|
7
|
50
|
33
|
|
|
44
|
$cmask |= 2 if ($minssf <= 1 and $maxssf >= 1); |
609
|
7
|
50
|
|
|
|
18
|
$cmask |= 4 if ($maxssf > 1); |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# find common bits |
612
|
7
|
|
|
|
|
10
|
$cmask &= $smask; |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
# parse server cipher options |
615
|
7
|
|
100
|
|
|
66
|
my @sciphers = split(/,/, $sparams->{'cipher-opts'}||$sparams->{cipher}||''); |
616
|
|
|
|
|
|
|
|
617
|
7
|
50
|
33
|
|
|
45
|
if (($cmask & 4) and $self->_select_cipher($minssf,$maxssf,\@sciphers)) { |
618
|
0
|
|
|
|
|
0
|
$response->{qop} = 'auth-conf'; |
619
|
0
|
|
|
|
|
0
|
$response->{cipher} = $self->{cipher}->{name}; |
620
|
0
|
|
|
|
|
0
|
$self->property('ssf', $self->{cipher}->{ssf}); |
621
|
0
|
|
|
|
|
0
|
return 1; |
622
|
|
|
|
|
|
|
} |
623
|
7
|
100
|
|
|
|
24
|
if ($cmask & 2) { |
624
|
4
|
|
|
|
|
9
|
$response->{qop} = 'auth-int'; |
625
|
4
|
|
|
|
|
12
|
$self->property('ssf', 1); |
626
|
4
|
|
|
|
|
14
|
return 1; |
627
|
|
|
|
|
|
|
} |
628
|
3
|
50
|
|
|
|
9
|
if ($cmask & 1) { |
629
|
3
|
|
|
|
|
7
|
$response->{qop} = 'auth'; |
630
|
3
|
|
|
|
|
11
|
$self->property('ssf', 0); |
631
|
3
|
|
|
|
|
11
|
return 1; |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
0
|
|
|
|
|
|
return undef; |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
sub _select_cipher { |
638
|
0
|
|
|
0
|
|
|
my ($self, $minssf, $maxssf, $ciphers) = @_; |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# compose a subset of candidate ciphers based on ssf and peer list |
641
|
|
|
|
|
|
|
my @a = map { |
642
|
0
|
|
|
|
|
|
my $c = $_; |
|
0
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
(grep { $c->{name} eq $_ } @$ciphers and |
644
|
0
|
0
|
0
|
|
|
|
$c->{ssf} >= $minssf and $c->{ssf} <= $maxssf) ? $_ : () |
645
|
|
|
|
|
|
|
} @ourciphers; |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# from these, select the first one we can create an instance of |
648
|
0
|
|
|
|
|
|
for (@a) { |
649
|
0
|
0
|
|
|
|
|
next unless eval "require $_->{pkg}"; |
650
|
0
|
|
|
|
|
|
$self->{cipher} = $_; |
651
|
0
|
|
|
|
|
|
return 1; |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
0
|
|
|
|
|
|
return 0; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
|
657
|
5
|
|
|
5
|
|
52
|
use Digest::HMAC_MD5 qw(hmac_md5); |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
3367
|
|
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
sub encode { # input: self, plaintext buffer,length (length not used here) |
660
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
661
|
0
|
|
|
|
|
|
my $seqnum = pack('N', $self->{sndseqnum}++); |
662
|
0
|
|
|
|
|
|
my $mac = substr(hmac_md5($seqnum . $_[0], $self->{kic}), 0, 10); |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# if integrity only, return concatenation of buffer, MAC, TYPE and SEQNUM |
665
|
0
|
0
|
|
|
|
|
return $_[0] . $mac.pack('n',1) . $seqnum unless ($self->{khc}); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# must encrypt, block ciphers need padding bytes |
668
|
0
|
|
|
|
|
|
my $pad = ''; |
669
|
0
|
|
|
|
|
|
my $bs = $self->{cipher}->{bs}; |
670
|
0
|
0
|
|
|
|
|
if ($bs > 1) { |
671
|
|
|
|
|
|
|
# padding is added in between BUF and MAC |
672
|
0
|
|
|
|
|
|
my $n = $bs - ((length($_[0]) + 10) & ($bs - 1)); |
673
|
0
|
|
|
|
|
|
$pad = chr($n) x $n; |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# XXX - for future AES cipher support, the currently used common _crypt() |
677
|
|
|
|
|
|
|
# function probably wont do; we might to switch to per-cipher routines |
678
|
|
|
|
|
|
|
# like so: |
679
|
|
|
|
|
|
|
# return $self->{khc}->encrypt($_[0] . $pad . $mac) . pack('n', 1) . $seqnum; |
680
|
0
|
|
|
|
|
|
return $self->_crypt(0, $_[0] . $pad . $mac) . pack('n', 1) . $seqnum; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
sub decode { # input: self, cipher buffer,length |
684
|
0
|
|
|
0
|
0
|
|
my ($self, $buf, $len) = @_; |
685
|
|
|
|
|
|
|
|
686
|
0
|
0
|
|
|
|
|
return if ($len <= 16); |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
# extract TYPE/SEQNUM from end of buffer |
689
|
0
|
|
|
|
|
|
my ($type,$seqnum) = unpack('na[4]', substr($buf, -6, 6, '')); |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
# decrypt remaining buffer, if necessary |
692
|
0
|
0
|
|
|
|
|
if ($self->{khs}) { |
693
|
|
|
|
|
|
|
# XXX - see remark above in encode() #$buf = $self->{khs}->decrypt($buf); |
694
|
0
|
|
|
|
|
|
$buf = $self->_crypt(1, $buf); |
695
|
|
|
|
|
|
|
} |
696
|
0
|
0
|
|
|
|
|
return unless ($buf); |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# extract 10-byte MAC from the end of (decrypted) buffer |
699
|
0
|
|
|
|
|
|
my ($mac) = unpack('a[10]', substr($buf, -10, 10, '')); |
700
|
|
|
|
|
|
|
|
701
|
0
|
0
|
0
|
|
|
|
if ($self->{khs} and $self->{cipher}->{bs} > 1) { |
702
|
|
|
|
|
|
|
# remove padding |
703
|
0
|
|
|
|
|
|
my $n = ord(substr($buf, -1, 1)); |
704
|
0
|
|
|
|
|
|
substr($buf, -$n, $n, ''); |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# check the MAC |
708
|
0
|
|
|
|
|
|
my $check = substr(hmac_md5($seqnum . $buf, $self->{kis}), 0, 10); |
709
|
0
|
0
|
|
|
|
|
return if ($mac ne $check); |
710
|
0
|
0
|
|
|
|
|
return if (unpack('N', $seqnum) != $self->{rcvseqnum}); |
711
|
0
|
|
|
|
|
|
$self->{rcvseqnum}++; |
712
|
|
|
|
|
|
|
|
713
|
0
|
|
|
|
|
|
return $buf; |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
sub _crypt { # input: op(decrypting=1/encrypting=0)), buffer |
717
|
0
|
|
|
0
|
|
|
my ($self,$d) = (shift,shift); |
718
|
0
|
|
|
|
|
|
my $bs = $self->{cipher}->{bs}; |
719
|
|
|
|
|
|
|
|
720
|
0
|
0
|
|
|
|
|
if ($bs <= 1) { |
721
|
|
|
|
|
|
|
# stream cipher |
722
|
0
|
0
|
|
|
|
|
return $d ? $self->{khs}->decrypt($_[0]) : $self->{khc}->encrypt($_[0]) |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
# the remainder of this sub is for block ciphers |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
# get current IV |
728
|
0
|
0
|
|
|
|
|
my $piv = \$self->{$d ? 'ivs' : 'ivc'}; |
729
|
0
|
|
|
|
|
|
my $iv = $$piv; |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
my $result = join '', map { |
732
|
0
|
|
|
|
|
|
my $x = $d |
733
|
|
|
|
|
|
|
? $iv ^ $self->{khs}->decrypt($_) |
734
|
0
|
0
|
|
|
|
|
: $self->{khc}->encrypt($iv ^ $_); |
735
|
0
|
0
|
|
|
|
|
$iv = $d ? $_ : $x; |
736
|
0
|
|
|
|
|
|
$x; |
737
|
|
|
|
|
|
|
} unpack("a$bs "x(int(length($_[0])/$bs)), $_[0]); |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
# store current IV |
740
|
0
|
|
|
|
|
|
$$piv = $iv; |
741
|
0
|
|
|
|
|
|
return $result; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
1; |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
__END__ |