I am looking for the simplest way to sign a given file and get the output in CMS format, but this must be using a PKCS11 provider as the private key for signing is on a smartcard.
I can get a signed file of the right format from the command line with openssl (but note this is getting the certificate from a file and not the smartcard)
openssl cms -sign -in sign.txt -out signout.txt -signer signer.pem -outform DER
I would like to do this from code using the thinnest wrapper possible. I could use the openssl library but to support pkcs11 you need to wire up through an engine (opensc have one) but then it starts getting very big. It strikes me that there must be a simple wrapper over PKCS11 available somewhere.
I am happy if the wrapper is in 'C' or .net. I can call the PKCS11 provider myself and do the signing, if I knew how to output as CMS so maybe a library to do this is enough?
Best wishes James
Very late to the party (what's 2 years amongst fellow hackers?), but here's my "minimal" OpenSSL + PKCS11 signing program (written in C99, convertible to C89 by moving all the variable definitions to the top of the block).
In my case, I built a custom crypto stack "by hand" in /opt/crypto, and the link line for this program is:
gcc -o tok-sign tok-sign.c -I /opt/crypto/include -g --std=gnu99 -Wall \
-L/opt/crypto/lib -lssl -lcrypto -lrt -lp11
The oddest bits were understanding that the "dynamic" engine really should be called a "meta-engine"; and that I had to go directly through the libp11 interface to get the certificates to pass into the CMS_sign
operation.
It could use more comments, and it's long; I apologize for both. The basic gist is:
dynamic
meta-engine to load the pkcs11
enginepkcs11
engine to use the opensc pkcs11 providerlibp11
CMS_sign
to do the heavy liftingHere's the program itself. I should probably turn this into a blog post somewhere.
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <openssl/cms.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <libp11.h>
#define FAIL( msg, dest ) \
do { \
fprintf( stderr, "error: " msg "\n" ); \
goto dest; \
} while ( 0 )
static
void
print_time( const char * label )
{
struct timespec ts;
clock_gettime( CLOCK_MONOTONIC, &ts );
fprintf( stderr, "+%8lld.%09ld: %s\n",
(long long)ts.tv_sec, ts.tv_nsec, label );
}
int
main( int argc, char * argv [] )
{
enum
{
ARG_IN_DATA_FILE_IX = 1,
ARG_OUT_SIG_FILE_IX = 2,
ARG_KEY_LABEL_IX = 3,
ARG_KEY_PIN_IX = 4
};
int exit_code = 0;
/* -------------------------------------------------------------- */
/* initialization */
exit_code = 1;
SSL_load_error_strings();
SSL_library_init();
/* -------------------------------------------------------------- */
/* command-line processing */
exit_code = 2;
if ( argc != 5 )
{
fprintf( stderr, "usage: %s IN_DATA_FILE OUT_SIG_FILE"
" KEY_LABEL KEY_PIN\n", argv[0] );
return 1;
}
BIO * in_data_file = BIO_new_file( argv[ ARG_IN_DATA_FILE_IX ], "rb" );
if ( ! in_data_file )
{
perror( argv[ ARG_IN_DATA_FILE_IX ] );
goto end;
}
BIO * out_sig_file = BIO_new_file( argv[ ARG_OUT_SIG_FILE_IX ], "wb" );
if ( ! out_sig_file )
{
perror( argv[ ARG_OUT_SIG_FILE_IX ] );
goto free_in_data_file;
}
const char * key_label = argv[ ARG_KEY_LABEL_IX ];
char * key_id = calloc( sizeof( char ), strlen( key_label ) + 7 );
strcpy( key_id, "label_" );
strcat( key_id, key_label );
const char * key_pin = argv[ ARG_KEY_PIN_IX ];
/* -------------------------------------------------------------- */
/* load dynamic modules / engines */
exit_code = 3;
/* mandatory is "not optional"... */
const int CMD_MANDATORY = 0;
ENGINE_load_dynamic();
ENGINE * pkcs11 = ENGINE_by_id( "dynamic" );
if ( ! pkcs11 )
FAIL( "retrieving 'dynamic' engine", free_out_sig_file );
char * engine_pkcs11_so = "/opt/crypto/lib/engines/engine_pkcs11.so";
if ( 0 != access( engine_pkcs11_so, R_OK ) )
{
engine_pkcs11_so = "/lib/engines/engine_pkcs11.so";
if ( 0 != access( engine_pkcs11_so, R_OK ) )
FAIL( "finding 'engine_pkcs11.so'", free_pkcs11 );
}
if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "SO_PATH", engine_pkcs11_so, CMD_MANDATORY ) )
FAIL( "pkcs11: setting so_path <= 'engine_pkcs11.so'", free_pkcs11 );
if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "ID", "pkcs11", CMD_MANDATORY ) )
FAIL( "pkcs11: setting id <= 'pkcs11'", free_pkcs11 );
if ( 1 != ENGINE_ctrl_cmd( pkcs11, "LIST_ADD", 1, NULL, NULL, CMD_MANDATORY ) )
FAIL( "pkcs11: setting list_add <= 1", free_pkcs11 );
if ( 1 != ENGINE_ctrl_cmd( pkcs11, "LOAD", 1, NULL, NULL, CMD_MANDATORY ) )
FAIL( "pkcs11: setting load <= 1", free_pkcs11 );
ENGINE * tok = ENGINE_by_id( "pkcs11" );
if ( ! tok )
FAIL( "tok: unable to get engine", free_pkcs11 );
char * opensc_pkcs11_so = "/opt/crypto/lib/opensc-pkcs11.so";
if ( 0 != access( opensc_pkcs11_so, R_OK ) )
{
opensc_pkcs11_so = "/lib/opensc-pkcs11.so";
if ( 0 != access( opensc_pkcs11_so, R_OK ) )
FAIL( "finding 'opensc-pkcs11.so'", free_tok );
}
if ( 1 != ENGINE_ctrl_cmd_string( tok, "MODULE_PATH", opensc_pkcs11_so, CMD_MANDATORY ) )
FAIL( "setting module_path <= 'opensc-pkcs11.so'", free_tok );
if ( 1 != ENGINE_ctrl_cmd_string( tok, "PIN", key_pin, CMD_MANDATORY ) )
FAIL( "setting pin", free_tok );
if ( 1 != ENGINE_init( tok ) )
FAIL( "tok: unable to initialize engine", free_tok );
/* -------------------------------------------------------------- */
/* reading from token */
exit_code = 4;
EVP_PKEY * key = ENGINE_load_private_key( tok, key_id, NULL, NULL );
if ( ! key )
FAIL( "reading private key", free_tok );
PKCS11_CTX * p11_ctx = PKCS11_CTX_new();
if ( ! p11_ctx )
FAIL( "opening pkcs11 context", free_key );
if ( 0 != PKCS11_CTX_load( p11_ctx, opensc_pkcs11_so ) )
FAIL( "unable to load module", free_p11_ctx );
PKCS11_SLOT * p11_slots;
unsigned int num_p11_slots;
if ( 0 != PKCS11_enumerate_slots( p11_ctx, &p11_slots, &num_p11_slots ) )
FAIL( "enumerating slots", free_p11_ctx_module );
PKCS11_SLOT * p11_used_slot =
PKCS11_find_token( p11_ctx, p11_slots, num_p11_slots );
if ( ! p11_used_slot )
FAIL( "finding token", free_p11_slots );
PKCS11_CERT * p11_certs;
unsigned int num_p11_certs;
if ( 0 != PKCS11_enumerate_certs( p11_used_slot->token, &p11_certs, &num_p11_certs ) )
FAIL( "enumerating certs", free_p11_slots );
STACK_OF(X509) * extra_certs = sk_X509_new_null();
if ( ! extra_certs )
FAIL( "allocating extra certs", free_p11_slots );
X509 * key_cert = NULL;
for ( unsigned int i = 0; i < num_p11_certs; ++i )
{
PKCS11_CERT * p11_cert = p11_certs + i;
if ( ! p11_cert->label )
continue;
// fprintf( stderr, "p11: got cert label='%s', x509=%p\n",
// p11_cert->label, p11_cert->x509 );
if ( ! p11_cert->x509 )
{
fprintf( stderr, "p11: ... no x509, ignoring\n" );
continue;
}
const char * label = p11_cert->label;
const unsigned int label_len = strlen( label );
if ( strcmp( label, key_label ) == 0 )
{
// fprintf( stderr, "p11: ... saving as signing cert\n" );
key_cert = p11_cert->x509;
}
else if ( strncmp( label, "encrypt", 7 ) == 0 &&
label_len == 8 &&
'0' <= label[7] && label[7] <= '3' )
{
// fprintf( stderr, "p11: ... ignoring as encrypting cert\n" );
}
else
{
// fprintf( stderr, "p11: ... saving as extra cert\n" );
if ( ! sk_X509_push( extra_certs, p11_cert->x509 ) )
FAIL( "pushing extra cert", free_extra_certs );
}
}
if ( ! key_cert )
FAIL( "finding signing cert", free_extra_certs );
/* -------------------------------------------------------------- */
/* signing */
exit_code = 5;
print_time( "calling CMS_sign" );
CMS_ContentInfo * ci = CMS_sign( key_cert, key, extra_certs, in_data_file,
CMS_DETACHED | CMS_BINARY );
/* if ( 1 != PEM_write_bio_CMS( out_sig_file, ci ) )
FAIL( "could not write signature in PEM", free_ci ); */
print_time( "calling i2d_CMS_bio" );
if ( 1 != i2d_CMS_bio( out_sig_file, ci ) )
FAIL( "could not write signature in DER", free_ci );
print_time( "done" );
/* -------------------------------------------------------------- */
/* success */
exit_code = 0;
/* -------------------------------------------------------------- */
/* cleanup */
free_ci:
CMS_ContentInfo_free( ci );
free_extra_certs:
/* these certs are actually "owned" by the libp11 code, and are
* presumably freed with the slot or context. */
sk_X509_free( extra_certs );
free_p11_slots:
PKCS11_release_all_slots( p11_ctx, p11_slots, num_p11_slots );
free_p11_ctx_module:
PKCS11_CTX_unload( p11_ctx );
free_p11_ctx:
PKCS11_CTX_free( p11_ctx );
free_key:
EVP_PKEY_free( key );
free_tok:
ENGINE_free( tok );
free_pkcs11:
ENGINE_free( pkcs11 );
free_out_sig_file:
BIO_vfree( out_sig_file );
free_in_data_file:
BIO_vfree( in_data_file );
ERR_print_errors_fp( stderr );
ERR_remove_state( /* pid= */ 0 );
ENGINE_cleanup();
CONF_modules_unload( /* all= */ 1 );
EVP_cleanup();
ERR_free_strings();
CRYPTO_cleanup_all_ex_data();
end:
return exit_code;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With