NAME

Crypt::LibSCEP - Perl extension for libscep, a C implementation of the SCEP protocol

SYNOPSIS

    use Crypt::LibSCEP;

    $sig_key  = ...
    $sig_cert = ...
    $enc_cert = ...

    $handle   = Crypt::LibSCEP::create_handle( {
        sigalg => 'sha256',
        encalg => 'aes256'
    });
    $conf = { handle => $handle };
    $pkcsreq = Crypt::LibSCEP::pkcsreq($conf, $sig_key, $sig_cert, $enc_cert, $req);
    $pkcsreq_parsed = Crypt::LibSCEP::parse($conf, $pkcsreq);

DESCRIPTION

Crypt::LibSCEP implements an easy-to-use interface between LibSCEP and Perl programs. Its goal is to provide Perl programs with the capability of generating and reading messages for the Simple Certificate Enrollment Protocol (SCEP).

DESIGN

Crypt::LibSCEP consists of three different kinds of functionality: Create SCEP messages, parse SCEP messages and accessor methods for extracting specific information from parsed SCEP messages.

Crypt::LibSCEP consists of the following components:

Blessed Objects

A parsed SCEP message is returned as a blessed object and not a Perl data structure. In order to obtain information from it, accessor methods are written that return the requested information.

Data Format

Certificates and keys must be provided as PEM-encoded strings. Keys may be encrypted. Also, all accessor functions return strings. There is no support for file handles.

Configuration Parameter

Besides accessor functions, the first parameter of every function is a hash reference reflecting a configuration. An empty hash reference is possible. Then, default values will be used. The use of the configuration parameter will be explained later on.

Handle

A handle carries information that needs to be persistent over multiple requests. This is mostly the case when dealing with cryptographic engines.

Parsing an SCEP Message

A SCEP message can contain encrypted information. Everything else can be parsed right ahead:

        $conf = {};
        $parsed = Crypt::LibSCEP::parse($conf, $scep_msg);

Afterwards, $parsed carries all information that has been stored in $scep_msg but the encrypted information. This is a convenience function in order to provide direct access to the data without involving cryptographic information.

In order to fully parse a message, the function unwrap is used.

        $unwrapped = Crypt::LibSCEP::unwrap(
        $conf, $scep_msg, $sig_cert, $enc_cert, $enc_key);

$sig_cert is used for checking the message's signature whereas $enc_cert and $enc_key are used for decrypting the message's encrypted part. $parsed and $unwrapped are the same data type.

ACCESSOR FUNCTIONS

Different messages types contain different values. The user is expected to provide an accessor function with the correct message. In case the content doesn't match the function (e.g. read out the failInfo from a SUCCESS message) the results can lead to false information!

get_transaction_id

Returns the transaction ID.

        Crypt::LibSCEP::get_transaction_id($parsed);

get_message_type

Returns the message type.

        Crypt::LibSCEP::get_message_type($parsed);

get_getcert_serial

In case the message type of $unwrapped is GetCert or GetCRL this function returns the serial number of the requested content.

        Crypt::LibSCEP::get_getcert_serial($unwrapped);

get_subject

In case the message type of $unwrapped is GetCertInitial this function returns the subject in one line used for the requested certificate.

        Crypt::LibSCEP::get_subject($unwrapped);

get_issuer

In case the message type of $unwrapped is GetCertInitial, GetCert or GetCRL this function returns the issuer in one line.

        Crypt::LibSCEP::get_issuer($unwrapped);

get_failInfo

In case the message type of $parsed is CertRep and PkiStatus is FAILURE, this function returns the failInfo.

        Crypt::LibSCEP::get_failInfo($parsed);

get_pkiStatus

In case the message type of $parsed is CertRep, this function returns the pkiStatus.

        Crypt::LibSCEP::get_pkiStatus($parsed);

get_cert

In case the message type of $unwrapped is CertRep as an answer to PKCSReq, GetCert or GetCertInitial and pkiStatus is SUCCESS this function returns the included certificate. This is a different function than getcert which creates a message of type GetCert.

        Crypt::LibSCEP::get_cert($unwrapped);

get_crl

In case the message type of $unwrapped is CertRep as an answer to GetCRL and pkiStatus is SUCCESS this function returns the included CRL. This is a different function than getcrl which creates a message of type GetCRL.

        Crypt::LibSCEP::get_crl($unwrapped);

get_signer_cert

Returns the signer certificate of the SCEP message.

        Crypt::LibSCEP::get_signer_cert($parsed);

get_pkcs10

In case the message type of $unwrapped is PKCSReq this function returns the decrypted certificate signing request.

        Crypt::LibSCEP::get_pkcs10($unwrapped);

get_recipientNonce

Returns the recipient nonce. As it is stored as a binary value one might want to convert it to a hex value.

        $senderNonce = Crypt::LibSCEP::get_recipientNonce($parsed);
        $hex = unpack(  'H*', $senderNonce );

get_senderNonce

Returns the sender nonce. As it is stored as a binary value one might want to convert it to a hex value.

        $senderNonce = Crypt::LibSCEP::get_senderNonce($parsed);
        $hex = unpack(  'H*', $senderNonce );

CREATING SCEP MESSAGES

SCEP defines so-called pkiMessages which can be created using a single code line. The string that is returned contains the pkiMessage in its PEM-encoded form. As a pkiMessage is a restricted version of a PKCS#7 message, the PEM starting line indicates that the message is a PKCS #7 message.

Creating a pkiMessage is not trivial: Some parameters are automatically generated and not configurable, some can be configured using the $conf parameter. Not configurable are the transaction ID and the sender nonce. Both are automatically generated according to the SCEP specification by libSCEP. It is not possible to provide them from the outside. Cryptographic parameters, namely the algorithms used for encryption and signing can be changed using the $conf parameter.

NOTE: Not every input data provided to Crypt::LibSCEP is suitable for creating pkiMessages. For example, the CSR that should be included into a pkiMessage of type PKCSReq MUST include the challengePassword attribute. LibSCEP will not check for the correctness of the input data. The user must verify whether the data provided to libSCEP fulfills the requirements of SCEP.

PKCSReq

The PKCSReq message type is basically an encrypted and signed CSR. Therefore, it requires a certificate and key to sign the message, an encryption certificate and the CSR to be sent. There is only one function to create a PKCSReq message.

    $pkcsreq = Crypt::LibSCEP::pkcsreq($conf, $sig_key, $sig_cert, $enc_cert, $req);

GetCertInitial

Similar to PKCSReq. Instead of a CSR, this message type includes a transaction ID, a subject and the issuer.

The transaction ID and subject is derived from the CSR, the issuer is derived from the encryption certificate. Even though the transaction ID is not part of the CSR, LibSCEP assumes that the transaction ID is derived from the public key of the CSR. In case a different transaction ID is is used, LibSCEP cannot deal with it.

One might ask why the $issuer_cert is required for the GetCertInitial request but not for the PKCSReq message. Indeed, the PKCSReq message does not require any information about the issuer. We can't find any reason why this is different for GetCertInitial. The client still doesn't know which certificate will be used to sign the request. We assume this is a flaw in the specification.

        $getcertinitial = Crypt::LibSCEP::getcertinitial(
        $conf, $sig_key, $sig_cert, $enc_cert, $req, $issuer_cert);

GetCRL

Similar to PKCSReq. Instead of a CSR, this message includes an IssuerAndSerial from the certificate to be validated. Instead of providing the issuer and serial separately as it is the case in the getcert function, this functions assumes that the certificate that should be validated is present. If not, it can be obtained from the CA by using cetcert.

        $getcertinitial = Crypt::LibSCEP::getcrl(
        $conf, $sig_key, $sig_cert, $enc_cert, $cert_to_be_validated);

CertRep

CertRep is similar to PKCSReq. However, the message format differs depending on the pkiStatus attribute. Three different functions are implemented, one for each pkiStatus (PENDING, FAILURE, SUCCESS). In order to build a CertRep message (which is a response message to a PKCSReq message), information from the previously received PKCSReq message are required. Therefore, the easiest solution is to provide the PKCSReq message when creating a CertRep message. As not the entire PKCSReq message is required in order to build a response message, additional functionality has been implemented allowing for creating CertRep messages without providing the PKCSReq message but only the content of the PKCSReq message that is required.

create_pending_reply

Creates a pkiMessage of type CertRep and pkiStatus set to PENDING.

        $certrep = Crypt::LibSCEP::create_pending_reply($config, $sig_key, $sig_cert, $pkcsreq);

        $transid = Crypt::LibSCEP::get_transaction_id($pkcsreq);
        $senderNonce = Crypt::LibSCEP::get_recipientNonce($pkcsreq);
        $certrep = Crypt::LibSCEP::create_pending_reply_wop7($config, $sig_key, $sig_cert, $transid, $senderNonce);

create_error_reply

Creates a pkiMessage of type CertRep and pkiStatus set to FAILURE. In addition to the CertRep PENDING, an error message also requires a failInfo attribute. Please look into the SCEP specification which values are allowed here.

        $failInfo = "badRequest";
        $certrep = Crypt::LibSCEP::create_error_reply(
        $config, $sig_key, $sig_cert, $pkcsreq, $failInfo);

        $certrep = Crypt::LibSCEP::create_error_reply_wop7(
        $config, $sig_key, $sig_cert, $transid, $senderNonce, $failInfo);

create_certificate_reply

Creates a pkiMessage of type CertRep and pkiStatus set to SUCCESS as an answer to the request-type PKCSReq, GetCertInitial or GetCert. As this message carries the issued or requested certificate, it must be provided to the function.

A certificate response message can also carry multiple certificates. However, the issued or requested one must be the first in list. The functions are built in such a way that they also accept an entire chain of certificates for $issuedCert. This allows for the user to include multiple ones. The user must be careful and provide a chain that conforms to this limitation.

A SUCCESS reply can be tricky: The user might want so get a signing certificate that should not be used for encryption. Normally, the certificate for encryption is the one issued. In order to provide a way to send a SUCCESS response with a different certificate for encryption, use the *_wo_p7 function and provide the encryption certificate manually.

        $certrep = Crypt::LibSCEP::create_certificate_reply(
        $config, $sig_key, $sig_cert, $pkcsreq_or_getcert, $req_cert_or_chain);

        $certrep = Crypt::LibSCEP::create_certificate_reply_wop7(
        $config, $sig_key, $sig_cert, $transid, $senderNonce
         $enc_cert, $req_cert_or_chain);

create_crl_reply

Like create_certificate_reply but instead of certificates, a CRL is included. This message is used as a SUCCESS answer to GetCRL.

        $crl_reply = Crypt::LibSCEP::create_crl_reply(
        $config, $sig_key, $sig_cert, $getcrl, $crl);

GetCert

A GetCert message contains an issuer and a serial number which together uniquely identify a certificate that is requested.

getcert

The function takes the serial number as a string whereas the issuer must be directly derived from the issuer certificate. Typically this way is easier even though only the subject of the issuer certificate rather than the entire certificate must be present for this task.

        $serial = "1";
        $getcert = Crypt::LibSCEP::getcert($conf, $sig_key, $sig_cert,
        $enc_cert, $issuer_cert, $serial);

Further Convenience Functions

create_nextca_reply

HTTP messages in the context of SCEP don't necessarily transport pkiMessages meaning they don't have SCEP-specific attributes. LibSCEP provides a convenience function to create so-called degenerate certificates-only PKCS#7 Signed-data messages. This is required for creating an HTTP response message to GetNextCACert. All certificates that should be included must be chained to one string and stored in $chain.

        $getNextCaCertReply = Crypt::LibSCEP::create_nextca_reply(
        $config, $chain, $sig_cert, $sig_key);

CONFIGURATION

The configuration parameter contains all variables that don't have to be necessarily provided for every request.

In case, the key that is used to sign a message is encrypted, passin contains the information on how to obtain the encryption password. Possible values are plain, pass and env. plain is the default value meaning the key is not encrypted. Note that IF the key is encrypted and passin is set to plain, the program will fail and not prompt for a password during runtime. env means that the password is stored in an environment variable called env. In case the environment variable is already occupied and another variable must be used, change the following code line in LibSCEP.xs and re-compile the XS-code.

        if(!strcmp(config->passin, "env")) {

The last option is to set passin to pass and provide the password directly using the configuration parameter passwd.

The hash algorithm used for signing can be changed with sigalg whereas the default encryption algorithm can be changed with encalg. The names of the algorithms are those used by OpenSSL. The names are directly passed to the OpenSSL functions EVP_get_digestbyname() and EVP_get_cipherbyname() respectively.

Per default, Crypt::LibSCEP logs in DEBUG mode to stdout. The configuration allows to provide a path to a log-file via log that is then used instead of stdout. This is the only time when Crypt::LibSCEP works on files. When working with a persistent handle, changing the logfile one the handle is created is not allowed.

In case the user wants to change the level of verbosity, change the following code line in LibSCEP.xs and re-compile the XS-code. Possible values are FATAL, ERROR, WARN, INFO and DEBUG.

        s = scep_conf_set(config->handle, SCEPCFG_VERBOSITY, DEBUG);

An example configuration looks as the following:

        $config = {passin=>"pass", passwd=>"mypassword", sigalg=>"sha256",
        encalg=>"aes256", log=>"/path/to/logfile"};

Persistency

For every request, LibSCEP creates a handle that contains all information that needs to be persistent during a request, for example the cryptographic algorithms to be used. Once the request is completed, the handle is deleted. It is more efficient to create the handle once and work on it for the entire session. This especially comes to practice when dealing with engines. When creating a handle, encalg and sigalg will be obtained from $config and stored in $handle. Afterwards, only the handle has to be provided to requests. The algorithms don't have to be provided again as they are already stored in $handle.

For requests, the handle can be provided as a configuration parameter. Once the user has finished using the handle, it must be deleted manually by executing cleanup.

        $config = {sigalg=>"sha256", encalg=>"aes256"};
        $handle = Crypt::LibSCEP::create_handle($config);
        $certrep = Crypt::LibSCEP::create_error_reply(
        {passin=>"pass", passwd=>"mypassword", handle=>$handle},
        $sig_key, $sig_cert, $pkcsreq, "badRequest");
        Crypt::LibSCEP::cleanup($handle);

Handles can also be updated by simply adding a parameter to the configuration when using the handle. In the following example, all three CertRep messages will be generated using the encryption algorithm "aes256". $certrep2 updates the handle and changes sigalg to md5. $certrep2 and $certrep3 will therefore be generated using md5.

        $config = {sigalg=>"sha256", encalg=>"aes256"};
        $handle = Crypt::LibSCEP::create_handle($config);
        $certrep1 = Crypt::LibSCEP::create_error_reply(
        {passin=>"pass", passwd=>"mypassword", handle=>$handle},
        $sig_key, $sig_cert, $pkcsreq, "badRequest");
        $certrep2 = Crypt::LibSCEP::create_error_reply(
        {passin=>"pass", passwd=>"mypassword", sigalg=>"md5", handle=>$handle},
        $sig_key, $sig_cert, $pkcsreq, "badRequest");
        $certrep3 = Crypt::LibSCEP::create_error_reply(
        {passin=>"pass", passwd=>"mypassword", handle=>$handle},
        $sig_key, $sig_cert, $pkcsreq, "badRequest");
        Crypt::LibSCEP::cleanup($handle);

DEALING WITH ENGINES

LibSCEP supports the use of engines. However, Crypt::LibSCEP only supports PKCS#11 engines.

The workflow of working with engines is relatively easy and consists of two steps. Fist, a handle is created as described above. Second, configuration information of the engine is added to the handle using the command create_engine. Then, the handle can be used as before. LibSCEP will do all the work for you.

PKCS11 Engine

In order to configure a PKCS11 engine, the following information is required: engine label, path to the engine, path to the module and PIN. The create_engine command takes all these arguments in a single hash. When using a PKCS11 engine, the key is managed by the engine and is not accessible for the user. Instead of providing the key to the command, a key identifier which points to the key managed by the engine is provided instead.

A full workflow looks as the following.

        $handle = Crypt::LibSCEP::create_handle({});
        $label = "pkcs11";
        $so = "/path/to/engine_pkcs11.so";
        $module = "/path/to/module.so";
        $pin = "123456";
        $id = "FFFF";
        $engine_conf = {module => $module, label => $label, so => $so,
        pin => $pin};
        Crypt::LibSCEP::create_engine({handle=>$handle}, $engine_conf);
        $pkcsreq = Crypt::LibSCEP::pkcsreq({handle=>$handle},
        $id, $sig_cert, $enc_cert, $req);
        Crypt::LibSCEP::cleanup($handle);

Note: The current implementation does not allow for mixing engines. That means, every Crypt::LibSCEP command after create_engine must involve $handle until cleanup($handle) is executed. Using commands not involving this handle, for example trying to use multiple engines in parallel, results in erroneous behavior.

AUTHOR

Florian Ruechel, Gideon Knoeke, Scott Hardin

COPYRIGHT AND LICENSE

Copyright (C) 2017

Apache License, version 2.0, January 2004