File Coverage

blib/lib/Net/DRI/Protocol/EPP/Extensions/ICANN/MarkSignedMark.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             ## Domain Registry Interface, Mark & Signed Mark for EPP (draft-ietf-eppext-tmch-smd-01)
2             ##
3             ## Copyright (c) 2013-2015 Patrick Mevzek . All rights reserved.
4             ##
5             ## This file is part of Net::DRI
6             ##
7             ## Net::DRI is free software; you can redistribute it and/or modify
8             ## it under the terms of the GNU General Public License as published by
9             ## the Free Software Foundation; either version 2 of the License, or
10             ## (at your option) any later version.
11             ##
12             ## See the LICENSE file that comes with this distribution for more details.
13             ####################################################################################################
14              
15             package Net::DRI::Protocol::EPP::Extensions::ICANN::MarkSignedMark;
16              
17 1     1   1149 use strict;
  1         2  
  1         30  
18 1     1   4 use warnings;
  1         1  
  1         20  
19              
20 1     1   4 use Net::DRI::Util;
  1         1  
  1         15  
21 1     1   4 use Net::DRI::Exception;
  1         1  
  1         15  
22 1     1   4 use Net::DRI::Protocol::EPP::Util;
  1         8  
  1         18  
23              
24 1     1   208 use XML::LibXML ();
  0            
  0            
25             use Encode;
26              
27             ####################################################################################################
28              
29             sub setup
30             {
31             my ($class,$po,$version)=@_;
32             $po->ns({ 'mark' => [ 'urn:ietf:params:xml:ns:mark-1.0','mark-1.0.xsd' ],
33             'signedMark' => [ 'urn:ietf:params:xml:ns:signedMark-1.0','signedMark-1.0'] });
34             return;
35             }
36              
37             sub implements { return 'https://tools.ietf.org/html/draft-ietf-eppext-tmch-smd-01'; }
38              
39             my %xml2perl = ( trademark => 'trademark',
40             treatyOrStatute => 'treaty_statute',
41             court => 'court',
42             markName => 'mark_name',
43             goodsAndServices=> 'goods_services',
44             apId => 'application_id',
45             apDate => 'application_date',
46             regNum => 'registration_number',
47             regDate => 'registration_date',
48             exDate => 'expiration_date',
49             refNum => 'reference_number',
50             proDate => 'protection_date',
51             execDate => 'execution_date',
52             courtName => 'court_name',
53             );
54              
55             ####################################################################################################
56              
57             sub build_marks
58             {
59             my ($po,$rd)=@_;
60             Net::DRI::Exception::usererr_invalid_parameters('A ref array must be passed for marks, or a standalone ref hash for only one mark') unless defined $rd && (ref $rd eq 'ARRAY' || ref $rd eq 'HASH');
61             my @r;
62             foreach my $m (ref $rd eq 'ARRAY' ? @$rd : $rd)
63             {
64             push @r,['mark:mark',{ 'xmlns:mark' => $po->ns()->{'mark'}->[0]},build_mark($m)];
65             }
66             return @r;
67             }
68              
69             sub build_mark
70             {
71             my ($rd)=@_;
72             my @r;
73              
74             Net::DRI::Exception::usererr_invalid_parameters() unless defined $rd && ref $rd eq 'HASH';
75             my $type=$rd->{type};
76             $type='' unless defined $type;
77              
78             if ($type eq 'trademark' || exists $rd->{jurisdiction})
79             {
80             return _build_trademark($rd);
81             } elsif ($type eq 'treaty_statute' || exists $rd->{protection})
82             {
83             return _build_treaty($rd);
84             } elsif ($type eq 'court' || exists $rd->{court_name})
85             {
86             return _build_court($rd);
87             } else
88             {
89             Net::DRI::Exception::usererr_invalid_parameters(qq{Unrecognized type "$type" of mark, and no "jurisdiction", "protection" or "court_name" element});
90             }
91             return;
92             }
93              
94             sub _build_addr
95             {
96             my ($contact)=@_;
97             my (@r,$v);
98              
99             $v=scalar $contact->street();
100             Net::DRI::Exception::usererr_insufficient_parameters('Contact address must have from 1 to 3 street elements') unless defined $v && ref $v eq 'ARRAY' && @$v >=1 && @$v <= 3;
101             push @r,map { ['mark:street',$_] } @$v;
102              
103             $v=scalar $contact->city();
104             Net::DRI::Exception::usererr_insufficient_parameters('Contact address must have a city') unless defined $v;
105             Net::DRI::Exception::usererr_invalid_parameters('Contact address city must be an XML token string') unless Net::DRI::Util::xml_is_token($v);
106             push @r,['mark:city',$v];
107              
108             $v=scalar $contact->sp();
109             if (defined $v && length $v)
110             {
111             Net::DRI::Exception::usererr_invalid_parameters('Contact address sp must be an XML token string') unless Net::DRI::Util::xml_is_token($v);
112             push @r,['mark:sp',$v];
113             }
114              
115             $v=scalar $contact->pc();
116             if (defined $v && length $v)
117             {
118             Net::DRI::Exception::usererr_invalid_parameters('Contact address pc must be an XML token string with 16 characters or less') unless Net::DRI::Util::xml_is_token($v,0,16);
119             push @r,['mark:pc',$v];
120             }
121              
122             $v=scalar $contact->cc();
123             Net::DRI::Exception::usererr_insufficient_parameters('Contact address must have a cc') unless defined $v;
124             Net::DRI::Exception::usererr_invalid_parameters('Contact address cc must be an XML token string of 2 characters') unless Net::DRI::Util::xml_is_token($v,2,2);
125             push @r,['mark:cc',$v];
126              
127             return @r;
128             }
129              
130             sub _build_contact
131             {
132             my ($type,$contact)=@_;
133              
134             Net::DRI::Exception::usererr_invalid_parameters('Element must be contact object, not: '.$contact) unless Net::DRI::Util::isa_contact($contact);
135              
136             my (@r,$v);
137              
138             $v=scalar $contact->name();
139             if (defined $v && length $v)
140             {
141             Net::DRI::Exception::usererr_invalid_parameters('Name of contact must be an XML token string, not: '.$v) unless Net::DRI::Util::xml_is_token($v);
142             push @r,['mark:name',$v];
143             } else
144             {
145             Net::DRI::Exception::usererr_insufficient_parameters('Name is mandatory for a contact') if ($type eq 'contact');
146             }
147              
148             $v=scalar $contact->org();
149             if (defined $v && length $v)
150             {
151             Net::DRI::Exception::usererr_invalid_parameters('Org of contact must be an XML token string, not: '.$v) unless Net::DRI::Util::xml_is_token($v);
152             push @r,['mark:org',$v];
153             }
154              
155             push @r,['mark:addr',_build_addr($contact)];
156              
157             $v=$contact->voice();
158             if (defined $v && length $v)
159             {
160             Net::DRI::Exception::usererr_invalid_parameters('Voice of contact must be an XML token string verifying pattern "(\+[0-9]{1,3}\.[0-9]{1,14})?"') unless Net::DRI::Util::xml_is_token($v,0,17) && $v=~m/^\+[0-9]{1,3}\.[0-9]{1,14}$/;
161             push @r,Net::DRI::Protocol::EPP::Util::build_tel('mark:voice',$v);
162             } else
163             {
164             Net::DRI::Exception::usererr_insufficient_parameters('Voice is mandatory for a contact') if ($type eq 'contact');
165             }
166              
167             $v=$contact->fax();
168             if (defined $v && length $v)
169             {
170             Net::DRI::Exception::usererr_invalid_parameters('Fax of contact must be an XML token string verifying pattern "(\+[0-9]{1,3}\.[0-9]{1,14})?"') unless Net::DRI::Util::xml_is_token($v,0,17) && $v=~m/^\+[0-9]{1,3}\.[0-9]{1,14}$/;
171             push @r,Net::DRI::Protocol::EPP::Util::build_tel('mark:fax',$v);
172             }
173              
174             $v=$contact->email();
175             if (defined $v && length $v)
176             {
177             Net::DRI::Exception::usererr_invalid_parameters('Email of contact must be an XML token string with at least 1 character, not: '.$v) unless Net::DRI::Util::xml_is_token($v,1);
178             push @r,['mark:email',$v];
179             } else
180             {
181             Net::DRI::Exception::usererr_insufficient_parameters('Email is mandatory for a contact') if ($type eq 'contact');
182             }
183              
184             return @r;
185             }
186              
187             sub _add_token
188             {
189             my ($rd,$key,$optional)=@_;
190             my $pkey=exists $xml2perl{$key} ? $xml2perl{$key} : $key;
191             if (Net::DRI::Util::has_key($rd,$pkey))
192             {
193             Net::DRI::Exception::usererr_invalid_parameters(qq{Value for "$pkey" key must be an XML token string}) unless Net::DRI::Util::xml_is_token($rd->{$pkey});
194             return ['mark:'.$key,$rd->{$pkey}];
195             } else
196             {
197             Net::DRI::Exception::usererr_insufficient_parameters(qq{"$pkey" key must exist}) unless (defined $optional && $optional);
198             return;
199             }
200             }
201              
202             sub _add_datetime
203             {
204             my ($rd,$key,$optional)=@_;
205             my $pkey=exists $xml2perl{$key} ? $xml2perl{$key} : $key;
206             if (Net::DRI::Util::has_key($rd,$pkey))
207             {
208             Net::DRI::Exception::usererr_invalid_parameters(qq{Value for "$pkey" key must be a DateTime object}) unless Net::DRI::Util::is_class($rd->{$pkey},'DateTime');
209             return ['mark:'.$key,Net::DRI::Util::dto2zstring($rd->{$pkey})];
210             } else
211             {
212             Net::DRI::Exception::usererr_insufficient_parameters(qq{"$pkey" key must exist}) unless (defined $optional && $optional);
213             return;
214             }
215             }
216              
217             sub _build_common1
218             {
219             my ($rd)=@_;
220             my @r;
221              
222             push @r,_add_token($rd,'id');
223             Net::DRI::Exception::usererr_invalid_parameters('Value for "id" key must match pattern "\d+-\d+"') unless $rd->{id}=~m/^\d+-\d+$/;
224              
225             push @r,_add_token($rd,'markName');
226              
227             Net::DRI::Exception::usererr_insufficient_parameters('"contact" key must exist') unless Net::DRI::Util::has_key($rd,'contact');
228             Net::DRI::Exception::usererr_invalid_parameters('Value for "contact" key must be a ContactSet object') unless Net::DRI::Util::isa_contactset($rd->{contact});
229             Net::DRI::Exception::usererr_insufficient_parameters('Value for "contact" key must have at least one contact of type holder_owner, holder_assignee or holder_licensee') unless grep { /^(?:holder_owner|holder_assignee|holder_licensee)$/ } $rd->{contact}->types();
230             foreach my $type (qw/owner assignee licensee/)
231             {
232             my @o=$rd->{contact}->get('holder_'.$type);
233             next unless @o;
234             foreach my $c (@o)
235             {
236             push @r,['mark:holder',{ entitlement => $type },_build_contact('holder',$c)];
237             }
238             }
239             foreach my $type (qw/owner agent thirdparty/)
240             {
241             my @o=$rd->{contact}->get('contact_'.$type);
242             next unless @o;
243             foreach my $c (@o)
244             {
245             push @r,['mark:contact',{ type => $type },_build_contact('contact',$c)];
246             }
247             }
248              
249             return @r;
250             }
251              
252             sub _build_common2
253             {
254             my ($rd)=@_;
255             my @r;
256              
257             if (Net::DRI::Util::has_key($rd,'label'))
258             {
259             foreach my $label (ref $rd->{label} eq 'ARRAY' ? @{$rd->{label}} : ($rd->{label}))
260             {
261             Net::DRI::Exception::usererr_invalid_parameters(qq{Label "$label" must be an XML token string from 1 to 63 characters}) unless Net::DRI::Util::xml_is_token($label,1,63);
262             Net::DRI::Exception::usererr_invalid_parameters(qq{Label "$label" must pass regex "[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?"}) unless $label=~m/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
263             push @r,['mark:label',$label];
264             }
265             }
266              
267             push @r,_add_token($rd,'goodsAndServices');
268              
269             return @r;
270             }
271              
272             sub _build_common3
273             {
274             my ($rd)=@_;
275             my @r;
276              
277             push @r,_add_token($rd,'refNum');
278             push @r,_add_datetime($rd,'proDate');
279              
280             return @r;
281             }
282              
283             sub _build_trademark
284             {
285             my ($rd)=@_;
286             my @r;
287              
288             push @r,_build_common1($rd); ## id/markName/holder/contact
289            
290             push @r,_add_token($rd,'jurisdiction');
291             Net::DRI::Exception::usererr_invalid_parameters(qq{Value for "jurisdiction" key must be an XML token string of 2 characters}) unless Net::DRI::Util::xml_is_token($rd->{jurisdiction},2,2);
292              
293             if (Net::DRI::Util::has_key($rd,'class'))
294             {
295             foreach my $class (ref $rd->{class} eq 'ARRAY' ? @{$rd->{class}} : ($rd->{class}))
296             {
297             Net::DRI::Exception::usererr_invalid_parameters('Class must be an integer, not: '.$class) unless $class=~m/^\d+$/;
298             push @r,['mark:class',$class];
299             }
300             }
301              
302             push @r,_build_common2($rd); ## label/goodsAndServices
303              
304             push @r,_add_token($rd,'apId',1);
305             push @r,_add_datetime($rd,'apDate',1);
306             push @r,_add_token($rd,'regNum');
307             push @r,_add_datetime($rd,'regDate');
308             push @r,_add_datetime($rd,'exDate',1);
309              
310             return ['mark:trademark',@r];
311             }
312              
313             sub _build_treaty
314             {
315             my ($rd)=@_;
316             my @r;
317              
318             push @r,_build_common1($rd); ## id/markName/holder/contact
319              
320             Net::DRI::Exception::usererr_insufficient_parameters('Key "protection" must exist') unless Net::DRI::Util::has_key($rd,'protection');
321             foreach my $rprot (ref $rd->{protection} eq 'ARRAY' ? @{$rd->{protection}} : ($rd->{protection}))
322             {
323             my @pro;
324             Net::DRI::Exception::usererr_invalid_parameters('Each protection item must be a ref hash, not: '.$rprot) unless ref $rprot eq 'HASH';
325              
326             push @r,_add_token($rprot,'cc');
327             Net::DRI::Exception::usererr_invalid_parameters(qq{Value for "cc" key must be an XML token string of 2 characters}) unless Net::DRI::Util::xml_is_token($rprot->{cc},2,2);
328              
329             push @r,_add_token($rprot,'region',1);
330              
331             if (Net::DRI::Util::has_key($rprot,'ruling'))
332             {
333             foreach my $ruling (ref $rprot->{ruling} eq 'ARRAY' ? @{$rprot->{ruling}} : ($rprot->{ruling}))
334             {
335             push @r,_add_token({ ruling => $ruling },'ruling');
336             Net::DRI::Exception::usererr_invalid_parameters(qq{Each "ruling" item must be an XML token string of 2 characters}) unless Net::DRI::Util::xml_is_token($ruling,2,2);
337             }
338             }
339              
340             push @r,['mark:protection',@pro];
341             }
342              
343             push @r,_build_common2($rd); ## label/goodsAndServices
344             push @r,_build_common3($rd); ## refNum/proDate
345              
346             push @r,_add_token($rd,'title');
347             push @r,_add_datetime($rd,'execDate');
348              
349             return ['mark:treatyOrStatute',@r];
350             }
351              
352             sub _build_court
353             {
354             my ($rd)=@_;
355             my @r;
356              
357             push @r,_build_common1($rd); ## id/markName/holder/contact
358             push @r,_build_common2($rd); ## label/goodsAndServices
359             push @r,_build_common3($rd); ## refNum/proDate
360              
361             push @r,_add_token($rd,'cc');
362             Net::DRI::Exception::usererr_invalid_parameters(qq{Value for "cc" key must be an XML token string of 2 characters}) unless Net::DRI::Util::xml_is_token($rd->{cc},2,2);
363              
364             if (Net::DRI::Util::has_key($rd,'region'))
365             {
366             foreach my $region (ref $rd->{region} eq 'ARRAY' ? @{$rd->{region}} : ($rd->{region}))
367             {
368             push @r,_add_token({ region => $region },'region');
369             }
370             }
371              
372             push @r,_add_token($rd,'courtName');
373              
374             return ['mark:court',@r];
375             }
376              
377             ####################################################################################################
378              
379             sub parse_tel
380             {
381             my ($node)=@_;
382             my $r=$node->textContent();
383             $r.='x'.$node->getAttribute('x') if $node->hasAttribute('x');
384             return $r;
385             }
386              
387             sub parse_contact
388             {
389             my ($po,$start)=@_;
390             my $contact=$po->create_local_object('contact');
391              
392             foreach my $el (Net::DRI::Util::xml_list_children($start))
393             {
394             my ($name,$node)=@$el;
395             if ($name=~m/^(?:name|org|email)$/)
396             {
397             $contact->$name($node->textContent());
398             } elsif ($name=~m/^(?:voice|fax)$/)
399             {
400             $contact->$name(parse_tel($node));
401             } elsif ($name eq 'addr')
402             {
403             my @street;
404             foreach my $subel (Net::DRI::Util::xml_list_children($node))
405             {
406             my ($addrname,$addrnode)=@$subel;
407             if ($addrname eq 'street')
408             {
409             push @street,$addrnode->textContent();
410             } elsif ($addrname=~m/^(?:city|sp|pc|cc)$/)
411             {
412             $contact->$addrname($addrnode->textContent());
413             }
414             }
415             $contact->street(\@street);
416             } elsif ($name=~m/^(?:voice|fax)$/)
417             {
418             $contact->$name(Net::DRI::Protocol::EPP::Util::parse_tel($node));
419             }
420             }
421              
422             return $contact;
423             }
424              
425             sub parse_mark
426             {
427             my ($po,$start)=@_;
428             my @marks;
429              
430             foreach my $el (Net::DRI::Util::xml_list_children($start))
431             {
432             my ($name,$node)=@$el;
433             if ($name=~m/^(?:trademark|treatyOrStatute|court)$/)
434             {
435             my %m=(type => $xml2perl{$name});
436             my (@class,@label,@protection,@region);
437             my $cs=$po->create_local_object('contactset');
438             foreach my $subel (Net::DRI::Util::xml_list_children($node))
439             {
440             my ($mname,$mnode)=@$subel;
441             if ($mname=~m/^(id|markName|jurisdiction|goodsAndServices|apId|regNum|refNum|title|cc|courtName)$/)
442             {
443             $m{exists $xml2perl{$mname} ? $xml2perl{$mname} : $mname}=$mnode->textContent();
444             } elsif ($mname eq 'holder')
445             {
446             my $type='holder_'.$mnode->getAttribute('entitlement'); ## owner, assignee, licensee
447             $cs->add(parse_contact($po,$mnode),$type);
448             } elsif ($mname eq 'contact')
449             {
450             my $type='contact_'.$mnode->getAttribute('type'); ## owner, agent, thirdparty
451             $cs->add(parse_contact($po,$mnode),$type);
452             } elsif ($mname eq 'class')
453             {
454             push @class,$mnode->textContent();
455             } elsif ($mname eq 'label')
456             {
457             push @label,$mnode->textContent();
458             } elsif ($mname=~m/^(?:apDate|regDate|exDate|proDate|execDate)$/)
459             {
460             $m{$xml2perl{$mname}}=$po->parse_iso8601($mnode->textContent());
461             } elsif ($mname eq 'protection')
462             {
463             my %p;
464             foreach my $pel (Net::DRI::Util::xml_list_children($mnode))
465             {
466             my ($pname,$pnode)=@$pel;
467             if ($pname=~m/^(cc|region)$/)
468             {
469             $p{$pname}=$pnode->textContent();
470             } elsif ($pname eq 'ruling')
471             {
472             push @{$p{ruling}},$pnode->textContent();
473             }
474             }
475             push @protection,\%p;
476             } elsif ($mname eq 'region')
477             {
478             push @region,$mnode->textContent();
479             }
480             }
481             $m{contact}=$cs;
482             $m{class}=\@class if @class;
483             $m{label}=\@label if @label;
484             $m{protection}=\@protection if @protection;
485             $m{region}=\@region if @region;
486             if (exists $m{goods_services})
487             {
488             $m{goods_services}=~s/\n +/ /g;
489             $m{goods_services}=~s/ +$//s;
490             }
491             push @marks,\%m;
492             }
493             }
494              
495             return \@marks;
496             }
497              
498             sub lined_content
499             {
500             my ($node,$signs,@keys)=@_;
501             my $r=Net::DRI::Util::xml_traverse($node,$signs,@keys);
502             return unless defined $r;
503             $r=$r->textContent();
504             $r=~s/\s+//g;
505             return $r;
506             }
507              
508             sub parse_signed_mark
509             {
510             my ($po,$start)=@_;
511             my %smark;
512              
513             foreach my $el (Net::DRI::Util::xml_list_children($start))
514             {
515             my ($name,$node)=@$el;
516             if ($name eq 'id')
517             {
518             $smark{id}=$node->textContent();
519             } elsif ($name eq 'issuerInfo')
520             {
521             my %issuer=(id => $node->getAttribute('issuerID'));
522             foreach my $iel (Net::DRI::Util::xml_list_children($node))
523             {
524             my ($iname,$inode)=@$iel;
525             if ($iname=~m/^(?:org|email|url)$/)
526             {
527             $issuer{$iname}=$inode->textContent();
528             } elsif ($iname eq 'voice')
529             {
530             $issuer{$iname}=parse_tel($inode);
531             }
532             }
533             $smark{issuer}=\%issuer;
534             } elsif ($name eq 'notBefore')
535             {
536             $smark{'creation_date'}=$po->parse_iso8601($node->textContent());
537             } elsif ($name eq 'notAfter')
538             {
539             $smark{'expiration_date'}=$po->parse_iso8601($node->textContent());
540             } elsif ($name eq 'mark')
541             {
542             $smark{mark}=parse_mark($po,$node);
543             } elsif ($name eq 'Signature')
544             {
545             my $signs='http://www.w3.org/2000/09/xmldsig#';
546             my %s=(id => $start->getAttribute('id'));
547             $s{'value'}=lined_content($node,$signs,qw/SignatureValue/);
548             ## TODO: handle other algorithms
549             $s{'key'}={ algorithm => 'rsa',
550             x509_certificate => lined_content($node,$signs,qw/KeyInfo X509Data X509Certificate/),
551             };
552             $s{'validated'}=_validate_xmldsig($start);
553             $smark{'signature'}=\%s;
554             }
555             }
556             return \%smark;
557             }
558              
559             sub _validate_xmldsig
560             {
561             my ($xml,$rs)=@_;
562              
563             require XML::LibXML::XPathContext;
564             require Digest::SHA;
565             require Crypt::OpenSSL::X509;
566             require Crypt::OpenSSL::RSA;
567             require MIME::Base64;
568              
569             my $xpc=XML::LibXML::XPathContext->new();
570             $xpc->registerNs('ds','http://www.w3.org/2000/09/xmldsig#');
571              
572             foreach my $node ($xpc->findnodes('//ds:Reference',$xml))
573             {
574             my $for=$node->getAttribute('URI');
575             $for=~s/^#//;
576             my $cnode=$xpc->findnodes("//*[\@id='${for}' or \@Id='${for}']",$xml);
577             return 0 unless $cnode->size() == 1;
578              
579             $cnode=$cnode->get_node(1); ## node on which we perform the digest operation
580              
581             my %algos=map { $_->getAttribute('Algorithm') => 1 } $xpc->findnodes('ds:Transforms/ds:Transform',$node);
582             return 0 unless exists $algos{'http://www.w3.org/2001/10/xml-exc-c14n#'};
583              
584             my $xmlstring=$cnode->toStringEC14N(0,exists $algos{'http://www.w3.org/2000/09/xmldsig#enveloped-signature'} ? q{(. | .//node() | .//@* | .//namespace::*)[not(self::comment() or ancestor-or-self::ds:Signature)]} : undef,$xpc);
585              
586             return 0 unless defined $xmlstring && $xpc->findnodes('ds:DigestValue',$node)->get_node(1)->textContent() eq _sha256b64padded($xmlstring);
587             }
588              
589             my $cert=$xpc->findnodes('//ds:X509Certificate',$xml)->get_node(1)->textContent();
590             $cert=~s/ /\n/g;
591             my $certobj=Crypt::OpenSSL::X509->new_from_string("-----BEGIN CERTIFICATE-----\n".$cert."\n-----END CERTIFICATE-----", Crypt::OpenSSL::X509::FORMAT_PEM());
592             my $key=Crypt::OpenSSL::RSA->new_public_key($certobj->pubkey());
593             $key->use_sha256_hash();
594              
595             my $xmlsi=$xpc->find('//ds:SignedInfo',$xml)->get_node(1);
596             $xmlsi->setNamespace('http://www.w3.org/2000/09/xmldsig#','ds',0);
597             my $sigval=$xpc->findnodes('//ds:SignatureValue',$xmlsi)->get_node(1)->textContent();
598             $sigval=~s!\s+!!g;
599             my $verify=$key->verify($xmlsi->toStringEC14N(0), MIME::Base64::decode_base64($sigval));
600             return (defined $verify && $verify) ? 1 : 0;
601             }
602              
603             sub _sha256b64padded
604             {
605             my ($in)=@_;
606             my $out = Digest::SHA::sha256_base64($in);
607             while (length($out) % 4) { $out .= '='; }
608             return $out;
609             }
610              
611             sub parse_encoded_signed_mark
612             {
613             my ($po,$start)=@_;
614             my $content;
615              
616             if (ref $start)
617             {
618             my $encoding=$start->hasAttribute('encoding') ? $start->getAttribute('encoding') : 'base64';
619             Net::DRI::Exception::err_invalid_parameter('For encoded signed mark, only base64 encoding is supported') unless $encoding eq 'base64';
620             $content=$start->textContent();
621             } else
622             {
623             my @a=grep { /-----BEGIN ENCODED SMD-----/ .. /-----END ENCODED SMD-----/ } split(/\n/,$start);
624             $content=join("\n",@a[1..($#a-1)]);
625             }
626              
627             require MIME::Base64;
628             my $xml=MIME::Base64::decode_base64($content);
629             $xml=Encode::decode('UTF-8',$xml,Encode::FB_CROAK | Encode::LEAVE_SRC);
630             my $root=XML::LibXML->load_xml(no_cdata => 1, no_blanks => 1, no_network => 1, string => $xml)->documentElement();
631             Net::DRI::Exception::err_invalid_parameter('Decoding should give a signedMark root element') unless $root->localname() eq 'signedMark';
632              
633             return parse_signed_mark($po,$root);
634             }
635              
636             ####################################################################################################
637             1;
638              
639             __END__