File Coverage

blib/lib/Crypt/PKCS11/Easy.pm
Criterion Covered Total %
statement 56 327 17.1
branch 1 124 0.8
condition 0 15 0.0
subroutine 19 49 38.7
pod 16 17 94.1
total 92 532 17.2


line stmt bran cond sub pod time code
1             package Crypt::PKCS11::Easy;
2             $Crypt::PKCS11::Easy::VERSION = '0.162150';
3             # ABSTRACT: Wrapper around Crypt::PKCS11 to make using a HSM not suck
4              
5 1     1   144417 use v5.16.3; # CentOS7
  1         2  
6 1     1   619 use Crypt::PKCS11 qw/:constant_names :constant/;
  1         52797  
  1         1776  
7 1     1   559 use Crypt::PKCS11::Attributes;
  1         186329  
  1         31  
8 1     1   531 use Log::Any '$log';
  1         8561  
  1         4  
9 1     1   3045 use Path::Tiny;
  1         5  
  1         40  
10 1     1   361 use Safe::Isa;
  1         318  
  1         100  
11 1     1   4 use Try::Tiny;
  1         1  
  1         43  
12 1     1   444 use Types::Standard qw/ArrayRef Str/;
  1         51400  
  1         10  
13 1     1   1038 use Types::Path::Tiny 'AbsFile';
  1         15400  
  1         8  
14 1     1   307 use version;
  1         1  
  1         8  
15 1     1   50 use Moo;
  1         1  
  1         6  
16 1     1   892 use namespace::clean;
  1         6671  
  1         4  
17              
18 1     1   5968 use experimental 'smartmatch';
  1         740  
  1         4  
19              
20              
21             has module => (
22             is => 'ro',
23             required => 1,
24             isa => Str,
25             );
26              
27             has _module => (
28             is => 'ro',
29             lazy => 1,
30             default => sub {
31             my $self = shift;
32              
33             # is module already a Path::Tiny object?
34             return $self->module if $self->module->$_isa('Path::Tiny');
35              
36             # does module look like a path?
37             return path($self->module)->absolute if $self->module =~ m|/|;
38              
39             # TODO care about non-linux?
40             # just a string, lets try to find a module
41             my $module_name = sprintf '%s.so', $self->module;
42             my $full_module_path;
43             for (@{$self->_module_dirs}) {
44             next unless $_->child($module_name)->is_file;
45             $full_module_path = $_->child($module_name);
46             }
47             if (!$full_module_path) {
48             die 'Unable to find a module for ' . $self->module;
49             }
50             return $full_module_path;
51             },
52              
53             isa => AbsFile,
54             );
55              
56              
57             has rw => (is => 'ro', default => 0);
58              
59              
60             has key => (is => 'ro', predicate => 1);
61              
62              
63             has function => (is => 'ro', default => 'sign');
64              
65              
66             has slot => (is => 'lazy');
67              
68              
69             has token => (is => 'ro', predicate => 1);
70              
71              
72             has pin => (is => 'ro', required => 0);
73              
74              
75             has module_dirs => (
76             is => 'ro',
77             lazy => 1,
78             isa => ArrayRef,
79             default => sub {
80             [
81             '/usr/lib64/pkcs11/', '/usr/lib/pkcs11',
82             '/usr/lib/x86_64-linux-gnu/pkcs11/'
83             ];
84             },
85             );
86              
87             has _pkcs11 => (is => 'rwp');
88              
89             has _key => (is => 'lazy');
90              
91             # to keep usage simple, only allowed one session per object
92             has _session => (is => 'lazy', predicate => 1);
93              
94             # TODO allow overriding defaults, possibly using predefined groups of related mechs
95             has _default_mech => (
96             is => 'ro',
97             default => sub {
98             {
99             digest => CKM_SHA_1,
100             encrypt => CKM_RSA_PKCS,
101             sign => CKM_SHA1_RSA_PKCS,
102             verify => CKM_SHA1_RSA_PKCS,
103              
104             };
105             },
106             );
107              
108             has _module_dirs => (
109             is => 'ro',
110             lazy => 1,
111             default => sub {
112             my $self = shift;
113             my @paths;
114             for (@{$self->module_dirs}) {
115             my $path = path($_)->absolute;
116             push @paths, $path if $path->is_dir;
117             }
118             die "No valid module paths found\n" if scalar @paths == 0;
119             return \@paths;
120             },
121             );
122              
123             has _flags => (
124             is => 'ro',
125             lazy => 1,
126             default => sub {
127             {
128             token => [
129             qw/rng write_protected login_required user_pin_initialized
130             restore_key_not_needed clock_on_token protected_authentication_path
131             dual_crypto_operations token_initialized secondary_authentication
132             user_pin_count_low user_pin_final_try user_pin_locked so_pin_count_low
133             user_pin_to_be_changed so_pin_final_try so_pin_locked so_pin_to_be_changed
134             error_state
135             /
136             ],
137             mechanism => [
138             qw/hw encrypt decrypt digest sign sign_recover verify verify_recover generate generate_key_pair wrap unwrap derive extension/
139             ],
140             slot => [qw/token_present removable_device hw_slot/],
141             };
142             },
143             );
144              
145             has [qw/_token_flags _mechanism_flags _slot_flags/] => (is => 'lazy');
146              
147             has _sig_length => (
148             is => 'ro',
149             lazy => 1,
150             default => sub {
151             {
152             1 => 20,
153             224 => 28,
154             256 => 32,
155             384 => 48,
156             512 => 64,
157             };
158             },
159             );
160              
161             sub _build__mechanism_flags {
162 0     0   0 _flags_to_hash($_[0]->_flags->{mechanism});
163             }
164              
165             sub _build__token_flags {
166 0     0   0 _flags_to_hash($_[0]->_flags->{token});
167             }
168              
169             sub _build__slot_flags {
170 0     0   0 _flags_to_hash($_[0]->_flags->{slot});
171             }
172              
173             sub BUILD {
174 4     4 0 144 my $self = shift;
175 4         11 return $self->_set__pkcs11($self->_build__pkcs11);
176             }
177              
178             sub _flags_to_hash {
179 0     0   0 my $flags = shift;
180 1     1   581 no strict 'refs'; ## no critic
  1         1  
  1         1486  
181             my %flag = map {
182 0         0 my $f = 'Crypt::PKCS11::CKF_' . uc($_);
  0         0  
183 0         0 $f->() => $_;
184             } @$flags;
185              
186 0         0 return \%flag;
187             }
188              
189             sub _build__pkcs11 {
190 4     4   1 my $self = shift;
191              
192 4         15 $log->debug('Initialising PKCS#11...');
193              
194             # Create the main PKCS #11 object, load a PKCS #11 provider .so library and initialize the module
195 4         72 my $pkcs11 = Crypt::PKCS11->new;
196              
197 4 50       124 $pkcs11->load($self->_module)
198             or die sprintf "Failed to load PKCS11 module [%s]: %s\n",
199             $self->_module, $pkcs11->errstr;
200              
201 0 0         $pkcs11->Initialize
202             or die sprintf "Failed to initialize PKCS11 module [%s]: %s\n",
203             $self->_module, $pkcs11->errstr;
204              
205 0           $log->debug("Loaded PKCS#11 module: " . $self->_module);
206              
207 0           return $pkcs11;
208             }
209              
210             sub _build__key {
211 0     0     my $self = shift;
212 0 0         if (!$self->has_key) {
213 0           die 'Tried to automagically find a key without a label';
214             }
215              
216 0           $self->login;
217              
218 0           my $tmpl = Crypt::PKCS11::Attributes->new;
219              
220 0           given ($self->function) {
221 0           return $self->get_signing_key($self->key) when 'sign';
222 0           return $self->get_verification_key($self->key) when 'verify';
223 0           return $self->get_encryption_key($self->key) when 'encrypt';
224 0           default {
225 0           die "Unknown key type: " . $self->function;
226             }
227             }
228              
229             }
230              
231             sub _build_slot {
232 0     0     my $self = shift;
233              
234             # if token is set we can try to find a slot that contains that token
235 0 0         if ($self->has_token) {
236 0           my $slot = $self->get_slot(token => $self->token);
237 0           return $slot->{id};
238             }
239              
240 0 0         my $slot_ids = $self->_pkcs11->GetSlotList(1)
241             or die 'Unable to find any available slots: ' . $self->_pkcs11->errstr;
242              
243 0 0         if (scalar @$slot_ids > 1) {
244 0           die 'There is more than one slot available, specify the one to use';
245             }
246              
247 0           return shift @$slot_ids;
248             }
249              
250             sub _build__session {
251 0     0     my $self = shift;
252              
253             # if this isn't called the Luna always gives UNKNOWN_ERROR when trying
254             # to open a session
255 0           $self->_pkcs11->CloseAllSessions($self->slot);
256              
257             # default to a ro session
258 0           my $flags;
259 0 0         if ($self->rw) {
260 0           $log->debug('Opening a RW session');
261 0           $flags = CKF_RW_SESSION | CKF_SERIAL_SESSION;
262             } else {
263 0           $log->debug('Opening a RO session');
264 0           $flags = CKF_SERIAL_SESSION;
265             }
266              
267 0 0         my $session = $self->_pkcs11->OpenSession($self->slot, $flags)
268             or die sprintf 'Error opening session on slot %s: %s', $self->slot,
269             $self->_pkcs11->errstr;
270              
271 0           $log->debug('Session opened on slot ' . $self->slot);
272 0           return $session;
273             }
274              
275             sub _clean_hash_values {
276 0     0     my $h = shift;
277              
278 0           for (keys %$h) {
279              
280 0 0         if ($_ =~ /^(firmware|hardware|library|cryptoki)Version$/) {
281 0           my $v = sprintf '%i.%i', $h->{$_}->{major}, $h->{$_}->{minor};
282 0           $h->{$_} = version->parse($v);
283 0           next;
284             }
285              
286 0 0         next if ref $h->{$_};
287              
288 0           $h->{$_} =~
289             s/\0$//; # safenet cryptoki 2.2 has some null terminated strings
290 0           $h->{$_} =~ s/\s*$//;
291 0 0         delete $h->{$_} if length $h->{$_} == 0;
292             }
293              
294 0           return;
295             }
296              
297              
298             sub get_info {
299 0     0 1   my $self = shift;
300              
301 0 0         my $info = $self->_pkcs11->GetInfo
302             or die 'Could not retrieve HSM info: ' . $self->_pkcs11->errstr;
303              
304             # according to v2.30 there are no flags and this is always 0
305 0           delete $info->{flags};
306 0           _clean_hash_values($info);
307 0           return $info;
308             }
309              
310              
311             sub get_token_info {
312 0     0 1   my ($self, $slot_id) = @_;
313              
314 0 0         my $token = $self->_pkcs11->GetTokenInfo($slot_id)
315             or die "Unable to retrive token info for slot $slot_id: "
316             . $self->_pkcs11->errstr;
317              
318 0           _clean_hash_values($token);
319              
320 0           for my $f (keys %{$self->_token_flags}) {
  0            
321             $token->{flag}->{$self->_token_flags->{$f}} =
322 0 0         ($token->{flags} & $f) ? 1 : 0;
323             }
324              
325 0           delete $token->{flags};
326              
327 0           return $token;
328             }
329              
330              
331             sub get_slot {
332 0     0 1   my ($self, %arg) = @_;
333              
334 0 0 0       unless (defined $arg{id} || defined $arg{token}) {
335 0           die 'Missing id or token';
336             }
337              
338 0           my ($slot, $slot_id);
339              
340 0 0         if (defined $arg{id}) {
    0          
341              
342 0           $log->debug("Retrieving info for slot $arg{id}");
343             $slot = $self->_pkcs11->GetSlotInfo($arg{id})
344 0 0         or die "Unable to retrieve info for slot $arg{id}: "
345             . $self->_pkcs11->errstr;
346 0           $slot_id = $arg{id};
347              
348             } elsif ($arg{token}) {
349              
350 0           $log->debug(
351             "Searching for slot containing token labelled '$arg{token}'");
352 0           my $slots = $self->get_slots(1);
353 0           for (@$slots) {
354 0 0 0       if ($_->{token}->{label} && $arg{token} eq $_->{token}->{label}) {
355 0           return $_;
356              
357             # last;
358             }
359             }
360 0 0         die "Unable to find slot containing token labelled '$arg{token}'"
361             unless $slot;
362             }
363              
364             # strip whitespace padding
365 0           _clean_hash_values($slot);
366              
367 0           $slot->{id} = $slot_id;
368 0           for my $f (keys %{$self->_slot_flags}) {
  0            
369             $slot->{flag}->{$self->_slot_flags->{$f}} =
370 0 0         ($slot->{flags} & $f) ? 1 : 0;
371             }
372              
373 0           delete $slot->{flags};
374              
375 0 0         if ($slot->{flag}->{token_present}) {
376             try {
377 0     0     $slot->{token} = $self->get_token_info($slot_id);
378             }
379             catch {
380             # there is a token present in this slot but details could not be retrieved.
381             # SoftHSM doesn't require an open session to work, but the Safenet Luna does
382             # the 2.20 docs don't show that a session is required...
383 0     0     $log->debug("Failed to access slot, trying to open a session");
384 0           my $session;
385 0 0         if ($self->_has_session) {
386 0           $session = $self->session;
387             } else {
388 0 0         $session =
389             $self->_pkcs11->OpenSession($slot_id, CKF_SERIAL_SESSION)
390             or die "Error opening session on slot $slot_id: "
391             . $self->_pkcs11->errstr;
392             }
393 0           $slot->{token} = $self->get_token_info($slot_id);
394              
395 0           $session->CloseSession;
396 0           };
397             }
398              
399 0           return $slot;
400             }
401              
402              
403             sub get_slots {
404 0     0 1   my ($self, $with_token) = @_;
405              
406 0 0         my $slot_ids = $self->_pkcs11->GetSlotList($with_token)
407             or die 'Unable to find any available slots: ' . $self->_pkcs11->errstr;
408              
409 0           my @slots;
410 0           for my $slot_id (sort @$slot_ids) {
411 0           my $slot = $self->get_slot(id => $slot_id);
412 0           push @slots, $slot;
413             }
414              
415 0           return \@slots;
416             }
417              
418              
419             sub login {
420 0     0 1   my $self = shift;
421              
422 0           my $pin;
423              
424 0           given (ref $self->pin) {
425 0           when ('CODE') {
426 0           $log->debug('Getting PIN from coderef');
427 0           $pin = $self->pin->();
428             }
429 0           when ('Path::Tiny') {
430 0           $log->debug("Reading PIN from file: " . $self->pin);
431 0           $pin = $self->pin->slurp;
432             }
433 0           default { $pin = $self->pin }
  0            
434             }
435              
436 0 0         die 'No PIN/password specified and no way to get one is set' unless $pin;
437              
438 0           chomp $pin;
439              
440 0           $log->debug('Logging in to session');
441 0 0         $self->_session->Login(CKU_USER, $pin)
442             or die "Failed to login: " . $self->_session->errstr;
443              
444 0           return;
445             }
446              
447             sub _get_key {
448 0     0     my ($self, $label, $tmpl) = @_;
449              
450 0           $log->debug("Searching for key with label: $label");
451 0           $tmpl->push(Crypt::PKCS11::Attribute::Label->new->set($label));
452 0           $self->_session->FindObjectsInit($tmpl);
453              
454             # labels are supposed to be unique
455 0 0         my $objects = $self->_session->FindObjects(1)
456             or die "Couldn't find any key matching label $label: "
457             . $self->_session->errstr;
458              
459 0           $self->_session->FindObjectsFinal;
460              
461             # pulObjectCount down in the XS would tell us how many results were returned
462 0 0         if (scalar @$objects == 0) {
463 0           die "Failed to find a key matching label $label";
464             }
465              
466 0           $log->debug("Found key $label");
467 0           return shift @$objects;
468             }
469              
470              
471             sub get_signing_key {
472 0     0 1   my ($self, $label) = @_;
473              
474 0           my $tmpl =
475             Crypt::PKCS11::Attributes->new->push(
476             Crypt::PKCS11::Attribute::Sign->new->set(1),
477             );
478              
479 0           return $self->_get_key($label, $tmpl);
480             }
481              
482              
483             sub get_verification_key {
484 0     0 1   my ($self, $label) = @_;
485              
486 0           my $tmpl =
487             Crypt::PKCS11::Attributes->new->push(
488             Crypt::PKCS11::Attribute::Verify->new->set(1),
489             );
490              
491 0           return $self->_get_key($label, $tmpl);
492             }
493              
494              
495             sub get_encryption_key {
496 0     0 1   my ($self, $label) = @_;
497              
498 0           $log->debug('Looking for an encryption key');
499              
500 0           my $tmpl =
501             Crypt::PKCS11::Attributes->new->push(
502             Crypt::PKCS11::Attribute::Encrypt->new->set(1),
503             );
504              
505 0           return $self->_get_key($label, $tmpl);
506             }
507              
508             sub _get_pss_params {
509 0     0     my ($self, $hash, $hash_number) = @_;
510              
511 0           $log->debug("Finding params for a $hash RSA PSS signature");
512              
513             # comes in bits, need bytes. Instead of simply dividing by 8 we use a mapping
514             # hash to verify that the length is correct
515 0           my $sig_length = $self->_sig_length->{$hash_number};
516 0 0         unless ($sig_length) {
517 0           die
518             'Unsupported hash type: not SHA1/SHA2-224/SHA2-256/SHA2-384/SHA2-512';
519             }
520              
521 0           $log->debug("slen $sig_length");
522              
523 0           my $pss_param = Crypt::PKCS11::CK_RSA_PKCS_PSS_PARAMS->new;
524              
525 1     1   4 no strict 'refs'; ## no critic
  1         2  
  1         175  
526 0           my $hash_const = 'Crypt::PKCS11::CKM_';
527              
528             # SHA1 is a special case
529 0 0         $hash_const .= $hash eq 'SHA1' ? 'SHA_1' : $hash;
530 0           $log->debug("Hash constant: $hash_const");
531              
532 0           my $r = $pss_param->set_hashAlg($hash_const->());
533 0 0         if ($r != CKR_OK) {
534 0           die 'Failed to set hash algorithm for PSS params: '
535             . Crypt::PKCS11::XS::rv2str($r);
536             }
537              
538 0           $r = $pss_param->set_sLen($sig_length);
539 0 0         if ($r != CKR_OK) {
540 0           die 'Failed to set sLen on PSS params: '
541             . Crypt::PKCS11::XS::rv2str($r);
542             }
543              
544 0           my $mgf_const = "Crypt::PKCS11::CKG_MGF1_$hash";
545 0           $log->debug("MGF constant: $mgf_const");
546              
547 0           $r = $pss_param->set_mgf($mgf_const->());
548 0 0         if ($r != CKR_OK) {
549 0           die 'Failed to set MGF on PSS params: '
550             . Crypt::PKCS11::XS::rv2str($r);
551             }
552              
553 0           return $pss_param;
554             }
555              
556             sub _get_oaep_params {
557 0     0     my ($self) = @_;
558              
559             # SHA1 is the only one supported for now as it is the only one supported by
560             # openssl and softhsm2
561             # https://github.com/openssl/openssl/blob/master/crypto/rsa/rsa_oaep.c
562             # https://github.com/pspacek/SoftHSMv2/blob/master/src/lib/SoftHSM.cpp#L10173
563 0           my $hash = 'SHA1';
564              
565 0           $log->debug('Finding params for an RSA OAEP encryption');
566              
567 0           my $oaep_param = Crypt::PKCS11::CK_RSA_PKCS_OAEP_PARAMS->new;
568              
569 1     1   4 no strict 'refs'; ## no critic
  1         1  
  1         346  
570 0           my $hash_const = 'Crypt::PKCS11::CKM_';
571              
572             # SHA1 is a special case
573 0 0         $hash_const .= $hash eq 'SHA1' ? 'SHA_1' : $hash;
574 0           $log->debug("Hash constant: $hash_const");
575              
576 0           my $r = $oaep_param->set_hashAlg($hash_const->());
577 0 0         if ($r != CKR_OK) {
578 0           die 'Failed to set hash algorithm for OAEP params: '
579             . Crypt::PKCS11::XS::rv2str($r);
580             }
581              
582 0           my $mgf_const = "Crypt::PKCS11::CKG_MGF1_$hash";
583 0           $log->debug("MGF constant: $mgf_const");
584              
585 0           $r = $oaep_param->set_mgf($mgf_const->());
586 0 0         if ($r != CKR_OK) {
587 0           die 'Failed to set MGF on OAEP params: '
588             . Crypt::PKCS11::XS::rv2str($r);
589             }
590              
591 0           $oaep_param->set_source(CKZ_DATA_SPECIFIED);
592              
593 0           return $oaep_param;
594             }
595              
596             sub _handle_common_args {
597 0     0     my ($self, $args) = @_;
598              
599 0 0 0       unless (exists $args->{file} || exists $args->{data}) {
600 0           die 'Missing filename or data';
601             }
602              
603             # first, we check if data is coming via a file and read it in
604 0 0         if ($args->{file}) {
605 0           my $file = delete $args->{file};
606              
607             # a filename or a Path::Tiny object
608 0 0         if (!ref $file) {
    0          
609 0           $file = path $file;
610             } elsif (ref $file ne 'Path::Tiny') {
611 0           die "Don't know how to handle a " . ref $file;
612             }
613 0           $args->{data} = $file->slurp_raw;
614             }
615              
616 0 0         return unless exists $args->{mech};
617              
618             # Check if a non-default mechanism is requested
619              
620 0           $args->{mech} =~ s/-/_/g;
621 0           my $const = 'Crypt::PKCS11::CKM_' . $args->{mech};
622 0           $log->debug("Attempting to use mechanism: $const");
623 1     1   5 no strict 'refs'; ## no critic
  1         1  
  1         855  
624 0           my $mech = Crypt::PKCS11::CK_MECHANISM->new;
625 0           $mech->set_mechanism($const->());
626              
627             # does this mechanism need parameters?
628 0           my $params;
629 0           given ($args->{mech}) {
630 0           when (/^(SHA(\d+))_RSA_PKCS_PSS$/) {
631 0           $params = $self->_get_pss_params($1, $2);
632             }
633 0           when (/^RSA_PKCS_OAEP$/) {
634 0           $params = $self->_get_oaep_params;
635             }
636 0           default { $log->debug('No extra params required for this mech') }
  0            
637             }
638              
639 0 0         if ($params) {
640 0           my $r = $mech->set_pParameter($params->toBytes);
641              
642 0 0         if ($r != CKR_OK) {
643 0           die 'Failed to set params for mechanism: '
644             . Crypt::PKCS11::XS::rv2str($r);
645             }
646             }
647              
648 0           $args->{mech} = $mech;
649              
650 0           return;
651             }
652              
653              
654             sub sign {
655 0     0 1   my ($self, %args) = @_;
656              
657 0           $self->_handle_common_args(\%args);
658              
659 0 0         if (!$args{mech}) {
660 0           $args{mech} = Crypt::PKCS11::CK_MECHANISM->new;
661 0           $args{mech}->set_mechanism($self->_default_mech->{sign});
662             }
663              
664 0 0         $self->_session->SignInit($args{mech}, $self->_key)
665             or die "Failed to init signing: " . $self->_session->errstr;
666              
667             my $sig = $self->_session->Sign($args{data})
668 0 0         or die "Failed to sign: " . $self->_session->errstr;
669              
670 0           return $sig;
671             }
672              
673              
674             sub sign_and_encode {
675 0     0 1   my $self = shift;
676              
677 0           require MIME::Base64;
678 0           my $sig_encoded = MIME::Base64::encode_base64($self->sign(@_), '');
679              
680 0           my @lines = unpack '(a64)*', $sig_encoded;
681              
682 0           return sprintf "-----BEGIN SIGNATURE-----\n%s\n-----END SIGNATURE-----\n",
683             (join "\n", @lines);
684              
685             }
686              
687              
688             sub verify {
689 0     0 1   my ($self, %args) = @_;
690              
691 0 0         die 'Missing signature' unless $args{sig};
692 0           $self->_handle_common_args(\%args);
693              
694 0 0         if (!$args{mech}) {
695 0           $args{mech} = Crypt::PKCS11::CK_MECHANISM->new;
696 0           $args{mech}->set_mechanism($self->_default_mech->{verify});
697             }
698              
699 0 0         $self->_session->VerifyInit($args{mech}, $self->_key)
700             or die 'Failed to init verify ' . $self->_session->errstr;
701              
702 0           my $v = $self->_session->Verify($args{data}, $args{sig});
703              
704 0 0         $log->info($self->_session->errstr) unless $v;
705              
706 0           return $v;
707             }
708              
709              
710             sub digest {
711 0     0 1   my ($self, %args) = @_;
712              
713 0           $self->_handle_common_args(\%args);
714              
715 0 0         if (!$args{mech}) {
716 0           $args{mech} = Crypt::PKCS11::CK_MECHANISM->new;
717 0           $args{mech}->set_mechanism($self->_default_mech->{digest});
718             }
719              
720             $self->_session->DigestInit($args{mech})
721 0 0         or die 'Failed to init digest ' . $self->_session->errstr;
722              
723 0           my $d = $self->_session->Digest($args{data});
724 0 0         $log->info($self->_session->errstr) unless $d;
725 0           return $d;
726             }
727              
728             # This shouldn't be here, it's not HSM specific.
729             # Also, CPAN must surely have a cert/key loading module
730              
731              
732             sub decode_signature {
733 0     0 1   my ($self, %args) = @_;
734              
735 0           $self->_handle_common_args(\%args);
736              
737 0           require MIME::Base64;
738              
739 0           say $args{data};
740              
741 0           $args{data} =~ /^-----BEGIN SIGNATURE-----(.+)-----END SIGNATURE-----/s;
742 0 0         die 'Unable to find signature in data' unless $1;
743              
744 0           return MIME::Base64::decode_base64($1);
745             }
746              
747              
748             sub get_mechanism_info {
749 0     0 1   my ($self, $mech, $slot_id) = @_;
750              
751 0   0       $slot_id //= $self->slot;
752              
753 0 0         my $mech_info = $self->_pkcs11->GetMechanismInfo($slot_id, $_)
754             or die 'Failed to get mechanism info ' . $self->_pkcs11->errstr;
755              
756 0           for my $f (keys %{$self->_mechanism_flags}) {
  0            
757             $mech_info->{flag}->{$self->_mechanism_flags->{$f}} =
758 0 0         ($mech_info->{flags} & $f) ? 1 : 0;
759             }
760              
761 0           delete $mech_info->{flags};
762              
763 0           return $mech_info;
764             }
765              
766              
767             # TODO might be nice to filter mechanisms by flags, e.g. give me all the mechs
768             # that can be used for singing
769             sub get_mechanisms {
770 0     0 1   my $self = shift;
771 0           my $slot_id = shift;
772              
773 0   0       $slot_id //= $self->slot;
774              
775 0           $log->debug("Fetching mechanisms for slot $slot_id");
776 0 0         my $mech_list = $self->_pkcs11->GetMechanismList($slot_id)
777             or die 'Failed to get mechanisms ' . $self->_pkcs11->errstr;
778              
779             my %mech = map {
780 0 0         my $name = $CKM_NAME{$_} ? $CKM_NAME{$_} : $_;
  0            
781 0           $name => $self->get_mechanism_info($_, $slot_id);
782             } @$mech_list;
783 0           return \%mech;
784             }
785              
786              
787             sub encrypt {
788 0     0 1   my ($self, %args) = @_;
789              
790 0           $self->_handle_common_args(\%args);
791              
792 0 0         if (!$args{mech}) {
793 0           $args{mech} = Crypt::PKCS11::CK_MECHANISM->new;
794 0           $args{mech}->set_mechanism($self->_default_mech->{encrypt});
795             }
796              
797             # TODO check key size and size of data to be encrypted and look up max sizes for mechanism
798             # XXX trying to encrypt data that is too big returns a CKR_GENERAL_ERROR, which is super-unhelpful
799              
800 0 0         $self->_session->EncryptInit($args{mech}, $self->_key)
801             or die "Failed to init encryption: " . $self->_session->errstr;
802              
803             my $encrypted_data = $self->_session->Encrypt($args{data})
804 0 0         or die "Failed to encrypt: " . $self->_session->errstr;
805              
806 0           return $encrypted_data;
807             }
808              
809             1;
810              
811             __END__