Skip to content

gamringer/php-pkcs11

Repository files navigation

PKCS11 bindings for PHP

Tested with the following HSMs:

  • SafeNET Luna SA 4
  • SoftHSM 2.6
  • Nitrokey HSM 2
  • AWS CloudHSM

Supports PHP version >= 7.4

Compile

phpize
./configure
make

Running tests

To make tests, ensure that SoftHSM2 is installed, configured and initialized.

Install SoftHSM2

From Source

  1. Get the source
  2. Extract Archive
  3. cd /path/to/extracted/archive
  4. ./configure
  5. make
  6. make install

Module will be located in /usr/local/lib/softhsm/libsofthsm2.so

From Ubuntu package repository

As of this writing, Ubuntu 20.04 provides v2.2 of SoftHSM2 in which not all mechanisms will be available

  1. sudo apt install softhsm2

Module will be located in /usr/lib/softhsm/libsofthsm2.so

Configure and initialize SoftHSM2

  1. Create a directory where HSM files will be stored /home/user/.softhsm
  2. Create a configuration file in your home directory ~/.config/softhsm2/softhsm2.conf
  3. Initialize token softhsm2-util --init-token --slot 0 --label "My token 1" --pin 123456 --so-pin 12345678
  4. Grab the resulting slot ID softhsm2-util --show-slots

Example configuration file

directories.tokendir = /home/user/.softhsm
objectstore.backend = file
log.level = INFO
slots.removable = false
slots.mechanisms = ALL

Run tests

export PHP11_MODULE=/path/to/libsofthsm2.so
export PHP11_SLOT={SLOT_ID_FROM_INITIALIZATION}
export PHP11_PIN=123456

make test

How to use

All examples assume the use of a locally compiled installation of SoftHSM, but it will work with other modules as well.

Loading a module

To load a PKCS11 module, create a new PKCS11\Module object.

$modulePath = '/path/to/libsofthsm2.so';
$module = new Pkcs11\Module($modulePath);

From the PKCS11\Module object, you can call most PKCS11 methods.

To get information the module:

$moduleInfo = $module->getInfo();

Slot Information

There are 2 methods to retrieve slots information. getSlotList can be used to retrieve a simple list of slots like C_GetSlotList would, while getSlots returns de result of C_GetSlotInfo for each available slot.

$slotList = $module->getSlotList();
$slots = $module->getSlots();

$slotId = $slotList[0];

$slotInfo = $module->getSlotInfo($slotId);

Token Information

$tokenInfo = $module->getTokenInfo($slotId);

Mechanisms

All mechanisms declared in PKCS11 version 3 are available under the Pkcs11 namespace.

$mechanismList = $module->getMechanismList($slotId);
$mechanismInfo = $module->getMechanismInfo($slotId, Pkcs11\CKM_AES_GCM);

Initializing a token

This extension supports initializing simple tokens via the C_InitToken function.

$module->initToken($slotId, $label, $soPin);

Opening a session

You can open a session and login as either a Security Officer or user. From the returned Pkcs11\Session object, more methods are available.

$session = $module->openSession($slotId, Pkcs11\CKF_RW_SESSION);
$session->login(Pkcs11\CKU_SO, $soPin);
$sessionInfo = $session->getInfo();
$session = $module->openSession($slotId, Pkcs11\CKF_RW_SESSION);
$session->login(Pkcs11\CKU_USER, $userPin);
$sessionInfo = $session->getInfo();

PIN Management

As a Security Officer, the user PIN can be set to any value.

$session->login(Pkcs11\CKU_SO, $soPin);
$session->initPin($userPin);
$session->logout();

As either user, the current user PIN can be changed.

$session->login(Pkcs11\CKU_SO, $soPin);
$session->setPin($soPin, $newPin);
$session->logout();
$session->login(Pkcs11\CKU_USER, $userPin);
$session->setPin($userPin, $newPin);
$session->logout();

Generating Keys

You can generate symmetric keys for any available mechanism.

$key = $session->generateKey(new Pkcs11\Mechanism(Pkcs11\CKM_AES_KEY_GEN), [
  Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
  Pkcs11\CKA_SENSITIVE => true,
  Pkcs11\CKA_ENCRYPT => true,
  Pkcs11\CKA_DECRYPT => true,
  Pkcs11\CKA_VALUE_LEN => 32,
  Pkcs11\CKA_KEY_TYPE => Pkcs11\CKK_AES,
  Pkcs11\CKA_LABEL => "Test AES",
  Pkcs11\CKA_PRIVATE => true,
]);
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_EC_KEY_PAIR_GEN), [
  Pkcs11\CKA_VERIFY => true,
  Pkcs11\CKA_LABEL => "Test ECDSA Public",
  Pkcs11\CKA_EC_PARAMS => hex2bin('06082A8648CE3D030107'),
],[
  Pkcs11\CKA_TOKEN => false,
  Pkcs11\CKA_PRIVATE => true,
  Pkcs11\CKA_SENSITIVE => true,
  Pkcs11\CKA_SIGN => true,
  Pkcs11\CKA_LABEL => "Test ECDSA Private",
]);

$pkey = $keypair->pkey;
$skey = $keypair->skey;

Encrypt/Decrypt

Given a symmetric key, you can encrypt something. Certain encryption mechanisms require a special Parameters object. Currently, the following are available:

  • Pkcs11\GcmParams
  • Pkcs11\Salsa20Params
  • Pkcs11\ChaCha20Params
  • Pkcs11\Salsa20Chacha20Poly1305Params
$iv = random_bytes(16);
$aad = '';
$tagLength = 128;
$gcmParams = new Pkcs11\GcmParams($iv, $aad, $tagLength);

$data = 'Hello World!';
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_AES_GCM, $gcmParams);
$ciphertext = $key->encrypt($mechanism, $data);
var_dump(bin2hex($ciphertext));
// string(56) "67940e19213d68c88d163b12d6cd565300f70d693309b5b744085b35"

$plaintext = $key->decrypt($mechanism, $ciphertext);
var_dump($plaintext);
// string(12) "Hello World!"

AWS CloudHSM does things in a particular way. The initialization vector is generated by the HSM for you and returned prepended to the ciphertext.

$aad = '';
$tagLength = 128;
// No need to specify the $iv
$gcmParams = new Pkcs11\GcmParams('', $aad, $tagLength);

$data = 'Hello World!';
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_CLOUDHSM_AES_GCM, $gcmParams);
$ciphertext = $key->encrypt($mechanism, $data);
var_dump(bin2hex($ciphertext));
// string(88) "0000000000000000000000000000000067940e19213d68c88d163b12d6cd565300f70d693309b5b744085b35"

$plaintext = $key->decrypt($mechanism, $ciphertext);
var_dump($plaintext);
// string(12) "Hello World!"

Given an RSA public key, you can encrypt a symmetric key. Similarly to symmetric mechanisms, some asymmetric encryption mechanisms require a special Parameters object. Currently, the following are available:

  • Pkcs11\RsaOaepParams
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), [
  Pkcs11\CKA_ENCRYPT => true,
  Pkcs11\CKA_MODULUS_BITS => 2048,
  Pkcs11\CKA_PUBLIC_EXPONENT => hex2bin('010001'),
  Pkcs11\CKA_LABEL => "Test RSA Encrypt Public",
],[
  Pkcs11\CKA_TOKEN => false,
  Pkcs11\CKA_PRIVATE => true,
  Pkcs11\CKA_SENSITIVE => true,
  Pkcs11\CKA_DECRYPT => true,
  Pkcs11\CKA_LABEL => "Test RSA Encrypt Private",
]);

// SoftHSM2 only supports CKG_MGF1_SHA1
$oaepParam = new Pkcs11\RsaOaepParams(Pkcs11\CKM_SHA_1, Pkcs11\CKG_MGF1_SHA1);
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_OAEP, $oaepParam);

$symkey = random_bytes(32);
$ciphertext = $keypair->pkey->encrypt($mechanism, $symkey);
var_dump($ciphertext);

$plaintext = $keypair->skey->decrypt($mechanism, $ciphertext);
var_dump($plaintext);

Derivation

Given an EC public key, you can derive a shared secret.

// P-384
$domainParameters = hex2bin('06052B81040022');
$rawPublickeyOther = hex2bin('049f0a09e8a6fc87f4804642c782b2cd3e566b3e62262090d94e12933a00916f559b62ea33197706a302f0722b781a9349ea8f0f2bcea854cdcf5d9ff0e0a19c3c35d63578292307d1d83031c0134700c2990ed5b38f6c92245103c2c1352132a3');

$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_EC_KEY_PAIR_GEN), [
  Pkcs11\CKA_LABEL => "Test ECDH Public",
  Pkcs11\CKA_EC_PARAMS => $domainParameters,
],[
  Pkcs11\CKA_PRIVATE => true,
  Pkcs11\CKA_SENSITIVE => true,
  Pkcs11\CKA_DERIVE => true,
  Pkcs11\CKA_LABEL => "Test ECDH Private",
]);

$shared = '';

// SoftHSM2 only supports CKD_NULL
$params = new Pkcs11\Ecdh1DeriveParams(Pkcs11\CKD_NULL, $shared, $rawPublickeyOther);
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_ECDH1_DERIVE, $params);
$secret = $keypair->skey->derive($mechanism, [
  Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
  Pkcs11\CKA_KEY_TYPE => Pkcs11\CKK_AES,
  Pkcs11\CKA_SENSITIVE => false,
  Pkcs11\CKA_EXTRACTABLE => true,
  Pkcs11\CKA_ENCRYPT => true,
  Pkcs11\CKA_DECRYPT => true,
]);

$rawSecret = $secret->getAttributeValue([Pkcs11\CKA_VALUE])[Pkcs11\CKA_VALUE];

Retrieving object by URI

You have the ability to retrieve object using RFC7512 URIs.

Note: Currently, only the following path attributes are supported:

  • id
  • object
  • type
$session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), [
  Pkcs11\CKA_ENCRYPT => true,
  Pkcs11\CKA_MODULUS_BITS => 2048,
  Pkcs11\CKA_PUBLIC_EXPONENT => hex2bin('010001'),
  Pkcs11\CKA_LABEL => "testPkcs11Url",
  Pkcs11\CKA_ID => "testPkcs11UrlPublicId",
],[
  Pkcs11\CKA_TOKEN => false,
  Pkcs11\CKA_PRIVATE => true,
  Pkcs11\CKA_SENSITIVE => true,
  Pkcs11\CKA_DECRYPT => true,
  Pkcs11\CKA_LABEL => "testPkcs11Url",
  Pkcs11\CKA_ID => "testPkcs11UrlPrivateId",
]);

$privateKeySearchResult = $session->openUri("pkcs11:object=testPkcs11Url;type=private;");

$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_SHA256_RSA_PKCS);

$data = "Hello World!";
$signature = $privateKeySearchResult[0]->sign($mechanism, $data);
var_dump(bin2hex($signature));

$publicKeySearchResult = $session->openUri("pkcs11:id=testPkcs11UrlPublicId");
$valid = $publicKeySearchResult[0]->verify($mechanism, $data, $signature);
var_dump($valid);

Retrieve object attributes

Given an Object, you can retrieve it's readable attributes.

Note: the following attributes are not implemented and retrieving them throws an exception:

  • CKA_WRAP_TEMPLATE
  • CKA_UNWRAP_TEMPLATE
  • CKA_DERIVE_TEMPLATE

Note: the following attributes internally provide a struct describing the date, but are here returned as a string:

  • CKA_START_DATE
  • CKA_END_DATE
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), /* ... */);

$attributes = $keypair->skey->getAttributeValue([
  Pkcs11\CKA_TOKEN,
  Pkcs11\CKA_PRIVATE,
  Pkcs11\CKA_SENSITIVE,
  Pkcs11\CKA_EXTRACTABLE,
  Pkcs11\CKA_NEVER_EXTRACTABLE,
  Pkcs11\CKA_ALWAYS_SENSITIVE,
  Pkcs11\CKA_SIGN,
  Pkcs11\CKA_DECRYPT,
  Pkcs11\CKA_PUBLIC_EXPONENT,
  Pkcs11\CKA_LABEL,
]);

var_dump($attributes[Pkcs11\CKA_TOKEN]);
var_dump($attributes[Pkcs11\CKA_PRIVATE]);
var_dump($attributes[Pkcs11\CKA_SENSITIVE]);
var_dump($attributes[Pkcs11\CKA_EXTRACTABLE]);
var_dump($attributes[Pkcs11\CKA_NEVER_EXTRACTABLE]);
var_dump($attributes[Pkcs11\CKA_ALWAYS_SENSITIVE]);
var_dump($attributes[Pkcs11\CKA_SIGN]);
var_dump($attributes[Pkcs11\CKA_DECRYPT]);
var_dump(bin2hex($attributes[Pkcs11\CKA_PUBLIC_EXPONENT]));
var_dump($attributes[Pkcs11\CKA_LABEL]);

/* Outputs:
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(false)
string(6) "010001"
string(16) "Test RSA Private"
*/