Skip to content

Commit b3732e9

Browse files
committed
[feature] [sal] add PKCS#11 provider layer (sal/pkcs11)
1 parent ab3d296 commit b3732e9

8 files changed

Lines changed: 3675 additions & 0 deletions

File tree

doc/resources/Markdown/00_introduction.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ For more details on each layer, please refer to the "Modules Documentation" sect
3131
\subpage stse_services
3232

3333
\subpage stse_core
34+
35+
\subpage pkcs11_sal

doc/resources/Markdown/03_LIBRARY_CONFIGURATION/03_LIBRARY_CONFIGURATION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,4 @@ The following table lits all parametters
167167
| STSE_FIRST_POLLING_INTERVAL | First polling delay definition in ms (see section below) | STSAFE-A / STSAFE-L
168168
| STSE_POLLING_RETRY_INTERVAL | Polling retry interval definition in ms (see section below) | STSAFE-A / STSAFE-L
169169
| STSE_FRAME_DEBUG_LOG | Enable HOST<->STSE communication logs | STSAFE-A / STSAFE-L
170+
| STSE_CONF_PKCS11_SUPPORT | Enable the PKCS#11 v2.40 Cryptoki provider layer (sal/pkcs11/) | STSAFE-A
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
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

Comments
 (0)