random technical thoughts from the Nominet technical team

Using Perl’s Inline::C to call OpenSSL’s EVP and ENGINE libraries.

1 Star2 Stars3 Stars4 Stars5 Stars (5 votes, average: 4.4 out of 5)
Loading ... Loading ...
Posted by roy on Apr 22nd, 2007

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();

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Recent Posts

Highest Rated

Categories

Archives

Meta: