Skip to content

Latest commit

Β 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

README.md

β–ˆβ–ˆβ•—  β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ–ˆβ•—    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘
β•šβ•β•  β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•     β•šβ•β•    β•šβ•β•β•β•β•β•β•β•šβ•β•     β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β•  β•šβ•β•   β•šβ•β•    β•šβ•β•β•β•β•β• β•šβ•β•  β•šβ•β•

Cybersecurity Projects Zig PKCS#11 Verified with License: AGPLv3

A software Hardware Security Module that compiles to a real Cryptoki (PKCS#11) shared object. Load it with pkcs11-tool, OpenSSL, or any PKCS#11 host the same way you would a real smartcard or HSM. It speaks the C ABI byte for byte, generates and stores keys, signs and encrypts, and keeps private key material sealed on disk and zeroized in RAM.

YouTube Learn Video Cybersecurity Project Walkthrough

Why PKCS#11 in Zig

PKCS#11 (Cryptoki) is the C-ABI standard that smartcards, YubiKeys, and cloud HSMs all speak. A conforming module is a .so that exports one function, C_GetFunctionList, returning a 68-entry table of function pointers in a fixed canonical order. Get one struct offset or one pointer slot wrong and the host loads garbage.

That makes it a near-perfect showcase for Zig's C interop: extern struct with natural alignment, callconv(.c), a version script that exports exactly one symbol, and a hand-written ABI that is machine-checked against the official OASIS headers at build time. On top of that ABI sits a real HSM: a login model, an attribute-bag object store, a full set of cryptographic mechanisms, and encrypted-at-rest key storage.

What Works Today

This is not a stub. A host can drive the module through a complete key lifecycle, and every capability below is exercised by a cross-process pkcs11-tool run, an in-process smoke harness against the built .so, and unit tests.

The ABI and the module

  • Loads under OpenSC pkcs11-tool 0.26.1, enumerates the slot and token (-L), advertises 21 mechanisms (-M)
  • Exports only C_GetFunctionList (verified with objdump -T); 34 __ubsan_* symbols are kept out of the dynamic table
  • The full v2.40 ABI hand-written in src/ck.zig: every type, 200+ constants, every struct, and the 68-entry CK_FUNCTION_LIST in canonical order
  • A build-time cross-check (zig build test) translates the vendored OASIS headers and asserts @sizeOf / @offsetOf / constant equality and per-function C-ABI signatures against ck.zig. Spec compliance is a compile-time invariant, not a hope

Tokens, sessions, login

  • C_InitToken / C_InitPIN / C_SetPIN / C_Login / C_Logout with a real Security Officer and User role split
  • PINs are stretched with Argon2id (t=3, m=64 MiB, p=1); only salt and hash touch disk, never the PIN
  • Three wrong attempts trip a lockout (CKR_PIN_LOCKED), reflected in the token flags
  • Read-only versus read-write session enforcement and the full session state machine

Objects

  • C_CreateObject / C_CopyObject / C_DestroyObject / C_GetObjectSize
  • Two-call C_GetAttributeValue, per-attribute, with CKR_ATTRIBUTE_SENSITIVE for sealed key material
  • C_FindObjects triad with CKA_PRIVATE login gating (private objects are invisible before login)
  • CKA_MODIFIABLE / CKA_DESTROYABLE honored

Cryptography

  • Digest: SHA-256 / SHA-384 / SHA-512, single-shot and multi-part
  • HMAC: HMAC-SHA-256 / 384 / 512, with constant-time tag verification
  • AES: CBC, CBC-PAD, and GCM (128 and 256-bit keys), encrypt and decrypt, with streaming GCM that buffers until the tag verifies
  • ECDSA: P-256 and P-384 keygen, CKM_ECDSA and CKM_ECDSA_SHA256, cross-verified against OpenSSL
  • RSA via libcrypto: 2048 to 4096-bit keygen, PKCS#1 v1.5, PSS, and OAEP; sign / verify / encrypt / decrypt and Sign / VerifyRecover
  • Key management: C_GenerateKey (AES), C_GenerateKeyPair (RSA / EC), C_WrapKey / C_UnwrapKey (AES-KEY-WRAP RFC 3394 and RSA-OAEP), C_DeriveKey (ECDH), C_DigestKey
  • RNG: C_GenerateRandom drawn from std.Io.randomSecure (the OS CSPRNG)

Encrypted at rest, zeroized in RAM

  • Token objects persist to a file. Sensitive attribute values are sealed with AES-256-GCM under a per-token master key
  • The master key is wrapped under a single User-PIN keyslot (Argon2id-derived KEK). There is no Security-Officer keyslot for user secrets by design
  • On C_Logout, C_CloseAllSessions, and C_Finalize, sealed secrets are re-sealed and the master key is wiped with std.crypto.secureZero. A failed re-seal fails closed (the plaintext is scrubbed in place)

See learn/CONFORMANCE.md for the precise return code at every deliberate boundary.

Quick Start

git clone https://github.com/CarterPerez-dev/Cybersecurity-Projects.git
cd Cybersecurity-Projects/PROJECTS/advanced/hsm-emulator
./install.sh

install.sh checks for Zig 0.16, OpenSC, and OpenSSL, builds the module in ReleaseSafe, runs the ABI cross-check plus the smoke test, and confirms pkcs11-tool can load it. Then drive it like any real token. Point both storage paths at a scratch directory so you do not write into $HOME:

export ANGELAMOS_HSM_TOKEN=/tmp/hsm-token
export ANGELAMOS_HSM_OBJECTS=/tmp/hsm-objects
MOD=zig-out/lib/libhsm.so

pkcs11-tool --module $MOD -L                    # list slots and token
pkcs11-tool --module $MOD -M                    # list 21 mechanisms

pkcs11-tool --module $MOD --init-token --label demo --so-pin 12345678
pkcs11-tool --module $MOD --init-pin --so-pin 12345678 --pin 1234
pkcs11-tool --module $MOD -l --pin 1234 \
    --keypairgen --key-type rsa:2048 --label signer
pkcs11-tool --module $MOD -l --pin 1234 \
    --sign --mechanism SHA256-RSA-PKCS --label signer \
    --input-file message.bin --output-file sig.bin
Available slots:
Slot 0 (0x0): AngelaMos HSM Emulator Slot 0
  token state:   uninitialized

Tip

This project uses just as a command runner. Type just to see everything. just spy -L wraps the module in pkcs11-spy.so and logs every Cryptoki call with its arguments and return code. It is the fastest way to watch the ABI work.

Install: curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin

Learn

This project ships a full teaching track. Read it in order, or jump to what you need.

Doc What it covers
learn/00-OVERVIEW.md What an HSM is, why it exists, and a 10-minute tour
learn/01-CONCEPTS.md Key sensitivity, the login model, encryption at rest, constant-time, padding oracles, with real breaches
learn/02-ARCHITECTURE.md The three-layer design, object model, locking, the threat model
learn/03-IMPLEMENTATION.md A code walkthrough of the ABI, an end-to-end operation, and the secret-handling patterns
learn/MECHANICS.md How each cryptographic mechanism actually works, byte by byte
learn/CONFORMANCE.md The v2.40 conformance statement: every narrowed behavior and its exact return code
learn/04-CHALLENGES.md Extension ideas from beginner to expert

Architecture

The same three-layer split SoftHSM2 uses: a thin C-ABI faΓ§ade over typed core state over the store and crypto backends.

   PKCS#11 host (pkcs11-tool, OpenSSL, p11-kit)
                      β”‚  C ABI
                      β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  C_GetFunctionList  (src/main.zig)          β”‚   one exported symbol,
   β”‚  68-entry CK_FUNCTION_LIST                  β”‚   one version script
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  ABI faΓ§ade   src/ck.zig  +  src/api/*.zig   β”‚   hand-written Cryptoki ABI
   β”‚  general Β· slot_token Β· session Β· object Β·   β”‚   + per-call entry points,
   β”‚  crypto_ops Β· keymgmt Β· random               β”‚   argument and FSM validation
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  core state   src/core/*.zig                 β”‚   global instance behind a lock,
   β”‚  state Β· session Β· object_store Β· token      β”‚   sessions, objects, PIN, master key
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  crypto   src/crypto/*.zig                   β”‚   pure-Zig std.crypto for AES/EC/
   β”‚  digest Β· mac Β· cipher Β· ecdsa Β· rsa Β·       β”‚   hash/HMAC/ECDH, libcrypto for RSA,
   β”‚  keystore Β· pin Β· openssl                    β”‚   Argon2id KDF, GCM envelope at rest
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Design decisions: non-RSA crypto is pure-Zig std.crypto. RSA links libcrypto (OpenSSL EVP) because std.crypto has no public RSA. The RSA binding is hand-written extern declarations, not @cImport, so the production .so exports nothing but C_GetFunctionList. The RNG is std.Io.randomSecure, which draws fresh entropy from the OS on every call (getrandom(2) on Linux) and keeps no CSPRNG state in process memory. The ABI is structured for v2.40 with room to add the v3.0 C_GetInterface surface later.

Build and Test

zig build               # build the module β†’ zig-out/lib/libhsm.so (Debug)
zig build --release=safe # the shipped artifact: ReleaseSafe, UB checks as traps
zig build test          # ABI cross-check vs OASIS headers + the unit suite
zig build smoke         # dlopen the built .so and exercise the whole ABI as a host would
just ci                 # fmt-check + test + smoke

The smoke harness in examples/smoke.zig is not a unit test. It dlopens the actual built shared object and calls through the function list exactly like an external host, so it catches export and ABI-shape bugs that in-process tests cannot. It walks a full lifecycle: init, login, keygen, sign, encrypt, wrap, derive, GCM streaming, dual-function, recover, operation-state, and the conformance edges.

Note

Plain zig build produces a Debug binary. The shipped artifact is --release=safe (ReleaseSafe), which keeps every undefined-behavior check live and turns it into a fail-closed trap rather than silent corruption. Set both ANGELAMOS_HSM_TOKEN and ANGELAMOS_HSM_OBJECTS to a scratch path for tests and tool runs, or the module falls back to $HOME/.angelamos-hsm-*.

Run in Docker

No Zig or OpenSC on the host? The container builds the module and drives it end to end through pkcs11-tool: token init, RSA and EC keygen and signing, AES-CBC round-trip, all inside the image.

just docker-demo        # build the image, then run the full pkcs11-tool demo

Or with Docker directly:

docker build -t angelamos-hsm:latest .
docker run --rm angelamos-hsm:latest

A multi-stage build compiles the module in ReleaseSafe in a debian-slim builder, then ships only the .so plus opensc and libssl3 in a roughly 96 MB runtime image. The demo exits non-zero if any signature fails to verify.

Project Structure

hsm-emulator/
β”œβ”€β”€ build.zig              # addLibrary(.dynamic), version script, sanitize_c=.trap,
β”‚                          #   libcrypto link, translate-c ABI cross-check, test + smoke
β”œβ”€β”€ build.zig.zon          # package manifest
β”œβ”€β”€ pkcs11.map             # version script: exports only C_GetFunctionList
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ ck.zig             # the hand-written Cryptoki v2.40 ABI (types, constants, structs, list)
β”‚   β”œβ”€β”€ config.zig         # identity strings, key-size bounds, mechanism list (no magic numbers)
β”‚   β”œβ”€β”€ util.zig           # comptime helpers (space-padded fixed fields)
β”‚   β”œβ”€β”€ main.zig           # exported C_GetFunctionList + the wired 68-slot table
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ state.zig       # global instance behind a lock, init-args parsing, generation counter
β”‚   β”‚   β”œβ”€β”€ lock.zig        # spinlock wrapper over std.atomic.Mutex
β”‚   β”‚   β”œβ”€β”€ env.zig         # reads std.c.environ for storage paths at the C boundary
β”‚   β”‚   β”œβ”€β”€ token.zig       # token record: PIN slots, fail counters, wrapped master key
β”‚   β”‚   β”œβ”€β”€ session.zig     # session table, op-state unions, the RUP-safe GCM buffer
β”‚   β”‚   └── object_store.zig# attribute-bag objects, the selective-sealing codec
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ general.zig     # C_Initialize / Finalize / GetInfo / WaitForSlotEvent
β”‚   β”‚   β”œβ”€β”€ slot_token.zig  # slot + token + mechanism queries, InitToken / InitPIN / SetPIN
β”‚   β”‚   β”œβ”€β”€ session.zig     # OpenSession / Login / Logout / Get+SetOperationState
β”‚   β”‚   β”œβ”€β”€ object.zig      # CreateObject / Find / GetAttributeValue
β”‚   β”‚   β”œβ”€β”€ crypto_ops.zig  # the digest / sign / verify / encrypt / decrypt / dual surface
β”‚   β”‚   β”œβ”€β”€ keymgmt.zig     # GenerateKey(Pair) / WrapKey / UnwrapKey / DeriveKey
β”‚   β”‚   └── random.zig      # GenerateRandom / SeedRandom
β”‚   └── crypto/
β”‚       β”œβ”€β”€ openssl.zig     # hand-written extern EVP/BN/OSSL_PARAM declarations
β”‚       β”œβ”€β”€ pin.zig         # Argon2id derive / verify (constant-time)
β”‚       β”œβ”€β”€ digest.zig      # SHA-2 hasher union + serializable op-state
β”‚       β”œβ”€β”€ mac.zig         # HMAC-SHA-2 union
β”‚       β”œβ”€β”€ cipher.zig      # AES-CBC/CBC-PAD/GCM + RFC 3394 key wrap
β”‚       β”œβ”€β”€ ecdsa.zig       # P-256/384 keygen, sign, verify, ECDH, curve OID + EC point DER
β”‚       β”œβ”€β”€ rsa.zig         # the stateless libcrypto RSA bridge
β”‚       └── keystore.zig    # master-key gen, wrap/unwrap, seal/unseal envelope
β”œβ”€β”€ tests/abi_test.zig     # @sizeOf/@offsetOf/constant/signature cross-check vs OASIS
β”œβ”€β”€ examples/smoke.zig     # loads the built .so via dlopen and drives the whole lifecycle
└── vendor/pkcs11/         # unmodified OASIS v2.40 headers (build-time cross-check only)

Roadmap

Each milestone ends with a proof from a real external tool. No feature is "done" until pkcs11-tool or OpenSSL exercises it.

Milestone Scope Proof
M0 βœ… Scaffold + hand-written ABI + loadable .so pkcs11-tool -L/-M, objdump -T
M1 βœ… Sessions + login + PIN (Argon2id, lockout) --init-token --init-pin --login --change-pin
M2 βœ… Objects + find, CKA_PRIVATE gating -O --read-object, two-call attribute fetch
M3 βœ… RNG + SHA-2 + HMAC + AES-CBC/CBC-PAD/GCM --hash --sign --encrypt --decrypt --generate-random
M4 βœ… ECDSA P-256/384 + keygen --keypairgen EC --sign, cross-verify with OpenSSL
M5 βœ… RSA via libcrypto (v1.5 / PSS / OAEP) sign / verify / encrypt / decrypt through the module
M6 βœ… Encrypted store at rest (AES-256-GCM under Argon2id KEK) survives restart; wrong PIN and tamper fail closed
M7 βœ… Hardening (secret zeroization, fail-closed relock) + Docker leak-checked build, just docker-demo exits 0
M9 βœ… Key management: wrap / unwrap, ECDH derive, digest-key RFC 3394 KAT, ECDH matches openssl pkeyutl -derive
M10 βœ… Crypto surface: GCM streaming, dual-function, Sign/VerifyRecover, op-state chunked GCM equals one-shot, recover round-trips
M11 βœ… Conformance pass + CONFORMANCE.md every N/A boundary asserted against the built .so
M12 βœ… Learn modules + mechanism reference this learn/ track

License

AGPL 3.0. The vendored OASIS headers under vendor/pkcs11/ keep their original copyright and are used only for the build-time cross-check.