Skip to content

Instantly share code, notes, and snippets.

@cmouse
Last active January 18, 2022 10:41
Show Gist options
  • Save cmouse/79fdd61bd74f0b1a80b5c7ce95ce2ab4 to your computer and use it in GitHub Desktop.
Save cmouse/79fdd61bd74f0b1a80b5c7ce95ce2ab4 to your computer and use it in GitHub Desktop.
Test code for PKCS#11
/* This code is released to Public Domain by the Author
*
* Compile using c++ -I/usr/include/p11-kit-1 -o test-pkcs11 test-pkcs11.cc -lp11-kit
*
* Usage example: ./test-pkcs11 yubico 0 02 12345678
*/
#include "p11-kit/p11-kit.h"
#include "p11-kit/pin.h"
#include "p11-kit/pkcs11.h"
#include <memory>
#include <cassert>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
int logError(const std::string& function, CK_RV rv)
{
std::cerr << function << " failed: (0x" << std::hex << rv << "): " << p11_kit_strerror(rv) << std::endl;
return 1;
}
CK_RV Hash(CK_FUNCTION_LIST_PTR functions, CK_SESSION_HANDLE& handle, const std::string& input, std::string& output)
{
CK_RV rv;
CK_MECHANISM mech;
mech.mechanism = CKM_SHA256;
mech.pParameter = nullptr;
mech.ulParameterLen = 0;
CK_BYTE buffer[256 / 8];
CK_ULONG olen = sizeof(buffer);
if ((rv = functions->C_DigestInit(handle, &mech)) != CKR_OK)
logError("C_Digest()", rv);
else if ((rv = functions->C_Digest(handle, reinterpret_cast<unsigned char*>(const_cast<char*>(input.data())), input.size(), buffer, &olen)) != CKR_OK) {
logError("C_Digest()", rv);
} else {
output.assign(reinterpret_cast<char*>(buffer), olen);
}
return rv;
}
CK_RV Sign(CK_FUNCTION_LIST_PTR functions, CK_SESSION_HANDLE& handle, const std::string& input, CK_OBJECT_HANDLE private_key, std::string& output)
{
CK_RV rv;
CK_BYTE buffer[1024];
CK_ULONG buflen = sizeof buffer; // should be enough for most signatures.
std::string hashed;
if (Hash(functions, handle, input, hashed) != CKR_OK)
return rv;
CK_MECHANISM mech;
// mech.mechanism = CKM_SHA512_RSA_PKCS;
mech.mechanism = CKM_ECDSA;
mech.pParameter = nullptr;
mech.ulParameterLen = 0;
// perform signature
if ((rv = functions->C_SignInit(handle, &mech, private_key)) != CKR_OK) {
logError("C_SignInit()", rv);
return rv;
}
if ((rv = functions->C_Sign(handle, reinterpret_cast<unsigned char*>(const_cast<char*>(hashed.data())), hashed.size(), buffer, &buflen)) != CKR_OK)
logError("C_Sign()", rv);
/* if ((rv = functions->C_SignFinal(handle, buffer, &buflen) != CKR_OK))
logError("C_SignFinal()", rv); */
if (rv == CKR_OK)
output.assign(reinterpret_cast<char*>(buffer), buflen);
return rv;
}
std::string hexToBin(const std::string& value)
{
assert(value.size() % 2 == 0);
std::string ret;
for(size_t i = 0; i < value.size(); i += 2) {
char buf[3];
buf[0] = value.at(i);
buf[1] = value.at(i+1);
buf[2] = '\0';
long value = std::strtol(buf, NULL, 16);
ret.append(1, static_cast<char>(value));
}
return ret;
}
int main(int argc, const char* argv[])
{
CK_RV rv;
CK_OBJECT_HANDLE private_key;
size_t plen;
if (argc < 5) {
std::cerr << "Usage: " << argv[0] << " module slot id pin" << std::endl;
return 1;
}
auto modules = p11_kit_modules_load_and_initialize(0);
auto functions = p11_kit_module_for_name(modules, argv[1]);
if (functions == NULL) {
std::cerr << "Module '"<< argv[1] << "' is not valid" << std::endl;
return 1;
}
auto pin = p11_kit_pin_new_for_string(argv[4]);
CK_ULONG slotCount;
if ((rv = functions->C_GetSlotList(CK_TRUE, NULL, &slotCount)) != CKR_OK)
return logError("C_GetSlotList()", rv);
auto slots = std::make_unique<CK_SLOT_ID[]>(slotCount);
if ((rv = functions->C_GetSlotList(CK_TRUE, slots.get(), &slotCount)) != CKR_OK)
return logError("C_GetSlotList()", rv);
CK_TOKEN_INFO info;
memset(&info, 0, sizeof(info));
if ((rv = functions->C_GetTokenInfo(atoi(argv[2]), &info)) != CKR_OK)
return logError("C_GetTokenInfo(" + std::string(argv[2]) + ")", rv);
info.label[sizeof(info.label)-1] = '\0';
info.manufacturerID[sizeof(info.manufacturerID)-1] = '\0';
info.model[sizeof(info.model)-1] = '\0';
info.serialNumber[sizeof(info.serialNumber)-1] = '\0';
std::cout << "Module : " << argv[1] << std::endl;
std::cout << "Slot : " << atoi(argv[2]) << std::endl;
std::cout << "Label : " << info.label << std::endl;
std::cout << "Manufacturer : " << info.manufacturerID << std::endl;
std::cout << "Model : " << info.model << std::endl;
std::cout << "Serial number: " << info.serialNumber << std::endl;
CK_SESSION_HANDLE handle;
if ((rv = functions->C_OpenSession(atoi(argv[2]), CKF_SERIAL_SESSION|CKF_RW_SESSION, 0, 0, &handle)) != CKR_OK) {
return logError("C_OpenSession failed(" + std::string(argv[2]) + ")", rv);
}
assert(handle != CK_INVALID_HANDLE);
if ((rv = functions->C_Login(handle, CKU_USER, const_cast<unsigned char*>(p11_kit_pin_get_value(pin, &plen)), p11_kit_pin_get_length(pin))) != CKR_OK) {
return logError("C_Login()", rv);
}
unsigned long cka_class = CKO_PRIVATE_KEY;
std::string id = hexToBin(argv[3]);
/* find us a private key */
CK_ATTRIBUTE attrs[2];
attrs[0].type = CKA_CLASS;
attrs[0].pValue = &cka_class;
attrs[0].ulValueLen = sizeof(cka_class);
attrs[1].type = CKA_ID;
attrs[1].pValue = const_cast<char*>(id.data());
attrs[1].ulValueLen = id.size();
if ((rv = functions->C_FindObjectsInit(handle, attrs, 2)) != CKR_OK)
return logError("C_FindObjectsInit()", rv);
CK_ULONG count;
if ((rv = functions->C_FindObjects(handle, &private_key, 1UL, &count)) != CKR_OK)
return logError("C_FindObjects()", rv);
if (count < 1) {
std::cerr << "Cannot find any objects" << std::endl;
}
if ((rv = functions->C_FindObjectsFinal(handle)) != CKR_OK)
return logError("C_FindObjectsFinal()", rv);
std::string input = "the quick brown fox jumped over the lazy dog";
std::string output;
std::cout << "Input : " << input << std::endl;
if ((rv = Sign(functions, handle, input, private_key, output)) != CKR_OK)
return 1;
std::cout << "Output : ";
for(char ch : output)
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(static_cast<unsigned char>(ch));
std::cout << std::endl;
if ((rv = Sign(functions, handle, input, private_key, output)) != CKR_OK)
return 1;
std::cout << "Output : ";
for(char ch : output)
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(static_cast<unsigned char>(ch));
std::cout << std::endl;
return 0;
}
@qpernil
Copy link

qpernil commented Jan 18, 2022

Hi;

pkcs11-tool shows the following for key id '02'

Private Key Object; RSA
label: Private key for Digital Signature
ID: 02
Usage: decrypt, sign
Access: always authenticate, sensitive, always sensitive, never extractable, local

The always authenticate access means you need to validate the PIN before each signature.
To re-authenticate you need to use C_Login(CKU_CONTEXT_SPECIFIC, ...) after calling C_SignInit (so there is an ongoing sign operation) and before C_Sign (or C_SignUpdate / C_SignFinal of you were useing those), which terminates the operation (except for particular situations, specifically if you get CKR_BUFFER_TOO_SMALL or if you send in NULL for the buffer, to get the required length).

Please note that PKCS#11 id 02 is the sign key is slot 9c in PIV. This mapping was chosen to be compatible with the id:s used by the opensc pkcs11 library.

Please also note that the access reported here is hard-coded in the library, not determined from the device. This will be correct unless you have used non-default PIN policy when importing or generating the key(s). The library assumes the defaults (specified by PIV) that says slot 9C is always-auth and the rest not.

You code should work with any other key ID.

If you look at source code for pkcs11-tool and others, they typically check the CKA_ALWAYS_AUTH attribute and do this re-auth if true. Another approach is to just react to this particular error code with a re-auth. If you are going to perform many sign operations a combined approach might be best where you 'learn' if you need to re-auth on the first attempt, and then keep doing the same. This would be to avoid the extra commands that are always going to fail (since every sign 'uses up' the pin verification).

For libykcs11 there is also a special case allowed if you want to create a key and then sign with it in the same pkcs11 session. To create or generate a key you need to log in as SO (which authenticates using the PIV admin key), but to sign you need to verify the PIN. You can do this by using the same C_Login(CKU_CONTEXT_SPECIFIC,...) when you have a SO session.

Best regards!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment