Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for pkcs11 engine signing #1

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cmake-single-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y openssl libssl-dev python3
run: sudo apt-get update && sudo apt-get install -y openssl libssl-dev python3 softhsm2 opensc libengine-pkcs11-openssl
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
Expand Down
141 changes: 121 additions & 20 deletions stm32mp-sign-tool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/ecdsa.h>
#include <openssl/engine.h>

static bool verbose = false;
static ENGINE* engine = nullptr;

struct STM32Header {
char magic[4];
Expand Down Expand Up @@ -62,17 +64,20 @@ std::vector<unsigned char> get_raw_pubkey(EC_KEY* key) {
if (!x || !y) {
if (x) BN_free(x);
if (y) BN_free(y);
throw std::runtime_error("Failed to allocate BIGNUM");
std::cerr << "Failed to allocate BIGNUM" << std::endl;
return {};
}
if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) {
BN_free(x);
BN_free(y);
throw std::runtime_error("Failed to get affine coordinates");
std::cerr << "Failed to get affine coordinates" << std::endl;
return {};
}
if (BN_bn2binpad(x, pubkey.data(), 32) != 32 || BN_bn2binpad(y, pubkey.data() + 32, 32) != 32) {
BN_free(x);
BN_free(y);
throw std::runtime_error("Failed to convert BIGNUM to binary");
std::cerr << "Failed to convert BIGNUM to binary" << std::endl;
return {};
}
BN_free(x);
BN_free(y);
Expand All @@ -95,7 +100,8 @@ int key_algorithm(EC_KEY* key) {
if (nid == NID_X9_62_prime256v1) {
return -1;
}
throw std::runtime_error("Unsupported ECDSA curve");
std::cerr << "Unsupported ECDSA curve" << std::endl;
return -1;
}

void print_hex(const std::string& label, const std::vector<unsigned char>& data) {
Expand All @@ -108,23 +114,92 @@ void print_hex(const std::string& label, const std::vector<unsigned char>& data)
std::cout << std::endl;
}

EC_KEY* load_key(const char* key_file) {
FILE* key_fp = fopen(key_file, "r");
if (!key_fp) {
throw std::runtime_error("Failed to open key file");
EC_KEY* load_key(const char* key_file, const char* passphrase) {
EC_KEY* ec_key = nullptr;
if (!key_file || std::strlen(key_file) == 0) {
std::cerr << "Key file path is empty" << std::endl;
return nullptr;
}
if (std::strncmp(key_file, "pkcs11:", 7) == 0) {
// Load key using PKCS#11

// Load the engine
ENGINE_load_builtin_engines();
engine = ENGINE_by_id("pkcs11");
if (!engine) {
std::cerr << "Failed to load PKCS#11 engine" << std::endl;
return nullptr;
}

EC_KEY* key = PEM_read_ECPrivateKey(key_fp, nullptr, nullptr, nullptr);
fclose(key_fp);
if (!key) {
throw std::runtime_error("Failed to read key");
// Initialize the engine
if (!ENGINE_init(engine)) {
ENGINE_free(engine);
std::cerr << "Failed to initialize PKCS#11 engine" << std::endl;
return nullptr;
}

// Set the PIN
if (passphrase && !ENGINE_ctrl_cmd_string(engine, "PIN", passphrase, 0)) {
ENGINE_finish(engine);
ENGINE_free(engine);
std::cerr << "Failed to set PKCS#11 PIN" << std::endl;
return nullptr;
}

// Load the private key
EVP_PKEY* pkey = ENGINE_load_private_key(engine, key_file, nullptr, nullptr);
if (!pkey) {
ENGINE_finish(engine);
ENGINE_free(engine);
std::cerr << "Failed to load private key from PKCS#11" << std::endl;
return nullptr;
}

// Extract the EC_KEY from the EVP_PKEY
ec_key = EVP_PKEY_get1_EC_KEY(pkey);
EVP_PKEY_free(pkey);

if (!ec_key) {
ENGINE_finish(engine);
ENGINE_free(engine);
std::cerr << "Failed to extract EC_KEY from EVP_PKEY" << std::endl;
return nullptr;
}
}
else {
// Load key from file
FILE* key_fp = fopen(key_file, "r");
if (!key_fp) {
std::cerr << "Failed to open key file" << std::endl;
return nullptr;
}

ec_key = PEM_read_ECPrivateKey(key_fp, nullptr, nullptr, (void*)passphrase);
fclose(key_fp);
if (!ec_key) {
std::cerr << "Failed to read key from file" << std::endl;
return nullptr;
}
}

return key;
return ec_key;
}

int verify_stm32_image(const std::vector<unsigned char>& image, const char* key_file) {
EC_KEY* key = load_key(key_file);
int verify_stm32_image(const std::vector<unsigned char>& image, const char* key_file, const char* passphrase) {
if (image.empty()) {
std::cerr << "Image data is empty" << std::endl;
return -1;
}
if (!key_file || std::strlen(key_file) == 0) {
std::cerr << "Key file path is empty" << std::endl;
return -1;
}
EC_KEY* key = load_key(key_file, passphrase);
if (!key) {
std::cerr << "Failed to load key" << std::endl;
return -1;
}

STM32Header header = unpack_stm32_header(image);

if (std::strncmp(header.magic, "STM2", sizeof(header.magic)) != 0) {
Expand All @@ -134,6 +209,10 @@ int verify_stm32_image(const std::vector<unsigned char>& image, const char* key_
}

std::vector<unsigned char> pubkey = get_raw_pubkey(key);
if (pubkey.empty()) {
EC_KEY_free(key);
return -1;
}
print_hex("Public Key", pubkey);

if (std::memcmp(header.ecdsa_pubkey, pubkey.data(), pubkey.size()) != 0) {
Expand Down Expand Up @@ -196,8 +275,21 @@ int verify_stm32_image(const std::vector<unsigned char>& image, const char* key_
}
}

int sign_stm32_image(std::vector<unsigned char>& image, const char* key_file) {
EC_KEY* key = load_key(key_file);
int sign_stm32_image(std::vector<unsigned char>& image, const char* key_file, const char* passphrase) {
if (image.empty()) {
std::cerr << "Image data is empty" << std::endl;
return -1;
}
if (!key_file || std::strlen(key_file) == 0) {
std::cerr << "Key file path is empty" << std::endl;
return -1;
}
EC_KEY* key = load_key(key_file, passphrase);
if (!key) {
std::cerr << "Failed to load key" << std::endl;
return -1;
}

STM32Header header = unpack_stm32_header(image);

if (std::strncmp(header.magic, "STM2", sizeof(header.magic)) != 0) {
Expand All @@ -212,6 +304,10 @@ int sign_stm32_image(std::vector<unsigned char>& image, const char* key_file) {

// Get the public key from the private key
std::vector<unsigned char> pubkey = get_raw_pubkey(key);
if (pubkey.empty()) {
EC_KEY_free(key);
return -1;
}
print_hex("Public Key", pubkey);

std::memcpy(header.ecdsa_pubkey, pubkey.data(), pubkey.size());
Expand Down Expand Up @@ -264,7 +360,7 @@ int sign_stm32_image(std::vector<unsigned char>& image, const char* key_file) {
EC_KEY_free(key);

// Verify the signature
if (verify_stm32_image(image, key_file)) {
if (verify_stm32_image(image, key_file, passphrase)) {
return 1;
}

Expand Down Expand Up @@ -296,7 +392,7 @@ int main(int argc, char* argv[]) {
output_file = optarg;
break;
default:
std::cerr << "Usage: " << argv[0] << " -k key_file [-p passphrase] [-v] [-i input_file] [-o output_file]" << std::endl;
std::cerr << "Usage: " << argv[0] << " -k key_file [-p passphrase/pin] [-v] [-i input_file] [-o output_file]" << std::endl;
return 1;
}
}
Expand All @@ -311,7 +407,7 @@ int main(int argc, char* argv[]) {
std::vector<unsigned char> image((std::istreambuf_iterator<char>(image_file)), std::istreambuf_iterator<char>());
image_file.close();

if (sign_stm32_image(image, key_file) != 0) {
if (sign_stm32_image(image, key_file, passphrase) != 0) {
return 1;
}

Expand All @@ -322,5 +418,10 @@ int main(int argc, char* argv[]) {
}
}

if (engine) {
ENGINE_finish(engine);
ENGINE_free(engine);
}

return 0;
}
22 changes: 21 additions & 1 deletion stm32mp-sign-tool_test.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/bin/bash -e
#!/bin/sh -ex
#
# Copyright (c) 2024
# Embetrix Embedded Systems Solutions, [email protected]
#


dd if=/dev/urandom of=image.bin bs=1M count=1 > /dev/null 2>&1
Expand All @@ -8,4 +12,20 @@ python3 stm32mp-gen-image.py image.stm32 image.bin
openssl ecparam -name prime256v1 -genkey -out private_key.pem
openssl ec -in private_key.pem -pubout -out public_key.pem

# test plain key file
./stm32mp-sign-tool -v -k private_key.pem -i image.stm32 -o image.stm32.signed

# test pkcs11 key
export PKCS11_MODULE_PATH=/usr/lib/softhsm/libsofthsm2.so
export PIN="12345"
export SO_PIN="1234"
export SOFTHSM2_CONF=$PWD/.softhsm/softhsm2.conf
export TOKEN_NAME="token0"

mkdir -p .softhsm/tokens
echo "directories.tokendir = $PWD/.softhsm/tokens" > .softhsm/softhsm2.conf
pkcs11-tool --pin $PIN --module $PKCS11_MODULE_PATH --slot-index=0 --init-token --label=$TOKEN_NAME --so-pin $SO_PIN --init-pin
pkcs11-tool --pin $PIN --module $PKCS11_MODULE_PATH --keypairgen --key-type EC:prime256v1 --id 1 --label "testkeyECp256"
./stm32mp-sign-tool -v -k "pkcs11:object=testkeyECp256" -i image.stm32 -p 12345 -o image.stm32.signed


Loading