line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mail::DKIM::PublicKey; |
2
|
14
|
|
|
14
|
|
116
|
use strict; |
|
14
|
|
|
|
|
30
|
|
|
14
|
|
|
|
|
424
|
|
3
|
14
|
|
|
14
|
|
69
|
use warnings; |
|
14
|
|
|
|
|
32
|
|
|
14
|
|
|
|
|
675
|
|
4
|
|
|
|
|
|
|
our $VERSION = '1.20230630'; # VERSION |
5
|
|
|
|
|
|
|
# ABSTRACT: Represents a DKIM key |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
# Copyright 2005 Messiah College. All rights reserved. |
8
|
|
|
|
|
|
|
# Jason Long |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# Copyright (c) 2004 Anthony D. Urso. All rights reserved. |
11
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or |
12
|
|
|
|
|
|
|
# modify it under the same terms as Perl itself. |
13
|
|
|
|
|
|
|
|
14
|
14
|
|
|
14
|
|
83
|
use base ( 'Mail::DKIM::KeyValueList', 'Mail::DKIM::Key' ); |
|
14
|
|
|
|
|
37
|
|
|
14
|
|
|
|
|
6646
|
|
15
|
|
|
|
|
|
|
*calculate_EM = \&Mail::DKIM::Key::calculate_EM; |
16
|
|
|
|
|
|
|
|
17
|
14
|
|
|
14
|
|
3058
|
use Crypt::OpenSSL::RSA; |
|
14
|
|
|
|
|
40585
|
|
|
14
|
|
|
|
|
439
|
|
18
|
14
|
|
|
14
|
|
3027
|
use Crypt::PK::Ed25519; |
|
14
|
|
|
|
|
161760
|
|
|
14
|
|
|
|
|
4225
|
|
19
|
14
|
|
|
14
|
|
6945
|
use MIME::Base64; |
|
14
|
|
|
|
|
9040
|
|
|
14
|
|
|
|
|
824
|
|
20
|
14
|
|
|
14
|
|
6154
|
use Mail::DKIM::DNS; |
|
14
|
|
|
|
|
195
|
|
|
14
|
|
|
|
|
41381
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub new { |
23
|
363
|
|
|
363
|
0
|
626
|
my $type = shift; |
24
|
363
|
|
|
|
|
609
|
my %prms = @_; |
25
|
|
|
|
|
|
|
|
26
|
363
|
|
|
|
|
684
|
my $self = {}; |
27
|
|
|
|
|
|
|
|
28
|
363
|
|
|
|
|
832
|
$self->{'GRAN'} = $prms{'Granularity'}; |
29
|
363
|
|
|
|
|
633
|
$self->{'NOTE'} = $prms{'Note'}; |
30
|
363
|
|
|
|
|
617
|
$self->{'TEST'} = $prms{'Testing'}; |
31
|
363
|
|
50
|
|
|
1301
|
$self->{'TYPE'} = ( $prms{'Type'} or 'rsa' ); |
32
|
363
|
|
|
|
|
638
|
$self->{'DATA'} = $prms{'Data'}; |
33
|
|
|
|
|
|
|
|
34
|
363
|
|
|
|
|
991
|
bless $self, $type; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub fetch { |
39
|
2
|
|
|
2
|
1
|
957
|
my $class = shift; |
40
|
2
|
|
|
|
|
12
|
my $waiter = $class->fetch_async(@_); |
41
|
2
|
|
|
|
|
6
|
my $self = $waiter->(); |
42
|
2
|
|
|
|
|
90
|
return $self; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# fetch_async() - asynchronously tries fetching a specific public key |
46
|
|
|
|
|
|
|
# using a specific protocol. |
47
|
|
|
|
|
|
|
# |
48
|
|
|
|
|
|
|
# Usage: |
49
|
|
|
|
|
|
|
# my $fut = Mail::DKIM::PublicKey->fetch_async( |
50
|
|
|
|
|
|
|
# Protocol => 'dns/txt', |
51
|
|
|
|
|
|
|
# Selector => 'selector1', |
52
|
|
|
|
|
|
|
# Domain => 'example.org', |
53
|
|
|
|
|
|
|
# Callbacks => { ... }, #optional |
54
|
|
|
|
|
|
|
# ); |
55
|
|
|
|
|
|
|
# |
56
|
|
|
|
|
|
|
# # some later time |
57
|
|
|
|
|
|
|
# my $pubkey = $fut->(); # blocks until the public key is returned |
58
|
|
|
|
|
|
|
# |
59
|
|
|
|
|
|
|
sub fetch_async { |
60
|
425
|
|
|
425
|
0
|
848
|
my $class = shift; |
61
|
425
|
|
|
|
|
1644
|
my %prms = @_; |
62
|
|
|
|
|
|
|
|
63
|
425
|
50
|
33
|
|
|
2924
|
defined( $prms{Protocol} ) && $prms{Protocol} =~ m{^dns(/txt)?$}s |
64
|
|
|
|
|
|
|
or die "invalid/missing Protocol\n"; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
defined( $prms{Selector} ) && length( $prms{Selector} ) |
67
|
425
|
100
|
66
|
|
|
1600
|
or die "invalid/missing Selector\n"; |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
defined( $prms{Domain} ) && length( $prms{Domain} ) |
70
|
423
|
50
|
33
|
|
|
1272
|
or die "invalid/missing Domain\n"; |
71
|
|
|
|
|
|
|
|
72
|
423
|
100
|
|
|
|
583
|
my %callbacks = %{ $prms{Callbacks} || {} }; |
|
423
|
|
|
|
|
1953
|
|
73
|
423
|
|
100
|
2
|
|
1152
|
my $on_success = $callbacks{Success} || sub { $_[0] }; |
|
2
|
|
|
|
|
22
|
|
74
|
|
|
|
|
|
|
$callbacks{Success} = sub { |
75
|
368
|
|
|
368
|
|
742
|
my @resp = @_; |
76
|
368
|
100
|
|
|
|
884
|
unless (@resp) { |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# no requested resource records or NXDOMAIN, |
79
|
5
|
|
|
|
|
20
|
return $on_success->(); |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
363
|
|
|
|
|
558
|
my $strn; |
83
|
363
|
|
|
|
|
669
|
foreach my $rr (@resp) { |
84
|
363
|
50
|
|
|
|
798
|
next unless $rr->type eq 'TXT'; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# join with no intervening spaces, RFC 6376 |
87
|
363
|
50
|
|
|
|
6581
|
if ( Net::DNS->VERSION >= 0.69 ) { |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# must call txtdata() in a list context |
90
|
363
|
|
|
|
|
1234
|
$strn = join '', $rr->txtdata; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
else { |
93
|
|
|
|
|
|
|
# char_str_list method is 'historical' |
94
|
0
|
|
|
|
|
0
|
$strn = join '', $rr->char_str_list; |
95
|
|
|
|
|
|
|
} |
96
|
363
|
|
|
|
|
11443
|
last; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$strn |
100
|
363
|
50
|
|
|
|
806
|
or return $on_success->(); |
101
|
|
|
|
|
|
|
|
102
|
363
|
|
|
|
|
1099
|
my $self = $class->parse($strn); |
103
|
360
|
|
|
|
|
1158
|
$self->{Selector} = $prms{'Selector'}; |
104
|
360
|
|
|
|
|
702
|
$self->{Domain} = $prms{'Domain'}; |
105
|
360
|
|
100
|
|
|
996
|
$self->{TYPE} = $self->get_tag('k') || 'rsa'; |
106
|
360
|
|
|
|
|
1130
|
$self->check; |
107
|
|
|
|
|
|
|
|
108
|
355
|
|
|
|
|
938
|
return $on_success->($self); |
109
|
423
|
|
|
|
|
1778
|
}; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# |
112
|
|
|
|
|
|
|
# perform DNS query for public key... |
113
|
|
|
|
|
|
|
# |
114
|
423
|
|
|
|
|
1264
|
my $host = $prms{Selector} . '._domainkey.' . $prms{Domain}; |
115
|
423
|
|
|
|
|
1260
|
my $waiter = |
116
|
|
|
|
|
|
|
Mail::DKIM::DNS::query_async( $host, 'TXT', Callbacks => \%callbacks, ); |
117
|
423
|
|
|
|
|
1205
|
return $waiter; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# check syntax of the public key |
122
|
|
|
|
|
|
|
# throw an error if any errors are detected |
123
|
|
|
|
|
|
|
sub check { |
124
|
360
|
|
|
360
|
0
|
528
|
my $self = shift; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# check public key version tag |
127
|
360
|
100
|
|
|
|
705
|
if ( my $v = $self->get_tag('v') ) { |
128
|
339
|
100
|
|
|
|
801
|
unless ( $v eq 'DKIM1' ) { |
129
|
1
|
|
|
|
|
10
|
die "unsupported version\n"; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# check public key granularity |
134
|
359
|
|
|
|
|
747
|
my $g = $self->granularity; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# check key type |
137
|
359
|
100
|
|
|
|
770
|
if ( my $k = $self->get_tag('k') ) { |
138
|
334
|
100
|
100
|
|
|
779
|
unless ( $k eq 'rsa' || $k eq 'ed25519' ) { |
139
|
1
|
|
|
|
|
13
|
die "unsupported key type\n"; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# check public-key data |
144
|
358
|
|
|
|
|
778
|
my $p = $self->data; |
145
|
358
|
50
|
|
|
|
866
|
if ( not defined $p ) { |
146
|
0
|
|
|
|
|
0
|
die "missing p= tag\n"; |
147
|
|
|
|
|
|
|
} |
148
|
358
|
100
|
|
|
|
678
|
if ( $p eq '' ) { |
149
|
1
|
|
|
|
|
10
|
die "revoked\n"; |
150
|
|
|
|
|
|
|
} |
151
|
357
|
50
|
|
|
|
1468
|
unless ( $p =~ /^[A-Za-z0-9\+\/\=]+$/ ) { |
152
|
0
|
|
|
|
|
0
|
die "invalid data\n"; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# have OpenSSL load the key |
156
|
|
|
|
|
|
|
eval { |
157
|
357
|
|
|
|
|
1195
|
local $SIG{__DIE__}; |
158
|
357
|
|
|
|
|
1208
|
$self->cork; |
159
|
356
|
|
|
|
|
1420
|
1 |
160
|
357
|
100
|
|
|
|
590
|
} || do { |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# see also finish_body |
163
|
1
|
|
|
|
|
352
|
chomp( my $E = $@ ); |
164
|
1
|
50
|
|
|
|
14
|
if ( $E =~ /(OpenSSL error: .*?) at / ) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
165
|
0
|
|
|
|
|
0
|
$E = "$1"; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
elsif ( $E =~ /^(panic:.*?) at / ) { |
168
|
0
|
|
|
|
|
0
|
$E = "OpenSSL $1"; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
elsif ( $E =~ /^FATAL: (.*) at / ) { |
171
|
1
|
|
|
|
|
4
|
$E = "Ed25519 $1"; |
172
|
|
|
|
|
|
|
} |
173
|
1
|
|
|
|
|
11
|
die "$E\n"; |
174
|
|
|
|
|
|
|
}; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# check service type |
177
|
356
|
100
|
|
|
|
963
|
if ( my $s = $self->get_tag('s') ) { |
178
|
22
|
|
|
|
|
79
|
my @list = split( /:/, $s ); |
179
|
22
|
100
|
|
|
|
51
|
unless ( grep { $_ eq '*' || $_ eq 'email' } @list ) { |
|
26
|
100
|
|
|
|
135
|
|
180
|
1
|
|
|
|
|
19
|
die "does not support email\n"; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
355
|
|
|
|
|
712
|
return 1; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# check_granularity() - check whether this key matches signature identity |
188
|
|
|
|
|
|
|
# |
189
|
|
|
|
|
|
|
# a public key record can restrict what identities it may sign with, |
190
|
|
|
|
|
|
|
# g=, granularity, restricts the local part of the identity |
191
|
|
|
|
|
|
|
# t=s, restricts whether subdomains can be used |
192
|
|
|
|
|
|
|
# |
193
|
|
|
|
|
|
|
# This method returns true if the given identity is allowed by this |
194
|
|
|
|
|
|
|
# public key; it returns false otherwise. |
195
|
|
|
|
|
|
|
# If false is returned, you can check C<$@> for an explanation of |
196
|
|
|
|
|
|
|
# why. |
197
|
|
|
|
|
|
|
# |
198
|
|
|
|
|
|
|
sub check_granularity { |
199
|
352
|
|
|
352
|
0
|
569
|
my $self = shift; |
200
|
352
|
|
|
|
|
673
|
my ( $identity, $empty_g_means_wildcard ) = @_; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# check granularity |
203
|
352
|
|
|
|
|
710
|
my $g = $self->granularity; |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# yuck- what is this $empty_g_means_wildcard parameter? |
206
|
|
|
|
|
|
|
# well, it turns out that with DomainKeys signatures, |
207
|
|
|
|
|
|
|
# an empty g= is the same as g=* |
208
|
352
|
100
|
100
|
|
|
875
|
if ( $g eq '' && $empty_g_means_wildcard ) { |
209
|
1
|
|
|
|
|
3
|
$g = '*'; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# split i= value into a "local part" and a "domain part" |
213
|
352
|
|
|
|
|
615
|
my ( $local_part, $domain_part ); |
214
|
352
|
100
|
|
|
|
1107
|
if ( $identity =~ /^(.*)\@([^@]*)$/ ) { |
215
|
57
|
|
|
|
|
141
|
$local_part = $1; |
216
|
57
|
|
|
|
|
112
|
$domain_part = $2; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
else { |
219
|
295
|
|
|
|
|
421
|
$local_part = ''; |
220
|
295
|
|
|
|
|
444
|
$domain_part = $identity; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
352
|
|
|
|
|
1088
|
my ( $begins, $ends ) = split /\*/, $g, 2; |
224
|
352
|
100
|
|
|
|
707
|
if ( defined $ends ) { |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# the g= tag contains an asterisk |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# the local part must be at least as long as the pattern |
229
|
351
|
100
|
66
|
|
|
2079
|
if ( |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
230
|
|
|
|
|
|
|
length($local_part) < length($begins) + length($ends) |
231
|
|
|
|
|
|
|
or |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# the local part must begin with $begins |
234
|
|
|
|
|
|
|
substr( $local_part, 0, length($begins) ) ne $begins |
235
|
|
|
|
|
|
|
or |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# the local part must end with $ends |
238
|
|
|
|
|
|
|
( length($ends) && substr( $local_part, -length($ends) ) ne $ends ) |
239
|
|
|
|
|
|
|
) |
240
|
|
|
|
|
|
|
{ |
241
|
3
|
|
|
|
|
8
|
$@ = "granularity mismatch\n"; |
242
|
3
|
|
|
|
|
48
|
return; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
else { |
246
|
1
|
50
|
|
|
|
5
|
if ( $g eq '' ) { |
247
|
1
|
|
|
|
|
3
|
$@ = "granularity is empty\n"; |
248
|
1
|
|
|
|
|
5
|
return; |
249
|
|
|
|
|
|
|
} |
250
|
0
|
0
|
|
|
|
0
|
unless ( $local_part eq $begins ) { |
251
|
0
|
|
|
|
|
0
|
$@ = "granularity mismatch\n"; |
252
|
0
|
|
|
|
|
0
|
return; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# check subdomains |
257
|
348
|
100
|
|
|
|
804
|
if ( $self->subdomain_flag ) { |
258
|
2
|
100
|
|
|
|
16
|
unless ( lc( $domain_part ) eq lc( $self->{'Domain'} ) ) { |
259
|
1
|
|
|
|
|
4
|
$@ = "does not support signing subdomains\n"; |
260
|
1
|
|
|
|
|
5
|
return; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
347
|
|
|
|
|
1202
|
return 1; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# returns true if the actual hash algorithm used is listed by this |
268
|
|
|
|
|
|
|
# public key; dies otherwise |
269
|
|
|
|
|
|
|
# |
270
|
|
|
|
|
|
|
sub check_hash_algorithm { |
271
|
354
|
|
|
354
|
0
|
576
|
my $self = shift; |
272
|
354
|
|
|
|
|
598
|
my ($hash_algorithm) = @_; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# check hash algorithm |
275
|
354
|
100
|
|
|
|
737
|
if ( my $h = $self->get_tag('h') ) { |
276
|
14
|
|
|
|
|
42
|
my @list = split( /:/, $h ); |
277
|
14
|
100
|
|
|
|
32
|
unless ( grep { $_ eq $hash_algorithm } @list ) { |
|
38
|
|
|
|
|
97
|
|
278
|
2
|
|
|
|
|
19
|
die "does not support hash algorithm '$hash_algorithm'\n"; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
352
|
|
|
|
|
780
|
return 1; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# Create an OpenSSL public key object from the Base64-encoded data |
285
|
|
|
|
|
|
|
# found in this public key's DNS record. The OpenSSL object is saved |
286
|
|
|
|
|
|
|
# in the "cork" property. |
287
|
|
|
|
|
|
|
sub _convert_rsa { |
288
|
353
|
|
|
353
|
|
502
|
my $self = shift; |
289
|
|
|
|
|
|
|
# have to PKCS1ify the pubkey because openssl is too finicky... |
290
|
353
|
|
|
|
|
495
|
my $cert = "-----BEGIN PUBLIC KEY-----\n"; |
291
|
|
|
|
|
|
|
|
292
|
353
|
|
|
|
|
688
|
for ( my $i = 0 ; $i < length $self->data ; $i += 64 ) { |
293
|
1355
|
|
|
|
|
2287
|
$cert .= substr $self->data, $i, 64; |
294
|
1355
|
|
|
|
|
3065
|
$cert .= "\n"; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
353
|
|
|
|
|
636
|
$cert .= "-----END PUBLIC KEY-----\n"; |
298
|
|
|
|
|
|
|
|
299
|
353
|
50
|
|
|
|
10383
|
my $cork = Crypt::OpenSSL::RSA->new_public_key($cert) |
300
|
|
|
|
|
|
|
or die 'unable to generate public key object'; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# segfaults on my machine |
303
|
|
|
|
|
|
|
# $cork->check_key or |
304
|
|
|
|
|
|
|
# return; |
305
|
|
|
|
|
|
|
|
306
|
353
|
|
|
|
|
17201
|
$self->cork($cork); |
307
|
353
|
|
|
|
|
738
|
return 1; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub _convert_ed25519 { |
311
|
4
|
|
|
4
|
|
8
|
my $self = shift; |
312
|
4
|
50
|
|
|
|
36
|
my $cork = Crypt::PK::Ed25519->new |
313
|
|
|
|
|
|
|
or die 'unable to generate Ed25519 public key object'; |
314
|
|
|
|
|
|
|
|
315
|
4
|
|
|
|
|
299
|
my $keybin = decode_base64($self->data); |
316
|
4
|
50
|
|
|
|
18
|
$cork->import_key_raw($keybin, 'public') |
317
|
|
|
|
|
|
|
or die 'failed to load Ed25519 public key'; |
318
|
|
|
|
|
|
|
|
319
|
3
|
|
|
|
|
80
|
$self->cork($cork); |
320
|
3
|
|
|
|
|
6
|
return 1; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
sub convert { |
324
|
357
|
|
|
357
|
0
|
502
|
my $self = shift; |
325
|
|
|
|
|
|
|
|
326
|
357
|
|
|
|
|
695
|
my $k_tag = $self->get_tag('k'); |
327
|
357
|
100
|
|
|
|
815
|
$k_tag = 'rsa' unless defined $k_tag; |
328
|
|
|
|
|
|
|
|
329
|
357
|
50
|
|
|
|
626
|
$self->data |
330
|
|
|
|
|
|
|
or return; |
331
|
|
|
|
|
|
|
|
332
|
357
|
100
|
|
|
|
1142
|
return $self->_convert_rsa if $k_tag eq 'rsa'; |
333
|
4
|
50
|
|
|
|
78
|
return $self->_convert_ed25519 if $k_tag eq 'ed25519'; |
334
|
0
|
|
|
|
|
0
|
die 'unsupported key type'; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub verify { |
338
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
339
|
0
|
|
|
|
|
0
|
my %prms = @_; |
340
|
|
|
|
|
|
|
|
341
|
0
|
|
|
|
|
0
|
my $rtrn; |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
eval { |
344
|
0
|
|
|
|
|
0
|
local $SIG{__DIE__}; |
345
|
0
|
|
|
|
|
0
|
$rtrn = $self->cork->verify( $prms{'Text'}, $prms{'Signature'} ); |
346
|
0
|
|
|
|
|
0
|
1 |
347
|
0
|
0
|
|
|
|
0
|
} || do { |
348
|
0
|
|
|
|
|
0
|
$self->errorstr($@); |
349
|
0
|
|
|
|
|
0
|
return; |
350
|
|
|
|
|
|
|
}; |
351
|
|
|
|
|
|
|
|
352
|
0
|
|
|
|
|
0
|
return $rtrn; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub granularity { |
357
|
711
|
|
|
711
|
1
|
1005
|
my $self = shift; |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# set new granularity if provided |
360
|
711
|
50
|
|
|
|
1453
|
(@_) |
361
|
|
|
|
|
|
|
and $self->set_tag( 'g', shift ); |
362
|
|
|
|
|
|
|
|
363
|
711
|
|
|
|
|
1497
|
my $g = $self->get_tag('g'); |
364
|
711
|
100
|
|
|
|
1435
|
if ( defined $g ) { |
365
|
44
|
|
|
|
|
92
|
return $g; |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
else { |
368
|
667
|
|
|
|
|
1248
|
return '*'; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
sub notes { |
373
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
374
|
|
|
|
|
|
|
|
375
|
0
|
0
|
|
|
|
0
|
(@_) |
376
|
|
|
|
|
|
|
and $self->set_tag( 'n', shift ); |
377
|
|
|
|
|
|
|
|
378
|
0
|
|
|
|
|
0
|
return $self->get_tag('n'); |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub data { |
382
|
3782
|
|
|
3782
|
0
|
4991
|
my $self = shift; |
383
|
|
|
|
|
|
|
|
384
|
3782
|
50
|
|
|
|
6425
|
(@_) |
385
|
|
|
|
|
|
|
and $self->set_tag( 'p', shift ); |
386
|
|
|
|
|
|
|
|
387
|
3782
|
|
|
|
|
7027
|
my $p = $self->get_tag('p'); |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# remove whitespace (actually only LWSP is allowed) and double quote (long string delimiter) |
390
|
3782
|
50
|
|
|
|
10369
|
$p =~ tr/\015\012 \t"//d if defined $p; |
391
|
3782
|
|
|
|
|
8929
|
return $p; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub flags { |
395
|
348
|
|
|
348
|
0
|
516
|
my $self = shift; |
396
|
|
|
|
|
|
|
|
397
|
348
|
50
|
|
|
|
676
|
(@_) |
398
|
|
|
|
|
|
|
and $self->set_tag( 't', shift ); |
399
|
|
|
|
|
|
|
|
400
|
348
|
|
100
|
|
|
812
|
return $self->get_tag('t') || ''; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# subdomain_flag() - checks whether "s" is specified in flags |
404
|
|
|
|
|
|
|
# |
405
|
|
|
|
|
|
|
# returns true if "s" is found, false otherwise |
406
|
|
|
|
|
|
|
# |
407
|
|
|
|
|
|
|
sub subdomain_flag { |
408
|
348
|
|
|
348
|
0
|
602
|
my $self = shift; |
409
|
348
|
|
|
|
|
676
|
my @flags = split /:/, $self->flags; |
410
|
348
|
|
|
|
|
1009
|
return grep { $_ eq 's' } @flags; |
|
20
|
|
|
|
|
75
|
|
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
sub revoked { |
414
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
415
|
|
|
|
|
|
|
|
416
|
0
|
0
|
|
|
|
0
|
$self->data |
417
|
|
|
|
|
|
|
or return 1; |
418
|
|
|
|
|
|
|
|
419
|
0
|
|
|
|
|
0
|
return; |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub testing { |
423
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
424
|
|
|
|
|
|
|
|
425
|
0
|
|
|
|
|
0
|
my $flags = $self->flags; |
426
|
0
|
|
|
|
|
0
|
my @flaglist = split( /:/, $flags ); |
427
|
0
|
0
|
|
|
|
0
|
if ( grep { $_ eq 'y' } @flaglist ) { |
|
0
|
|
|
|
|
0
|
|
428
|
0
|
|
|
|
|
0
|
return 1; |
429
|
|
|
|
|
|
|
} |
430
|
0
|
|
|
|
|
0
|
return undef; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub verify_sha1_digest { |
434
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
435
|
0
|
|
|
|
|
0
|
my ( $digest, $signature ) = @_; |
436
|
0
|
|
|
|
|
0
|
return $self->verify_digest( 'SHA-1', $digest, $signature ); |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub _verify_digest_rsa { |
440
|
341
|
|
|
341
|
|
534
|
my $self = shift; |
441
|
341
|
|
|
|
|
602
|
my ( $digest_algorithm, $digest, $signature ) = @_; |
442
|
|
|
|
|
|
|
|
443
|
341
|
|
|
|
|
720
|
my $rsa_pub = $self->cork; |
444
|
341
|
50
|
|
|
|
680
|
if ( !$rsa_pub ) { |
445
|
0
|
0
|
|
|
|
0
|
$@ = $@ ne '' ? "RSA failed: $@" : 'RSA unknown problem'; |
446
|
0
|
|
|
|
|
0
|
$@ .= ", s=$self->{Selector} d=$self->{Domain}"; |
447
|
0
|
|
|
|
|
0
|
return; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
341
|
|
|
|
|
963
|
$rsa_pub->use_no_padding; |
451
|
341
|
|
|
|
|
14126
|
my $verify_result = $rsa_pub->encrypt($signature); |
452
|
|
|
|
|
|
|
|
453
|
333
|
|
|
|
|
1068
|
my $k = $rsa_pub->size; |
454
|
333
|
|
|
|
|
891
|
my $expected = calculate_EM( $digest_algorithm, $digest, $k ); |
455
|
333
|
100
|
|
|
|
1331
|
return 1 if ( $verify_result eq $expected ); |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
# well, the RSA verification failed; I wonder if the RSA signing |
458
|
|
|
|
|
|
|
# was performed on a different digest value? I think we can check... |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# basically, if the $verify_result has the same prefix as $expected, |
461
|
|
|
|
|
|
|
# then only the digest was different |
462
|
|
|
|
|
|
|
|
463
|
63
|
|
|
|
|
111
|
my $digest_len = length $digest; |
464
|
63
|
|
|
|
|
115
|
my $prefix_len = length($expected) - $digest_len; |
465
|
63
|
100
|
|
|
|
200
|
if ( |
466
|
|
|
|
|
|
|
substr( $verify_result, 0, $prefix_len ) eq |
467
|
|
|
|
|
|
|
substr( $expected, 0, $prefix_len ) ) |
468
|
|
|
|
|
|
|
{ |
469
|
60
|
|
|
|
|
99
|
$@ = 'message has been altered'; |
470
|
60
|
|
|
|
|
327
|
return; |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
3
|
|
|
|
|
18
|
$@ = 'bad RSA signature'; |
474
|
3
|
|
|
|
|
20
|
return; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub _verify_digest_ed25519 { |
478
|
3
|
|
|
3
|
|
5
|
my $self = shift; |
479
|
3
|
|
|
|
|
7
|
my ( $digest_algorithm, $digest, $signature ) = @_; |
480
|
|
|
|
|
|
|
|
481
|
3
|
|
|
|
|
10
|
my $ed = $self->cork; |
482
|
3
|
50
|
|
|
|
9
|
if ( !$ed ) { |
483
|
0
|
0
|
|
|
|
0
|
$@ = $@ ne '' ? "Ed25519 failed: $@" : 'Ed25519 unknown problem'; |
484
|
0
|
|
|
|
|
0
|
$@ .= ", s=$self->{Selector} d=$self->{Domain}"; |
485
|
0
|
|
|
|
|
0
|
return; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
3
|
|
|
|
|
19320
|
my $verify_result = $ed->verify_message($signature, $digest); |
489
|
3
|
100
|
|
|
|
41
|
return $verify_result if ($verify_result == 1); |
490
|
|
|
|
|
|
|
|
491
|
1
|
|
|
|
|
5
|
$@ = 'bad Ed25519 signature'; |
492
|
1
|
|
|
|
|
11
|
return; |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
# verify_digest() - returns true if the digest verifies, false otherwise |
496
|
|
|
|
|
|
|
# |
497
|
|
|
|
|
|
|
# if false, $@ is set to a description of the problem |
498
|
|
|
|
|
|
|
# |
499
|
|
|
|
|
|
|
sub verify_digest { |
500
|
344
|
|
|
344
|
0
|
561
|
my $self = shift; |
501
|
344
|
|
|
|
|
713
|
my ( $digest_algorithm, $digest, $signature ) = @_; |
502
|
|
|
|
|
|
|
|
503
|
344
|
|
100
|
|
|
695
|
my $k_tag = $self->get_tag('k') || 'rsa'; |
504
|
|
|
|
|
|
|
|
505
|
344
|
100
|
|
|
|
1131
|
return $self->_verify_digest_rsa($digest_algorithm, $digest, $signature) if $k_tag eq 'rsa'; |
506
|
3
|
50
|
|
|
|
17
|
return $self->_verify_digest_ed25519($digest_algorithm, $digest, $signature) if $k_tag eq 'ed25519'; |
507
|
0
|
|
|
|
|
|
$@ = 'unsupported key type'; |
508
|
0
|
|
|
|
|
|
return; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
1; |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
__END__ |