Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
[#4] Support encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
ioanrogers committed Aug 2, 2016
1 parent 3096b8b commit 1f0af1f
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 13 deletions.
117 changes: 112 additions & 5 deletions lib/Crypt/PKCS11/Easy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,11 @@ has _default_mech => (
is => 'ro',
default => sub {
{
sign => CKM_SHA1_RSA_PKCS,
verify => CKM_SHA1_RSA_PKCS,
digest => CKM_SHA_1,
digest => CKM_SHA_1,
encrypt => CKM_RSA_PKCS,
sign => CKM_SHA1_RSA_PKCS,
verify => CKM_SHA1_RSA_PKCS,

};
},
);
Expand Down Expand Up @@ -281,6 +283,7 @@ sub _build__key {
given ($self->function) {
return $self->get_signing_key($self->key) when 'sign';
return $self->get_verification_key($self->key) when 'verify';
return $self->get_encryption_key($self->key) when 'encrypt';
default {
die "Unknown key type: " . $self->function;
}
Expand Down Expand Up @@ -605,6 +608,28 @@ sub get_verification_key {
return $self->_get_key($label, $tmpl);
}

=method C<get_encryption_key(Str $label)>
Will look for a key matching with a label matching C<$label> which can be used
for encryption.
The returned key is a L<Crypt::PKCS11::Object>.
=cut

sub get_encryption_key {
my ($self, $label) = @_;

$log->debug('Looking for an encryption key');

my $tmpl =
Crypt::PKCS11::Attributes->new->push(
Crypt::PKCS11::Attribute::Encrypt->new->set(1),
);

return $self->_get_key($label, $tmpl);
}

sub _get_pss_params {
my ($self, $hash, $hash_number) = @_;

Expand Down Expand Up @@ -653,6 +678,46 @@ sub _get_pss_params {
return $pss_param;
}

sub _get_oaep_params {
my ($self) = @_;

# SHA1 is the only one supported for now as it is the only one supported by
# openssl and softhsm2
# https://github.com/openssl/openssl/blob/master/crypto/rsa/rsa_oaep.c
# https://github.com/pspacek/SoftHSMv2/blob/master/src/lib/SoftHSM.cpp#L10173
my $hash = 'SHA1';

$log->debug('Finding params for an RSA OAEP encryption');

my $oaep_param = Crypt::PKCS11::CK_RSA_PKCS_OAEP_PARAMS->new;

no strict 'refs'; ## no critic
my $hash_const = 'Crypt::PKCS11::CKM_';

# SHA1 is a special case
$hash_const .= $hash eq 'SHA1' ? 'SHA_1' : $hash;
$log->debug("Hash constant: $hash_const");

my $r = $oaep_param->set_hashAlg($hash_const->());
if ($r != CKR_OK) {
die 'Failed to set hash algorithm for OAEP params: '
. Crypt::PKCS11::XS::rv2str($r);
}

my $mgf_const = "Crypt::PKCS11::CKG_MGF1_$hash";
$log->debug("MGF constant: $mgf_const");

$r = $oaep_param->set_mgf($mgf_const->());
if ($r != CKR_OK) {
die 'Failed to set MGF on OAEP params: '
. Crypt::PKCS11::XS::rv2str($r);
}

$oaep_param->set_source(CKZ_DATA_SPECIFIED);

return $oaep_param;
}

sub _handle_common_args {
my ($self, $args) = @_;

Expand Down Expand Up @@ -686,12 +751,19 @@ sub _handle_common_args {

# does this mechanism need parameters?
my $params;
if ($args->{mech} =~ /(^SHA(\d+))_RSA_PKCS_PSS$/) {
$params = $self->_get_pss_params($1, $2);
given ($args->{mech}) {
when (/^(SHA(\d+))_RSA_PKCS_PSS$/) {
$params = $self->_get_pss_params($1, $2);
}
when (/^RSA_PKCS_OAEP$/) {
$params = $self->_get_oaep_params;
}
default { $log->debug('No extra params required for this mech') }
}

if ($params) {
my $r = $mech->set_pParameter($params->toBytes);

if ($r != CKR_OK) {
die 'Failed to set params for mechanism: '
. Crypt::PKCS11::XS::rv2str($r);
Expand Down Expand Up @@ -901,6 +973,41 @@ sub get_mechanisms {
return \%mech;
}

=method C<encrypt((data => 'some data' | file => '/path'), mech => 'RSA_PKCS'?)>
Returns encrypted data. The data to be encrypted is either passed as a scalar
in C<data>, or in C<file> which can be a string path or a L<Path::Tiny> object.
A PKCS#11 mechanism can optionally be specified as a string and without the
leading 'CKM_'.
my $encrypted_data = $hsm->sign(file => $file, mech => 'RSA_PKCS');
my $encrypted_data = $hsm->sign(data => 'SIGN ME');
=cut

sub encrypt {
my ($self, %args) = @_;

$self->_handle_common_args(\%args);

if (!$args{mech}) {
$args{mech} = Crypt::PKCS11::CK_MECHANISM->new;
$args{mech}->set_mechanism($self->_default_mech->{encrypt});
}

# TODO check key size and size of data to be encrypted and look up max sizes for mechanism
# XXX trying to encrypt data that is too big returns a CKR_GENERAL_ERROR, which is super-unhelpful

$self->_session->EncryptInit($args{mech}, $self->_key)
or die "Failed to init encryption: " . $self->_session->errstr;

my $encrypted_data = $self->_session->Encrypt($args{data})
or die "Failed to encrypt: " . $self->_session->errstr;

return $encrypted_data;
}

1;

__END__
Expand Down
28 changes: 26 additions & 2 deletions t/02-basic.t
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ test signing_and_verifying => sub {
my $data_file = path 't/data/10K.file';
my $key_file = path 't/keys/1024_sign.pem';

my $pkcs11 = $self->_new_pkcs11('signing_key', 0);
my $pkcs11 = $self->_new_pkcs11(key => 'signing_key', slot => 0);

ok my $sig = $pkcs11->sign(file => $data_file);
my $ossl_sig = $self->openssl_sign($key_file, $data_file);
Expand All @@ -104,11 +104,35 @@ mUwq3aC/GjFW+pOLRYevQ2UwJiZmcVtP4nDD9Vt/exZS/ggM4HnaoGm8QyGnhlk3
is $enc_sig, $expected_sig, 'Encoded sigs are good';

$pkcs11 = undef;
$pkcs11 = $self->_new_pkcs11('signing_key', 0, 'verify');
$pkcs11 =
$self->_new_pkcs11(key => 'signing_key', slot => 0, func => 'verify');
ok $pkcs11->verify(sig => $sig, file => $data_file), 'verified signature';

$key_file = path 't/keys/1024_sign_pub.pem';
ok $self->openssl_verify($key_file, $sig_file, $data_file);
};

test encryption => sub {
my $self = shift;

my $data_file = path 't/data/64B.file';

for my $mech (qw/RSA_PKCS RSA_PKCS_OAEP/) {
my $pkcs11 = $self->_new_pkcs11(
key => 'encryption_key',
slot => 0,
func => 'encrypt'
);
ok my $encrypted_data =
$pkcs11->encrypt(file => $data_file, mech => $mech),
"Encrypted with mech $mech";

my $private_key_file = path 't/keys/1024_enc.pem';
ok my $decrypted_data =
$self->openssl_decrypt($private_key_file, $encrypted_data, $mech),
'Decrypted using openssl';
is $decrypted_data, $data_file->slurp_raw, 'Successful round-trip';
}

};

Expand Down
1 change: 1 addition & 0 deletions t/data/64B.file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
P�AbxX�ޢ�}�Df�P�v���+r��l���$���d�«0"�80�\�'���v���O��a3�
42 changes: 36 additions & 6 deletions t/lib/CommonTest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ use Test::TempDir::Tiny;
use Path::Tiny;
use IPC::Cmd 0.92 qw/can_run run run_forked/;

has _openssl => (
is => 'ro',
lazy => 1,
default => sub {
my $p = can_run 'openssl';
BAIL_OUT "openssl not found, cannot continue" unless $p;
return path $p;
},
);

has workdir => (
is => 'ro',
clearer => 1,
Expand Down Expand Up @@ -90,7 +100,8 @@ sub _build_pkcs11 {
}

sub _new_pkcs11 {
my ($self, $key, $slot, $func) = @_;
my $self = shift;
my %args = @_;

my $mod = 'Crypt::PKCS11::Easy';

Expand All @@ -100,9 +111,10 @@ sub _new_pkcs11 {

my $args = [module => 'libsofthsm2'];

push @$args, key => $key if $key;
push @$args, slot => $slot if defined $slot;
push @$args, function => $func if defined $func;
push @$args, key => $args{key} if $args{key};
push @$args, slot => $args{slot} if defined $args{slot};
push @$args, function => $args{func} if $args{func};
push @$args, mech => $args{mech} if $args{mech};
push @$args, pin => '1234';

my $obj = new_ok $mod => $args;
Expand Down Expand Up @@ -141,10 +153,28 @@ sub openssl_verify {
$self->_openssl, 'dgst', '-sha1', '-verify',
$key_file, '-signature', $sig_file, $data_file
];
my $output = run_forked $openssl_cmd,
{verbose => $ENV{TEST_DEBUG}, child_stdin => $data_file->slurp_raw};

return run command => $openssl_cmd, verbose => $ENV{TEST_DEBUG};
}

sub openssl_decrypt {
my ($self, $private_key_file, $encrypted_data, $mech) = @_;

my $openssl_cmd =
[$self->_openssl, 'rsautl', '-decrypt', '-inkey', $private_key_file];

if ($mech eq 'RSA_PKCS') {
push @$openssl_cmd, '-pkcs';
} elsif ($mech eq 'RSA_PKCS_OAEP') {
push @$openssl_cmd, '-oaep';
} else {
die "Unsupported mech: $mech";
}

my $output = run_forked $openssl_cmd,
{verbose => $ENV{TEST_DEBUG}, child_stdin => $encrypted_data};
chomp $output->{stdout};

return $output->{stdout};
}

Expand Down

0 comments on commit 1f0af1f

Please sign in to comment.