Using Perl’s Inline::C to call OpenSSL’s EVP and ENGINE libraries.
We’d like to use the SCA6000, a hardware security module from a Perl script.
The problem is that there is no perl package that combines OpenSSL’s ENGINE and EVP functionality. To route around this problem, we’ll call the necessary C library functions straight from perl, using Perl’s Inline C package. Inline::C is very useful to make add-hoc function calls to C libraries from Perl. We’ll try to give a brief explanation here.
Note that we don’t need to write any C code for this test, we just need to import C functions using prototypes from openssl/evp.h and openssl/engine.h. One thing is important to realize. In perl, there is no concept of types. A variable can transpose from integer to string to integer on the fly. This makes Perl powerful, or annoying, depending on your perspective. But, to include C code in your perl program, the concept of types rises to the surface. To call a C function from perl, the perl variable needs to be casted to a proper C type. This casting is done by using a typemap, which maps C types to perl, and vice versa.
For well known C types, such as int, short, char, etc, there exist a typemap file. A particular line in a typemap contains the C type on the left hand side, and a perl macro on the right hand side. The macro contains an INPUT section, to translate perl to C types, and an OUTPUT section to translate C types to perl. For unknown C types, like the EVP_PKEY struct, we’d have to write our own typemap. Luckily, most of these things are pointers to structs of which we don’t need to use the internals in perl, so we’ll map those to T_PTROBJ macros.
In short, it is trivial to include C code in your perl scripts if it is using well known C types. For other types, the typemap needs some extra work. That is it really. A proof of concept perl script (EVP_signer.pl) is included below. If you’re interested in what happens under the blanket, look at the example below, and move on to read perlguts, perlxs, etc.
I’ll give an example from an actual typemap file, included in any perl distro:
int T_IV INPUT T_IV $var = (int)SvIV($arg) OUTPUT T_IV sv_setiv($arg, (IV)$var);
$var is the C variable, $arg is the perl variable.
In the INPUT section (turns perl variable to C) we see a function SvIV($arg). This gets the value of $arg. It is then casted to an integer (using (int)), and stored in $var.
The OUTPUT section (that turns C to perl) we see a function sv_setiv(). This turns an integer, stored in $var, into a perl variable in $arg.
The real typemap we use for this exercise (a perl program that uses the EVP and ENGINE function calls) is fairly straightforward:
EVP_MD_CTX * T_PTROBJ EVP_PKEY * T_PTROBJ ENGINE * T_PTROBJ UI_METHOD * T_PTROBJ EVP_MD * T_PTROBJ const EVP_MD * T_PTROBJ unsigned int* T_PV const void* T_PV
The program itself is below. The code is commented inline:
#!/usr/bin/perl -w
use strict;
use subs qw/check_error_queue/;
# the SCA6000 constant indicates if we're using a SUN SCA6000 card, in which case we're using
# the pkcs11 engine, or if we're using the default openssl engine. We'll also base the path to the
# openssl library based on the SCA6000 value.
use constant SCA6000 => 0;
use constant OPENSSLPATH => SCA6000?'/opt/openssl-0.9.8d':'/usr/local/ssl';
sub main
{
# The user needs to specify a key identifier as argument.
$#ARGV == 0 or die "usage: EVP_signer keyid\n";
# all the parameters we'd like the user to specify, but we'll declare it here for now.
my $message = "Hello World!"; # the message to be signed.
my $key_id = $ARGV[0]; # the key identifier, derived from the argument
my $engine_id = SCA6000?"pkcs11":"openssl"; # the engine
# load human readable error strings.
ERR_load_crypto_strings();
# read the standard openssl config file
OPENSSL_config(0); check_error_queue;
# setup engine
ENGINE_load_openssl(); check_error_queue;
my $engine = ENGINE_by_id($engine_id); check_error_queue;
ENGINE_init($engine) or check_error_queue;
# To use the engine, we need to gain access first. The user is authenticated by a PIN.
# This is only useful when the SCA6000 is used as an engine.
SCA6000 and (ENGINE_ctrl_cmd_string($engine, 'PIN', 'nominet1:abc123', 0) or check_error_queue);
# assign the private key
my $key = ENGINE_load_private_key($engine, $key_id, UI_OpenSSL(), 0); check_error_queue;
# create a digest context and assign the digest method
my $ctx = EVP_MD_CTX_create(); check_error_queue;
EVP_SignInit($ctx, EVP_sha1()) or check_error_queue;
# hash the message into digest context
EVP_SignUpdate($ctx, $message, length($message)) or check_error_queue;
# setup a buffer to store the signature in. The buffer needs to be as long as the private key.
my $sig_buflen = EVP_PKEY_size($key); check_error_queue;
my $sig_buf = "\0" x $sig_buflen;
# sign the hash in the digest context with our key
EVP_SignFinal( $ctx, $sig_buf, $sig_buflen, $key) or check_error_queue;
# print the signature in hexadecimal encoding.
print "\n", unpack( "H*", $sig_buf), "\n";
# clean up after us
EVP_MD_CTX_destroy($ctx);check_error_queue;
EVP_PKEY_free($key);check_error_queue;
ENGINE_finish($engine);check_error_queue;
ENGINE_free($engine);check_error_queue;
}
main;
# The check_error_queue function is a wrapper around ERR_get_error() to print human
# readable error strings. If an error occurred, the program dies, printing
# the error.
#
sub check_error_queue
{
my $errcode=ERR_get_error();
my $errstr="\0"x256;
if ($errcode)
{
ERR_error_string($errcode,$errstr);
die($errstr,"\n");
}
}
use Inline C => DATA =>
ENABLE => AUTOWRAP =>
TYPEMAPS => './typemap' =>
LIBS => '-L'.OPENSSLPATH.'/lib -lcrypto' =>
INC => '-I'.OPENSSLPATH.'/include'
__END__
__C__
#include <openssl/evp.h>
/* Note that we only need to include the openssl/evp.h header file.
* The others, like engine.h and err.h are included by the evp.h file
*/
void OPENSSL_config(const char *config_name);
EVP_MD_CTX* EVP_MD_CTX_create();
void EVP_MD_CTX_destroy(EVP_MD_CTX* ctx);
const EVP_MD* EVP_sha1();
void EVP_PKEY_free(EVP_PKEY* pkey);
int EVP_PKEY_size(EVP_PKEY* pkey);
int EVP_SignInit(EVP_MD_CTX* ctx,const EVP_MD* type);
int EVP_SignUpdate(EVP_MD_CTX* ctx,const void* d,size_t cnt);
int EVP_SignFinal(EVP_MD_CTX* ctx,unsigned char* md,unsigned int* s,EVP_PKEY* pkey);
int ENGINE_init(ENGINE *e);
int ENGINE_finish(ENGINE *e);
int ENGINE_free(ENGINE *e);
void ENGINE_load_openssl();
ENGINE* ENGINE_by_id(const char *id);
EVP_PKEY* ENGINE_load_private_key(ENGINE *e, const char *key_id, UI_METHOD *ui_method, void *callback_data);
int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg, int cmd_optional);
void ERR_load_crypto_strings();
unsigned long ERR_get_error();
char* ERR_error_string(unsigned long e,char *buf);
UI_METHOD* UI_OpenSSL();

(5 votes, average: 4.4 out of 5)