File Coverage

blib/lib/Authen/WebAuthn/SSLeayChainVerifier.pm
Criterion Covered Total %
statement 85 102 83.3
branch 27 46 58.7
condition n/a
subroutine 10 10 100.0
pod 0 2 0.0
total 122 160 76.2


line stmt bran cond sub pod time code
1             package Authen::WebAuthn::SSLeayChainVerifier;
2             $Authen::WebAuthn::SSLeayChainVerifier::VERSION = '0.005';
3 5     5   153856 use warnings;
  5         18  
  5         333  
4 5     5   26 use strict;
  5         17  
  5         165  
5 5     5   3605 use Net::SSLeay 1.88;
  5         70149  
  5         5374  
6             Net::SSLeay::initialize();
7              
8             our $verification_time;
9              
10             # Parse PEM data into a X509 structure
11             # The returned structure must be freed
12             sub getX509 {
13 26     26 0 65 my ( $data, $is_pem ) = @_;
14              
15 26         89 my $method = Net::SSLeay::BIO_s_mem();
16 26 50       73 die "Could not resolve BIO_s_mem" unless $method;
17 26         145 my $bio = Net::SSLeay::BIO_new($method);
18 26 50       62 if ($bio) {
19 26         163 my $rv = Net::SSLeay::BIO_write( $bio, $data );
20 26 50       61 if ( $rv > 0 ) {
21 26         45 my $x509;
22 26 100       55 if ($is_pem) {
23 10         5754 $x509 = Net::SSLeay::PEM_read_bio_X509($bio);
24             }
25             else {
26 16         10716 $x509 = Net::SSLeay::d2i_X509_bio($bio);
27             }
28 26         160 Net::SSLeay::BIO_free($bio);
29              
30 26 50       81 if ( $x509 != 0 ) {
31 26         92 return $x509;
32             }
33             else {
34 0         0 die "Could not parse certificate: "
35             . Net::SSLeay::ERR_error_string(
36             Net::SSLeay::ERR_get_error() );
37 0         0 Net::SSLeay::ERR_clear_error();
38             }
39             }
40             else {
41 0         0 Net::SSLeay::BIO_free($bio);
42 0         0 die "Could not copy certificate to BIO";
43             }
44             }
45             else {
46 0 0       0 die "Could not allocate new BIO" unless $bio;
47             }
48              
49             }
50              
51             # Create a trust store and populate it with the provided list
52             # The return value must be freed by the caller
53             sub _get_trust_store {
54 12     12   35 my @x509_list = @_;
55              
56 12         92 my $x509_store = Net::SSLeay::X509_STORE_new();
57 12 50       42 if ( $x509_store != 0 ) {
58 12         32 for my $x509 (@x509_list) {
59 10         118 my $rv = Net::SSLeay::X509_STORE_add_cert( $x509_store, $x509 );
60              
61 10 50       38 if ( $rv == 0 ) {
62 0         0 Net::SSLeay::X509_STORE_free($x509_store);
63 0         0 die "Could not add certificate to store";
64             }
65             }
66              
67 12 50       38 if ($verification_time) {
68 12         56 my $pm = Net::SSLeay::X509_VERIFY_PARAM_new();
69 12         66 Net::SSLeay::X509_VERIFY_PARAM_set_time( $pm, $verification_time );
70 12         56 Net::SSLeay::X509_STORE_set1_param( $x509_store, $pm );
71 12         42 Net::SSLeay::X509_VERIFY_PARAM_free($pm);
72             }
73              
74 12         31 return $x509_store;
75              
76             }
77             else {
78 0         0 die "Could not allocate new trust store";
79             }
80             }
81              
82             # The result must be freed with _free_x509_list
83             sub _get_x509_list {
84 24     24   66 my ( $list, $is_pem ) = @_;
85              
86 24         38 my @result;
87              
88 24         63 for my $pem (@$list) {
89 14         29 my $x509 = eval { getX509( $pem, $is_pem ) };
  14         29  
90              
91 14 50       50 if ($@) {
92              
93             # Release already allocated list
94 0         0 _free_x509_list(@result);
95 0         0 die $@;
96             }
97 14         41 push @result, $x509;
98             }
99 24         68 return @result;
100             }
101              
102             sub _free_x509_list {
103 12     12   40 my (@list) = @_;
104 12         83 Net::SSLeay::X509_free($_) for @list;
105             }
106              
107             sub _get_stack {
108 12     12   30 my @x509_list = @_;
109              
110 12         42 my $stack = Net::SSLeay::sk_X509_new_null();
111 12 50       30 if ($stack) {
112 12         30 for my $x509 (@x509_list) {
113 4         10 my $rv = Net::SSLeay::sk_X509_push( $stack, $x509 );
114 4 50       11 if ( $rv == 0 ) {
115 0         0 Net::SSLeay::sk_X509_free($stack);
116 0         0 die "Could not add certificate to stack";
117             }
118             }
119 12         26 return $stack;
120             }
121             else {
122 0         0 die "Cannot allocate X509 stack";
123             }
124             }
125              
126             sub _get_context {
127 12     12   32 my ( $trust_store, $to_verify, $chain ) = @_;
128              
129 12         43 my $x509_store_ctx = Net::SSLeay::X509_STORE_CTX_new;
130 12 50       33 if ( $x509_store_ctx != 0 ) {
131 12         84 my $rv =
132             Net::SSLeay::X509_STORE_CTX_init( $x509_store_ctx, $trust_store,
133             $to_verify, $chain );
134              
135             # Old versions of Net::SSLeay don't provide a return code
136 12 50       66 if ( $Net::SSLeay::VERSION < '1.91' ) {
137 0         0 $rv = 1;
138             }
139              
140 12 50       35 if ( $rv != 0 ) {
141 12         26 return $x509_store_ctx;
142             }
143             else {
144 0         0 Net::SSLeay::X509_STORE_CTX_free($x509_store_ctx);
145 0         0 die "Cannot initialize X509 store context";
146             }
147             }
148             else {
149 0         0 die "Cannot allocate X509 store context";
150             }
151             }
152              
153             sub verify_chain {
154 12     12 0 224106 my ( $trusted_list, $target, $untrusted_list ) = @_;
155              
156 12         32 my ( $to_verify, @trusted, @untrusted, $trust_store, $chain, $context );
157 12         46 my $result = { result => 0 };
158              
159             # Catch any dies so we can deallocate resources used by this function
160 12         27 eval {
161              
162             # Allocations are made here
163 12         51 $to_verify = getX509($target);
164 12         76 @trusted = _get_x509_list( $trusted_list, 1 );
165 12         29 @untrusted = _get_x509_list($untrusted_list);
166 12         40 $trust_store = _get_trust_store(@trusted);
167 12         41 $chain = _get_stack(@untrusted);
168 12         36 $context = _get_context( $trust_store, $to_verify, $chain );
169              
170 12         4970 my $rv = Net::SSLeay::X509_verify_cert($context);
171 12         60 my $error = Net::SSLeay::X509_STORE_CTX_get_error($context);
172 12         69 Net::SSLeay::ERR_clear_error();
173 12 100       110 if ( $rv == 1 ) {
174 5         36 $result = { result => 1 };
175             }
176             else {
177 7         142 die( "Could not verify X.509 chain: "
178             . Net::SSLeay::X509_verify_cert_error_string($error) );
179             }
180             };
181 12         56 my $eval_result = $@;
182              
183             # Deallocate everything we used
184 12 50       87 Net::SSLeay::X509_free($to_verify) if $to_verify;
185 12 100       100 _free_x509_list(@trusted) if @trusted;
186 12 100       35 _free_x509_list(@untrusted) if @untrusted;
187 12 50       116 Net::SSLeay::X509_STORE_free($trust_store) if $trust_store;
188 12 50       95 Net::SSLeay::sk_X509_free($chain) if $chain;
189 12 50       289 Net::SSLeay::X509_STORE_CTX_free($context) if $context;
190              
191 12 100       38 if ($eval_result) {
192 7         69 return { result => 0, message => "$eval_result" };
193             }
194 5         27 return $result;
195              
196             }
197              
198             1;