← Index
NYTProf Performance Profile   « line view »
For /usr/local/libexec/sympa/task_manager-debug.pl
  Run on Tue Jun 1 22:32:51 2021
Reported on Tue Jun 1 22:35:14 2021

Filename/usr/local/lib/perl5/site_perl/Mail/DKIM/PublicKey.pm
StatementsExecuted 0 statements in 0s
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
0000s0sMail::DKIM::PublicKey::::BEGIN@14Mail::DKIM::PublicKey::BEGIN@14
0000s0sMail::DKIM::PublicKey::::BEGIN@17Mail::DKIM::PublicKey::BEGIN@17
0000s0sMail::DKIM::PublicKey::::BEGIN@2Mail::DKIM::PublicKey::BEGIN@2
0000s0sMail::DKIM::PublicKey::::BEGIN@280Mail::DKIM::PublicKey::BEGIN@280
0000s0sMail::DKIM::PublicKey::::BEGIN@3Mail::DKIM::PublicKey::BEGIN@3
0000s0sMail::DKIM::PublicKey::::__ANON__Mail::DKIM::PublicKey::__ANON__ (xsub)
0000s0sMail::DKIM::PublicKey::::__ANON__[:104]Mail::DKIM::PublicKey::__ANON__[:104]
0000s0sMail::DKIM::PublicKey::::__ANON__[:70]Mail::DKIM::PublicKey::__ANON__[:70]
0000s0sMail::DKIM::PublicKey::::checkMail::DKIM::PublicKey::check
0000s0sMail::DKIM::PublicKey::::check_granularityMail::DKIM::PublicKey::check_granularity
0000s0sMail::DKIM::PublicKey::::check_hash_algorithmMail::DKIM::PublicKey::check_hash_algorithm
0000s0sMail::DKIM::PublicKey::::convertMail::DKIM::PublicKey::convert
0000s0sMail::DKIM::PublicKey::::dataMail::DKIM::PublicKey::data
0000s0sMail::DKIM::PublicKey::::fetchMail::DKIM::PublicKey::fetch
0000s0sMail::DKIM::PublicKey::::fetch_asyncMail::DKIM::PublicKey::fetch_async
0000s0sMail::DKIM::PublicKey::::flagsMail::DKIM::PublicKey::flags
0000s0sMail::DKIM::PublicKey::::granularityMail::DKIM::PublicKey::granularity
0000s0sMail::DKIM::PublicKey::::newMail::DKIM::PublicKey::new
0000s0sMail::DKIM::PublicKey::::notesMail::DKIM::PublicKey::notes
0000s0sMail::DKIM::PublicKey::::revokedMail::DKIM::PublicKey::revoked
0000s0sMail::DKIM::PublicKey::::subdomain_flagMail::DKIM::PublicKey::subdomain_flag
0000s0sMail::DKIM::PublicKey::::testingMail::DKIM::PublicKey::testing
0000s0sMail::DKIM::PublicKey::::verifyMail::DKIM::PublicKey::verify
0000s0sMail::DKIM::PublicKey::::verify_digestMail::DKIM::PublicKey::verify_digest
0000s0sMail::DKIM::PublicKey::::verify_sha1_digestMail::DKIM::PublicKey::verify_sha1_digest
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1package Mail::DKIM::PublicKey;
2use strict;
3use warnings;
4our $VERSION = '1.20200907'; # VERSION
5# ABSTRACT: Represents a DKIM key
6
7# Copyright 2005 Messiah College. All rights reserved.
8# Jason Long <jlong@messiah.edu>
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
14use base ( 'Mail::DKIM::KeyValueList', 'Mail::DKIM::Key' );
15*calculate_EM = \&Mail::DKIM::Key::calculate_EM;
16
17use Mail::DKIM::DNS;
18
19sub new {
20 my $type = shift;
21 my %prms = @_;
22
23 my $self = {};
24
25 $self->{'GRAN'} = $prms{'Granularity'};
26 $self->{'NOTE'} = $prms{'Note'};
27 $self->{'TEST'} = $prms{'Testing'};
28 $self->{'TYPE'} = ( $prms{'Type'} or 'rsa' );
29 $self->{'DATA'} = $prms{'Data'};
30
31 bless $self, $type;
32}
33
34
35sub fetch {
36 my $class = shift;
37 my $waiter = $class->fetch_async(@_);
38 my $self = $waiter->();
39 return $self;
40}
41
42# fetch_async() - asynchronously tries fetching a specific public key
43# using a specific protocol.
44#
45# Usage:
46# my $fut = Mail::DKIM::PublicKey->fetch_async(
47# Protocol => 'dns/txt',
48# Selector => 'selector1',
49# Domain => 'example.org',
50# Callbacks => { ... }, #optional
51# );
52#
53# # some later time
54# my $pubkey = $fut->(); # blocks until the public key is returned
55#
56sub fetch_async {
57 my $class = shift;
58 my %prms = @_;
59
60 defined( $prms{Protocol} ) && $prms{Protocol} =~ m{^dns(/txt)?$}s
61 or die "invalid/missing Protocol\n";
62
63 defined( $prms{Selector} ) && length( $prms{Selector} )
64 or die "invalid/missing Selector\n";
65
66 defined( $prms{Domain} ) && length( $prms{Domain} )
67 or die "invalid/missing Domain\n";
68
69 my %callbacks = %{ $prms{Callbacks} || {} };
70 my $on_success = $callbacks{Success} || sub { $_[0] };
71 $callbacks{Success} = sub {
72 my @resp = @_;
73 unless (@resp) {
74
75 # no requested resource records or NXDOMAIN,
76 return $on_success->();
77 }
78
79 my $strn;
80 foreach my $rr (@resp) {
81 next unless $rr->type eq 'TXT';
82
83 # join with no intervening spaces, RFC 6376
84 if ( Net::DNS->VERSION >= 0.69 ) {
85
86 # must call txtdata() in a list context
87 $strn = join '', $rr->txtdata;
88 }
89 else {
90 # char_str_list method is 'historical'
91 $strn = join '', $rr->char_str_list;
92 }
93 last;
94 }
95
96 $strn
97 or return $on_success->();
98
99 my $self = $class->parse($strn);
100 $self->{Selector} = $prms{'Selector'};
101 $self->{Domain} = $prms{'Domain'};
102 $self->check;
103 return $on_success->($self);
104 };
105
106 #
107 # perform DNS query for public key...
108 #
109 my $host = $prms{Selector} . '._domainkey.' . $prms{Domain};
110 my $waiter =
111 Mail::DKIM::DNS::query_async( $host, 'TXT', Callbacks => \%callbacks, );
112 return $waiter;
113}
114
115
116# check syntax of the public key
117# throw an error if any errors are detected
118sub check {
119 my $self = shift;
120
121 # check public key version tag
122 if ( my $v = $self->get_tag('v') ) {
123 unless ( $v eq 'DKIM1' ) {
124 die "unsupported version\n";
125 }
126 }
127
128 # check public key granularity
129 my $g = $self->granularity;
130
131 # check key type
132 if ( my $k = $self->get_tag('k') ) {
133 unless ( $k eq 'rsa' ) {
134 die "unsupported key type\n";
135 }
136 }
137
138 # check public-key data
139 my $p = $self->data;
140 if ( not defined $p ) {
141 die "missing p= tag\n";
142 }
143 if ( $p eq '' ) {
144 die "revoked\n";
145 }
146 unless ( $p =~ /^[A-Za-z0-9\+\/\=]+$/ ) {
147 die "invalid data\n";
148 }
149
150 # have OpenSSL load the key
151 eval {
152 local $SIG{__DIE__};
153 $self->cork;
154 1
155 } || do {
156
157 # see also finish_body
158 chomp( my $E = $@ );
159 if ( $E =~ /(OpenSSL error: .*?) at / ) {
160 $E = "$1";
161 }
162 elsif ( $E =~ /^(panic:.*?) at / ) {
163 $E = "OpenSSL $1";
164 }
165 die "$E\n";
166 };
167
168 # check service type
169 if ( my $s = $self->get_tag('s') ) {
170 my @list = split( /:/, $s );
171 unless ( grep { $_ eq '*' || $_ eq 'email' } @list ) {
172 die "does not support email\n";
173 }
174 }
175
176 return 1;
177}
178
179# check_granularity() - check whether this key matches signature identity
180#
181# a public key record can restrict what identities it may sign with,
182# g=, granularity, restricts the local part of the identity
183# t=s, restricts whether subdomains can be used
184#
185# This method returns true if the given identity is allowed by this
186# public key; it returns false otherwise.
187# If false is returned, you can check C<$@> for an explanation of
188# why.
189#
190sub check_granularity {
191 my $self = shift;
192 my ( $identity, $empty_g_means_wildcard ) = @_;
193
194 # check granularity
195 my $g = $self->granularity;
196
197 # yuck- what is this $empty_g_means_wildcard parameter?
198 # well, it turns out that with DomainKeys signatures,
199 # an empty g= is the same as g=*
200 if ( $g eq '' && $empty_g_means_wildcard ) {
201 $g = '*';
202 }
203
204 # split i= value into a "local part" and a "domain part"
205 my ( $local_part, $domain_part );
206 if ( $identity =~ /^(.*)\@([^@]*)$/ ) {
207 $local_part = $1;
208 $domain_part = $2;
209 }
210 else {
211 $local_part = '';
212 $domain_part = $identity;
213 }
214
215 my ( $begins, $ends ) = split /\*/, $g, 2;
216 if ( defined $ends ) {
217
218 # the g= tag contains an asterisk
219
220 # the local part must be at least as long as the pattern
221 if (
222 length($local_part) < length($begins) + length($ends)
223 or
224 substr( $local_part, 0, length($begins) ) ne $begins
225
226
227 or
228 ( length($ends) && substr( $local_part, -length($ends) ) ne $ends )
229
230
231 )
232 {
233 $@ = "granularity mismatch\n";
234 return;
235 }
236 }
237 else {
238 if ( $g eq '' ) {
239 $@ = "granularity is empty\n";
240 return;
241 }
242 unless ( $local_part eq $begins ) {
243 $@ = "granularity mismatch\n";
244 return;
245 }
246 }
247
248 # check subdomains
249 if ( $self->subdomain_flag ) {
250 unless ( lc( $domain_part ) eq lc( $self->{'Domain'} ) ) {
251 $@ = "does not support signing subdomains\n";
252 return;
253 }
254 }
255
256 return 1;
257}
258
259# returns true if the actual hash algorithm used is listed by this
260# public key; dies otherwise
261#
262sub check_hash_algorithm {
263 my $self = shift;
264 my ($hash_algorithm) = @_;
265
266 # check hash algorithm
267 if ( my $h = $self->get_tag('h') ) {
268 my @list = split( /:/, $h );
269 unless ( grep { $_ eq $hash_algorithm } @list ) {
270 die "does not support hash algorithm '$hash_algorithm'\n";
271 }
272 }
273 return 1;
274}
275
276# Create an OpenSSL public key object from the Base64-encoded data
277# found in this public key's DNS record. The OpenSSL object is saved
278# in the "cork" property.
279sub convert {
280 use Crypt::OpenSSL::RSA;
281
282 my $self = shift;
283
284 $self->data
285 or return;
286
287 # have to PKCS1ify the pubkey because openssl is too finicky...
288 my $cert = "-----BEGIN PUBLIC KEY-----\n";
289
290 for ( my $i = 0 ; $i < length $self->data ; $i += 64 ) {
291 $cert .= substr $self->data, $i, 64;
292 $cert .= "\n";
293 }
294
295 $cert .= "-----END PUBLIC KEY-----\n";
296
297 my $cork = Crypt::OpenSSL::RSA->new_public_key($cert)
298 or die 'unable to generate public key object';
299
300 # segfaults on my machine
301 # $cork->check_key or
302 # return;
303
304 $self->cork($cork);
305
306 return 1;
307}
308
309sub verify {
310 my $self = shift;
311 my %prms = @_;
312
313 my $rtrn;
314
315 eval {
316 local $SIG{__DIE__};
317 $rtrn = $self->cork->verify( $prms{'Text'}, $prms{'Signature'} );
318 1
319 } || do {
320 $self->errorstr($@);
321 return;
322 };
323
324 return $rtrn;
325}
326
327
328sub granularity {
329 my $self = shift;
330
331 # set new granularity if provided
332 (@_)
333 and $self->set_tag( 'g', shift );
334
335 my $g = $self->get_tag('g');
336 if ( defined $g ) {
337 return $g;
338 }
339 else {
340 return '*';
341 }
342}
343
344sub notes {
345 my $self = shift;
346
347 (@_)
348 and $self->set_tag( 'n', shift );
349
350 return $self->get_tag('n');
351}
352
353sub data {
354 my $self = shift;
355
356 (@_)
357 and $self->set_tag( 'p', shift );
358
359 my $p = $self->get_tag('p');
360
361 # remove whitespace (actually only LWSP is allowed) and double quote (long string delimiter)
362 $p =~ tr/\015\012 \t"//d if defined $p;
363 return $p;
364}
365
366sub flags {
367 my $self = shift;
368
369 (@_)
370 and $self->set_tag( 't', shift );
371
372 return $self->get_tag('t') || '';
373}
374
375# subdomain_flag() - checks whether "s" is specified in flags
376#
377# returns true if "s" is found, false otherwise
378#
379sub subdomain_flag {
380 my $self = shift;
381 my @flags = split /:/, $self->flags;
382 return grep { $_ eq 's' } @flags;
383}
384
385sub revoked {
386 my $self = shift;
387
388 $self->data
389 or return 1;
390
391 return;
392}
393
394sub testing {
395 my $self = shift;
396
397 my $flags = $self->flags;
398 my @flaglist = split( /:/, $flags );
399 if ( grep { $_ eq 'y' } @flaglist ) {
400 return 1;
401 }
402 return undef;
403}
404
405sub verify_sha1_digest {
406 my $self = shift;
407 my ( $digest, $signature ) = @_;
408 return $self->verify_digest( 'SHA-1', $digest, $signature );
409}
410
411# verify_digest() - returns true if the digest verifies, false otherwise
412#
413# if false, $@ is set to a description of the problem
414#
415sub verify_digest {
416 my $self = shift;
417 my ( $digest_algorithm, $digest, $signature ) = @_;
418
419 my $rsa_pub = $self->cork;
420 if ( !$rsa_pub ) {
421 $@ = $@ ne '' ? "RSA failed: $@" : 'RSA unknown problem';
422 $@ .= ", s=$self->{Selector} d=$self->{Domain}";
423 return;
424 }
425
426 $rsa_pub->use_no_padding;
427 my $verify_result = $rsa_pub->encrypt($signature);
428
429 my $k = $rsa_pub->size;
430 my $expected = calculate_EM( $digest_algorithm, $digest, $k );
431 return 1 if ( $verify_result eq $expected );
432
433 # well, the RSA verification failed; I wonder if the RSA signing
434 # was performed on a different digest value? I think we can check...
435
436 # basically, if the $verify_result has the same prefix as $expected,
437 # then only the digest was different
438
439 my $digest_len = length $digest;
440 my $prefix_len = length($expected) - $digest_len;
441 if (
442 substr( $verify_result, 0, $prefix_len ) eq
443 substr( $expected, 0, $prefix_len ) )
444 {
445 $@ = 'message has been altered';
446 return;
447 }
448
449 $@ = 'bad RSA signature';
450 return;
451}
452
4531;
454
455__END__