|
| 1 | +\page pkcs11_sal PKCS#11 SAL — Usage Guide |
| 2 | + |
| 3 | +# PKCS#11 SAL — Usage Guide |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The STSE PKCS#11 provider layer (`sal/pkcs11/`) maps the standard PKCS#11 v2.40 |
| 8 | +Cryptoki API to the STSELib services. This allows a STSAFE‑A110 or STSAFE‑A120 |
| 9 | +device to act as a standard hardware security token for high-level applications |
| 10 | +such as **OpenSSL**, **StrongSwan**, **OpenSSH**, and **PKCS#11-aware TLS stacks** |
| 11 | +without any application-specific changes. |
| 12 | + |
| 13 | +``` |
| 14 | +Application (OpenSSL / StrongSwan / OpenSSH / mbedTLS) |
| 15 | + │ standard PKCS#11 v2.40 Cryptoki calls │ |
| 16 | + ▼ │ |
| 17 | + ┌─────────────────────────────────────────────────┤ |
| 18 | + │ sal/pkcs11/stse_cryptoki.c │ |
| 19 | + │ (C_Initialize, C_Sign, C_GenerateKeyPair …) │ |
| 20 | + ├─────────────────────────────────────────────────┤ |
| 21 | + │ sal/pkcs11/stse_pkcs11.c │ |
| 22 | + │ (object enumeration, format conversion) │ |
| 23 | + ├─────────────────────────────────────────────────┤ |
| 24 | + │ STSELib Core — API / Services / Core layers │ |
| 25 | + └─────────────────────────────────────────────────┘ |
| 26 | + │ I2C / ST1Wire │ |
| 27 | + ▼ ▼ |
| 28 | + STSAFE-A110 / STSAFE-A120 |
| 29 | +``` |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## Build integration |
| 34 | + |
| 35 | +### 1. Enable the PKCS#11 provider in `stse_conf.h` |
| 36 | + |
| 37 | +Add the following define to your project's `stse_conf.h` **alongside** the usual |
| 38 | +STSAFE-A device and algorithm enables: |
| 39 | + |
| 40 | +```c |
| 41 | +/* Enable the PKCS#11 provider layer */ |
| 42 | +#define STSE_CONF_PKCS11_SUPPORT |
| 43 | + |
| 44 | +/* The PKCS#11 layer requires at least STSAFE-A support and SHA-256 */ |
| 45 | +#define STSE_CONF_STSAFE_A_SUPPORT |
| 46 | +#define STSE_CONF_ECC_NIST_P_256 |
| 47 | +#define STSE_CONF_HASH_SHA_256 |
| 48 | +``` |
| 49 | + |
| 50 | +Optional algorithms automatically expand the set of supported PKCS#11 mechanisms: |
| 51 | + |
| 52 | +| `stse_conf.h` define | PKCS#11 mechanism(s) added | |
| 53 | +|-----------------------------|-------------------------------------| |
| 54 | +| `STSE_CONF_ECC_NIST_P_256` | `CKM_EC_KEY_PAIR_GEN`, `CKM_ECDSA`, `CKM_ECDSA_SHA256`, `CKM_ECDH1_DERIVE` | |
| 55 | +| `STSE_CONF_ECC_NIST_P_384` | P-384 variants of the above | |
| 56 | +| `STSE_CONF_ECC_NIST_P_521` | P-521 variants of the above | |
| 57 | +| `STSE_CONF_HASH_SHA_256` | `CKM_SHA256`, `CKM_ECDSA_SHA256` | |
| 58 | +| `STSE_CONF_HASH_SHA_384` | `CKM_SHA384`, `CKM_ECDSA_SHA384` | |
| 59 | +| `STSE_CONF_HASH_SHA_512` | `CKM_SHA512`, `CKM_ECDSA_SHA512` | |
| 60 | + |
| 61 | +### 2. Add source files to your build |
| 62 | + |
| 63 | +Add the following files to your build system (CMake, Makefile, IDE project, …): |
| 64 | + |
| 65 | +``` |
| 66 | +sal/pkcs11/stse_pkcs11.c |
| 67 | +sal/pkcs11/stse_cryptoki.c |
| 68 | +``` |
| 69 | + |
| 70 | +No external PKCS#11 SDK is needed — `sal/pkcs11/pkcs11.h` is a self-contained |
| 71 | +bundled header. |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Hardware configuration |
| 76 | + |
| 77 | +Before calling `C_Initialize()`, optionally supply a `stse_pkcs11_config_t` |
| 78 | +structure with your hardware parameters. If you skip this step the defaults |
| 79 | +(address `0x20`, bus 0, 400 kHz, device type `STSAFE_A120`) are used. |
| 80 | + |
| 81 | +```c |
| 82 | +#include "stselib.h" /* STSE_CONF_PKCS11_SUPPORT must be set */ |
| 83 | + |
| 84 | +void app_init(void) |
| 85 | +{ |
| 86 | + /* 1. (Optional) Set hardware parameters */ |
| 87 | + stse_pkcs11_config_t cfg = { |
| 88 | + .i2c_addr = 0x20, /* 7-bit I2C address of the STSAFE-A */ |
| 89 | + .bus_id = 0, /* Host I2C bus index */ |
| 90 | + .bus_speed = 400, /* Bus speed in kHz */ |
| 91 | + .device_type = STSAFE_A120, |
| 92 | + |
| 93 | + /* Map data zones to X.509 DER certificate objects. |
| 94 | + * cert_zone_count = 0 means no certificate objects are exposed. */ |
| 95 | + .cert_zone_count = 1, |
| 96 | + .cert_zone_indices = { 0 }, /* Zone 0 holds the device certificate */ |
| 97 | + }; |
| 98 | + stse_pkcs11_set_config(&cfg); |
| 99 | + |
| 100 | + /* 2. Initialise the Cryptoki library */ |
| 101 | + CK_RV rv = C_Initialize(NULL); |
| 102 | + if (rv != CKR_OK) { |
| 103 | + /* handle error */ |
| 104 | + } |
| 105 | +} |
| 106 | +``` |
| 107 | +
|
| 108 | +--- |
| 109 | +
|
| 110 | +## Supported PKCS#11 mechanisms |
| 111 | +
|
| 112 | +| Mechanism | Description | |
| 113 | +|--------------------------|--------------------------------------------------| |
| 114 | +| `CKM_EC_KEY_PAIR_GEN` | On-chip ECC key-pair generation (private key never leaves the device) | |
| 115 | +| `CKM_ECDSA` | Raw ECDSA — caller supplies the pre-computed digest | |
| 116 | +| `CKM_ECDSA_SHA256` | ECDSA with integrated SHA-256 hashing | |
| 117 | +| `CKM_ECDSA_SHA384` | ECDSA with integrated SHA-384 hashing (requires `STSE_CONF_HASH_SHA_384`) | |
| 118 | +| `CKM_ECDSA_SHA512` | ECDSA with integrated SHA-512 hashing (requires `STSE_CONF_HASH_SHA_512`) | |
| 119 | +| `CKM_SHA256` | Hardware SHA-256 digest | |
| 120 | +| `CKM_SHA384` | Hardware SHA-384 digest (requires `STSE_CONF_HASH_SHA_384`) | |
| 121 | +| `CKM_SHA512` | Hardware SHA-512 digest (requires `STSE_CONF_HASH_SHA_512`) | |
| 122 | +| `CKM_ECDH1_DERIVE` | ECDH key agreement — shared secret computed on hardware | |
| 123 | +
|
| 124 | +--- |
| 125 | +
|
| 126 | +## Object model |
| 127 | +
|
| 128 | +The token exposes **three types of PKCS#11 objects**, all backed by the |
| 129 | +STSAFE-A non-volatile storage: |
| 130 | +
|
| 131 | +| PKCS#11 class | Handle range | Maps to | |
| 132 | +|--------------------|-----------------|----------------------------------------| |
| 133 | +| `CKO_PRIVATE_KEY` | `0x001 – 0x0FF` | STSAFE-A private-key slot N | |
| 134 | +| `CKO_PUBLIC_KEY` | `0x100 – 0x1FF` | Corresponding public half of slot N | |
| 135 | +| `CKO_CERTIFICATE` | `0x200 – 0x2FF` | Data zone M (configured in `cert_zone_indices[]`) | |
| 136 | +
|
| 137 | +All private keys have `CKA_SENSITIVE = TRUE` and `CKA_EXTRACTABLE = FALSE`, |
| 138 | +enforcing the hardware security boundary. |
| 139 | +
|
| 140 | +--- |
| 141 | +
|
| 142 | +## Usage examples |
| 143 | +
|
| 144 | +### 1. Discover the token |
| 145 | +
|
| 146 | +```c |
| 147 | +CK_SLOT_ID slot_list[1]; |
| 148 | +CK_ULONG slot_count = 1; |
| 149 | +
|
| 150 | +C_GetSlotList(CK_TRUE, slot_list, &slot_count); |
| 151 | +/* slot_list[0] == 0 (single slot representing the STSAFE-A) */ |
| 152 | +``` |
| 153 | + |
| 154 | +### 2. Open a session and enumerate key objects |
| 155 | + |
| 156 | +```c |
| 157 | +CK_SESSION_HANDLE hSession; |
| 158 | +C_OpenSession(0, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &hSession); |
| 159 | + |
| 160 | +/* Search for all private keys */ |
| 161 | +CK_OBJECT_CLASS privkey_class = CKO_PRIVATE_KEY; |
| 162 | +CK_ATTRIBUTE search_tmpl[] = { |
| 163 | + { CKA_CLASS, &privkey_class, sizeof(privkey_class) } |
| 164 | +}; |
| 165 | + |
| 166 | +C_FindObjectsInit(hSession, search_tmpl, 1); |
| 167 | + |
| 168 | +CK_OBJECT_HANDLE handles[8]; |
| 169 | +CK_ULONG found = 0; |
| 170 | +C_FindObjects(hSession, handles, 8, &found); |
| 171 | +C_FindObjectsFinal(hSession); |
| 172 | + |
| 173 | +/* handles[0..found-1] are the private key handles */ |
| 174 | +``` |
| 175 | +
|
| 176 | +### 3. Generate a key pair in slot 0 |
| 177 | +
|
| 178 | +```c |
| 179 | +CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; |
| 180 | +
|
| 181 | +/* EC parameters OID for NIST P-256 (DER: 06 08 2A 86 48 CE 3D 03 01 07) */ |
| 182 | +static const CK_BYTE p256_oid[] = { |
| 183 | + 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 |
| 184 | +}; |
| 185 | +
|
| 186 | +CK_ATTRIBUTE pub_tmpl[] = { |
| 187 | + { CKA_EC_PARAMS, (void *)p256_oid, sizeof(p256_oid) } |
| 188 | +}; |
| 189 | +
|
| 190 | +/* CKA_ID = 0 means slot number 0 */ |
| 191 | +CK_BYTE slot_id = 0; |
| 192 | +CK_ATTRIBUTE priv_tmpl[] = { |
| 193 | + { CKA_ID, &slot_id, sizeof(slot_id) } |
| 194 | +}; |
| 195 | +
|
| 196 | +CK_OBJECT_HANDLE hPublic, hPrivate; |
| 197 | +CK_RV rv = C_GenerateKeyPair( |
| 198 | + hSession, &mech, |
| 199 | + pub_tmpl, 1, |
| 200 | + priv_tmpl, 1, |
| 201 | + &hPublic, &hPrivate); |
| 202 | +``` |
| 203 | + |
| 204 | +### 4. Sign data with ECDSA-SHA256 |
| 205 | + |
| 206 | +```c |
| 207 | +CK_MECHANISM sign_mech = { CKM_ECDSA_SHA256, NULL, 0 }; |
| 208 | + |
| 209 | +/* hPrivate is the handle returned by C_GenerateKeyPair or found by C_FindObjects */ |
| 210 | +C_SignInit(hSession, &sign_mech, hPrivate); |
| 211 | + |
| 212 | +CK_BYTE data[] = { /* application data to sign */ }; |
| 213 | +CK_BYTE signature[64]; /* 32 + 32 bytes for P-256 R||S */ |
| 214 | +CK_ULONG sig_len = sizeof(signature); |
| 215 | + |
| 216 | +CK_RV rv = C_Sign(hSession, data, sizeof(data), signature, &sig_len); |
| 217 | +``` |
| 218 | +
|
| 219 | +### 5. Digest data with SHA-256 |
| 220 | +
|
| 221 | +```c |
| 222 | +CK_MECHANISM hash_mech = { CKM_SHA256, NULL, 0 }; |
| 223 | +C_DigestInit(hSession, &hash_mech); |
| 224 | +
|
| 225 | +CK_BYTE digest[32]; |
| 226 | +CK_ULONG digest_len = sizeof(digest); |
| 227 | +
|
| 228 | +CK_RV rv = C_Digest(hSession, data, sizeof(data), digest, &digest_len); |
| 229 | +``` |
| 230 | + |
| 231 | +### 6. Read a certificate from a data zone |
| 232 | + |
| 233 | +```c |
| 234 | +/* CKA_ID = 0 selects the first configured certificate zone */ |
| 235 | +CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; |
| 236 | +CK_BYTE cert_id = 0; |
| 237 | +CK_ATTRIBUTE cert_search[] = { |
| 238 | + { CKA_CLASS, &cert_class, sizeof(cert_class) }, |
| 239 | + { CKA_ID, &cert_id, sizeof(cert_id) } |
| 240 | +}; |
| 241 | + |
| 242 | +C_FindObjectsInit(hSession, cert_search, 2); |
| 243 | + |
| 244 | +CK_OBJECT_HANDLE hCert; |
| 245 | +CK_ULONG cert_found = 0; |
| 246 | +C_FindObjects(hSession, &hCert, 1, &cert_found); |
| 247 | +C_FindObjectsFinal(hSession); |
| 248 | + |
| 249 | +/* Retrieve the DER-encoded certificate bytes */ |
| 250 | +CK_BYTE cert_der[2048]; |
| 251 | +CK_ATTRIBUTE cert_value[] = { |
| 252 | + { CKA_VALUE, cert_der, sizeof(cert_der) } |
| 253 | +}; |
| 254 | +C_GetAttributeValue(hSession, hCert, cert_value, 1); |
| 255 | +/* cert_der[0..cert_value[0].ulValueLen-1] contains the X.509 DER data */ |
| 256 | +``` |
| 257 | +
|
| 258 | +### 7. ECDH key agreement |
| 259 | +
|
| 260 | +```c |
| 261 | +/* peer_public_key_point: uncompressed EC point 0x04 || X || Y of the peer */ |
| 262 | +CK_BYTE peer_point[] = { 0x04, /* X bytes… */, /* Y bytes… */ }; |
| 263 | +
|
| 264 | +CK_ECDH1_DERIVE_PARAMS ecdh_params = { |
| 265 | + .kdf = CKD_NULL, |
| 266 | + .ulSharedDataLen = 0, |
| 267 | + .pSharedData = NULL, |
| 268 | + .ulPublicDataLen = sizeof(peer_point), |
| 269 | + .pPublicData = peer_point, |
| 270 | +}; |
| 271 | +
|
| 272 | +CK_MECHANISM derive_mech = { |
| 273 | + CKM_ECDH1_DERIVE, |
| 274 | + &ecdh_params, |
| 275 | + sizeof(ecdh_params) |
| 276 | +}; |
| 277 | +
|
| 278 | +CK_OBJECT_HANDLE hDerived; |
| 279 | +C_DeriveKey(hSession, &derive_mech, hPrivate, NULL, 0, &hDerived); |
| 280 | +``` |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +## Using with OpenSSL (libp11 / pkcs11-provider) |
| 285 | + |
| 286 | +The PKCS#11 SAL can be loaded as a shared library by any PKCS#11-aware OpenSSL |
| 287 | +engine or provider. The entry point required is `C_GetFunctionList`, which is |
| 288 | +already implemented in `stse_cryptoki.c`. |
| 289 | + |
| 290 | +**Example — TLS client using hardware-backed private key:** |
| 291 | + |
| 292 | +```bash |
| 293 | +# Load the shared library and reference the private key by its PKCS#11 URI |
| 294 | +openssl s_client \ |
| 295 | + -engine pkcs11 \ |
| 296 | + -keyform engine \ |
| 297 | + -key "pkcs11:token=STSAFE-A%20Token;object=0" \ |
| 298 | + -connect example.com:443 |
| 299 | +``` |
| 300 | + |
| 301 | +The PKCS#11 URI scheme uses the object ID (`CKA_ID`) set during key generation |
| 302 | +as the `object` field. Object ID `0` refers to hardware slot 0. |
| 303 | + |
| 304 | +--- |
| 305 | + |
| 306 | +## Using with OpenSSH |
| 307 | + |
| 308 | +Configure `~/.ssh/config` or `/etc/ssh/ssh_config` to load the PKCS#11 module: |
| 309 | + |
| 310 | +``` |
| 311 | +Host myhost |
| 312 | + PKCS11Provider /path/to/libstse_pkcs11.so |
| 313 | +``` |
| 314 | + |
| 315 | +OpenSSH will call `C_GetFunctionList` to discover the token, enumerate keys |
| 316 | +using `C_FindObjects`, and sign the authentication challenge via `C_Sign`. |
| 317 | + |
| 318 | +--- |
| 319 | + |
| 320 | +## Error handling |
| 321 | + |
| 322 | +All `C_*` functions return a standard `CK_RV` value. The most relevant codes are: |
| 323 | + |
| 324 | +| Code | Typical cause | |
| 325 | +|--------------------------------|---------------------------------------------------| |
| 326 | +| `CKR_OK` | Success | |
| 327 | +| `CKR_CRYPTOKI_NOT_INITIALIZED` | `C_Initialize()` was not called | |
| 328 | +| `CKR_DEVICE_ERROR` | I2C communication failure with the STSAFE-A | |
| 329 | +| `CKR_KEY_HANDLE_INVALID` | Slot number out of range or key not yet generated | |
| 330 | +| `CKR_MECHANISM_INVALID` | Mechanism not supported (check `stse_conf.h`) | |
| 331 | +| `CKR_BUFFER_TOO_SMALL` | Output buffer shorter than the result; `*pulLen` updated with required size | |
| 332 | +| `CKR_OPERATION_ACTIVE` | `C_SignInit` / `C_DigestInit` called twice without finalising | |
| 333 | +| `CKR_OPERATION_NOT_INITIALIZED`| `C_Sign` / `C_Digest` called without a prior `Init` | |
| 334 | + |
| 335 | +--- |
| 336 | + |
| 337 | +## Configuration reference |
| 338 | + |
| 339 | +The `stse_pkcs11_config_t` structure controls the hardware mapping: |
| 340 | + |
| 341 | +```c |
| 342 | +typedef struct stse_pkcs11_config_t { |
| 343 | + uint8_t i2c_addr; /* 7-bit I2C address (default 0x20) */ |
| 344 | + uint8_t bus_id; /* Host I2C bus index (default 0) */ |
| 345 | + uint16_t bus_speed; /* kHz: 100 or 400 (default 400) */ |
| 346 | + stse_device_t device_type; /* STSAFE_A110 or STSAFE_A120 */ |
| 347 | + uint8_t cert_zone_count; /* Number of certificate zones (0-8) */ |
| 348 | + uint8_t cert_zone_indices[8]; /* Data-zone indices for certificates */ |
| 349 | +} stse_pkcs11_config_t; |
| 350 | +``` |
| 351 | + |
| 352 | +Call `stse_pkcs11_set_config(&cfg)` before `C_Initialize()`. |
| 353 | +Call `stse_pkcs11_get_config()` to inspect the active configuration at runtime. |
| 354 | + |
| 355 | +--- |
| 356 | + |
| 357 | +## Limitations |
| 358 | + |
| 359 | +- **Single session**: Only one concurrent PKCS#11 session is supported. If your |
| 360 | + middleware opens multiple sessions, serialise access externally. |
| 361 | +- **No PIN / login**: `C_Login` succeeds silently — the STSAFE-A hardware does |
| 362 | + not use a software PIN. |
| 363 | +- **No multi-part signing**: `C_SignUpdate` / `C_SignFinal` return |
| 364 | + `CKR_FUNCTION_NOT_SUPPORTED`. Supply the full message to `C_Sign`. |
| 365 | +- **Public key cache**: `CKA_EC_POINT` is only available after |
| 366 | + `C_GenerateKeyPair` has been called in the current session, as the public key |
| 367 | + is cached in RAM. It is not persisted across power cycles or |
| 368 | + `C_Initialize` / `C_Finalize` calls. |
0 commit comments