Barracuda Application Server C/C++ Reference
Native APIs, integration guides, and platform interfaces
PSA Crypto: How It Works

This companion to Getting Started with PSA Crypto explains how the Barracuda App Server (BAS) connects its Lua Trusted Platform Module (TPM) API, ba.tpm, to a PSA Crypto provider. Read it when selecting a provider, customizing key storage, or planning migration and recovery.

What PSA supplies

Arm's Platform Security Architecture describes a broader approach to device security. The PSA Crypto API is the cryptographic interface used here. A provider implements that interface and connects operations to software, isolated firmware, or hardware. BAS supplies the Lua binding and SharkSSL consumer adapters. The product manufacturer (OEM) supplies the target provider, its initialization and protected storage, and root provisioning.

A TPM's native command interface is not itself a PSA Crypto provider. PSA support does not imply TCG TPM command compatibility, complete implementation of Arm's security architecture, or security certification. Protection depends on where the PSA provider stores keys and performs operations. In particular, non-exportable attributes in a software provider do not create hardware isolation.

Provider requirements and build settings

Use the normal BAS or Barracuda Web Server (BWS) build for your target, then configure the PSA path across the application and libraries. The standard native makefiles do not select a vendor provider for you: add that provider's include paths, libraries, drivers, and platform initialization to your project.

Header and provider layout

Add BAS inc to the public include path. The source includes name the directory:

#include <psa/crypto.h>
#include <psa-consumer/SharkSslPSAConsumerECDSA.h>

In the development checkout, inc/psa and inc/psa-consumer are SVN directory externals from SharkSSL. SDK exports contain ordinary copies of both directories. Do not add inc/psa or inc/psa-consumer individually to compiler include paths. For a different PSA provider, place its public include directory before BAS inc, so <psa/crypto.h> matches the linked provider.

SharkSSL keeps the provider implementation in src/psa and its consumer adapters in src/psa-consumer. The Windows projects build the provider separately through src/plugins/SharkSSL/build/VC-Win/SharkSSLPSA.vcxproj. The provider's private implementation headers remain internal to that build.

Source files

With SHARKSSL_PSA_CONSUMER=1, amalgamation/BAS.c already includes xrc/lua/lpsa.c and these files from src/plugins/SharkSSL/src/psa-consumer/:

File Role
SharkSslPSAConsumerCrypto.c Hashes, randomness, and TLS cryptographic operations.
SharkSslPSAConsumerPBKDF2.c PSA-backed PBKDF2 derivation.
SharkSslPSAConsumerECDSAAdapter.c Adapts SharkSSL's internal ECDSA operations.
SharkSslPSAConsumerECDSA.c ECDSA consumer helpers.
SharkSslPSAConsumerRSA.c RSA consumer helpers used by the CSR builder when RSA is enabled.
SharkSslPSAConsumerAES.c Standalone AES operations used by BAS.

The Barracuda Embedded Web Server library (amalgamation/BWS.c) also includes the consumer files, without the Lua binding. Do not compile included files a second time. A non-amalgamated build must compile the corresponding consumer files and, when using Lua, lpsa.c. Link the provider separately and use its matching psa/crypto.h and library.

Compile-time settings

Set these options throughout the BAS consumer and host build:

Setting Requirement
BA_ENABLE_TPM Set to integer 1 for the standard Lua TPM APIs. The default is 0.
SHARKSSL_PSA_CONSUMER Set to integer 1 to select the PSA backend. The default non-PSA path uses native SharkSSL and softTPM.
SHARKSSL_USE_SHA_512 Defaults to integer 1; explicit 0 remains supported. PSA uses HKDF-SHA-512 at 1, HKDF-SHA-256 at 0. This changes the root policy and derived bytes. Minimum/maximum returned lengths are 64/16,320 or 32/8,160 bytes, respectively. softTPM retains its existing PBKDF2 selection.

With BA_ENABLE_TPM=1, inc/BaConfig.h supplies the required CSR, certificate, P-256/P-384, SHA-256/SHA-384 and AES-GCM settings. It reports a compile error if a required option is explicitly disabled. It also selects and checks the backend-specific requirements: native ECC key generation and signing for softTPM, or standalone AES and non-strict identity policy for PSA. Keep the existing PSA defaults that disable native private-key operations and use provider randomness. The host must still initialize the TPM.

inc/balua.h already defaults BALUA_ENABLE_LEGACY_PEM_KEY_API to 0 with PSA and rejects enabling it. lpsa.c checks that Lua integers are 64-bit. These checks need no additional project definitions. C-only PSA applications such as SharkTrustX do not need BA_ENABLE_TPM for their native key API.

The target provider still needs the hashes, curves, key agreement and signature verification required by your TLS profile and peer certificate chains, including AES-256-GCM for configuration and user databases. Consumer settings do not configure the provider's algorithms, storage or key capacity.

For the named Lua API, the PSA provider must support persistent P-256 and P-384 ECDSA key generation, signing, public-key export, attribute lookup, and the policies below. It must also support the root derivation services; an ECC signing-only driver is insufficient. Public certificate-chain verification may require RSA even when all of your own identities use ECC.

For ba.tpm.globalkey(), ba.tpm.uniquekey() and ba.tpm.jsonuser(), require standard PSA_ALG_HKDF(PSA_ALG_SHA_512) (or PSA_ALG_HKDF(PSA_ALG_SHA_256) when SHARKSSL_USE_SHA_512=0) with persistent protected-root input, derived-byte output, and direct output of a temporary AES-256 key. That key must support AES-GCM encryption/decryption at the selected location. Confirm these capabilities and input/output limits with the provider; exposing the PSA API alone is insufficient. The binding uses standard PSA derivation calls and requires no SharkSSL-specific PBKDF2 extension.

Size key capacity for concurrent TLS connections

A TLS connection can need several active key handles for key agreement, handshake secrets and traffic keys. Overlapping browser connections, outbound ACME requests, session caches, persistent identities and temporary JSON keys share the provider's capacity. Exhaustion can appear as a cryptographic or allocation error even when heap memory is available.

Check the target's limits for active keys and concurrent operations, separately from persistent storage capacity. Configure the provider or secure firmware for the expected peak and test overlapping handshakes, renewal and reconnects. There is no universal slot count per socket or PSA-wide setting; use the provider's configuration and leave room for temporary keys.

Host startup and resource selection

The supplied Mako and Xedge hosts already register balua_psa(L). Their trusted Lua resources install ba.tpm before application startup and automatic certificate management. For a custom BAS host:

  1. Initialize entropy, the provider, its hardware connection, and persistent storage before creating the Lua state or verifying signed resources. Require psa_crypto_init() to succeed; Lua creation itself uses provider randomness.
  2. Make the required roots available using the root setup.
  3. Register balua_psa(L) after the normal BAS Lua/crypto bindings.
  4. Load the trusted PSA TPM resource and complete its initialization before starting applications or certificate management.

Mako's .openports loads .lua/psatpm.lua when the native PSA binding is present. The resource builder packages both backends. Standalone Xedge instead selects the resource at build time: SHARKSSL_PSA_CONSUMER=1 in the builder's environment replaces .lua/tpm.lua with the PSA module. Its common .config uses that selected module. The environment variable does not set the C compiler definition; both selections must match. PSA hosts exclude the embedded softTPM secret initialization.

The PSA module captures the native dispatcher privately and sets ba.psa to Boolean true before checking the roots. The flag identifies the backend; it does not establish readiness or hardware isolation. Standard softTPM builds leave it absent. Missing required global roots, incompatible attributes, or provider errors stop host initialization without falling back to softTPM.

Shared startup and restart contract

Both hosts require a provider hook when SHARKSSL_PSA_CONSUMER=1. The hook has no arguments and returns an integer: 0 means success; any nonzero value stops startup before server or Lua creation. Non-PSA builds do not call or require it.

Initialize entropy, hardware access, and persistent storage in the provider's required order, including a successful psa_crypto_init(). Board clocks, buses, storage services, and any allocator used by the provider must already be ready or be initialized by the hook. No Lua state or dispatcher mutex is available. If platform startup already initialized PSA, reuse that state.

Event Behavior in both hosts
Initial server startup Call the provider hook before cryptographic use, signed-resource verification, and Lua creation. Register balua_psa(L) in the new Lua state before trusted Lua startup.
Debugger restart Unload applications and close listeners while their TLS objects and provider are still alive. Recreate Lua and its bindings without calling the provider hook again.
Debugger termination Honor the debugger's Boolean restart flag: true restarts Lua; false (the default) completes shutdown and returns from the host function.
Later host invocation Call the hook again. It must safely reuse initialized provider state. Host invocations are sequential, never concurrent.

Keep the provider, its storage, and its allocator alive through Lua shutdown and any subsequent host invocation. Rebuild the host with current matching Lua resources: their shutdown handlers close listeners before Lua teardown. The supplied Windows PsaSimInit.c adapters cache only successful initialization and keep the shared simulator store open until process exit.

Root provisioning is a separate policy. With the device-root option, trusted Lua startup creates or reuses the root. For shared data, make the provisioned global root available before that startup. Do not reset persistent keys on startup or debugger restart.

Mako startup hook

Supply int makoInitPsa(void), declared in examples/MakoServer/src/mako.h, and compile its implementation into Mako. Follow the shared contract. Mako calls the hook once per server runMako() invocation, after service/daemon setup, including POSIX daemon forking. Help and service-management commands exit without calling it. Mako reports a nonzero result and exits.

MakoExtM1.ch runs once per runMako() invocation, before service/daemon setup. Use it for process prerequisites and move existing PSA setup into makoInitPsa(). The later MakoExtM2.ch, MakoExtM3.ch, and MakoExtM4.ch hooks run for each Lua state, including debugger restarts. Use MakoExtM2.ch for custom Lua bindings in PSA builds. makoOpenAUX() belongs to softTPM startup and is not called by the supplied PSA host. The optional built-in allocator pool retains its process lifetime.

For Windows testing, the supplied PSA projects compile examples/MakoServer/src/PsaSimInit.c with examples/misc/PsaSimulator.c. For hardware, omit these simulator files and supply your own hook. In the public Visual Studio project, set PsaTestSimulator=false and add the implementation through PsaSourceFiles or directly to the project.

Standalone Xedge startup hook

Supply int xedgeInitPsa(void), declared in examples/xedge/src/xedge.h, and compile its implementation into Xedge. Follow the shared contract. Xedge calls the hook once per barracuda() invocation, before its debugger restart label. The platform must complete its prerequisite setup before calling barracuda(), or do that work in the hook. Xedge logs a nonzero result, calls its fatal-error handler, and returns if that handler returns.

XEDGE_ENABLE_AUX=0 does not disable the PSA hook. Both xedgeInitDiskIo() and xedgeOpenAUX() run after Lua creation, so neither can initialize provider storage or hardware needed by the PSA hook. When enabled, they run for each Lua state.

For Windows testing, compile examples/xedge/src/PsaSimInit.c together with examples/misc/PsaSimulator.c and define PSA_TEST_SIMULATOR=1. For hardware, omit these simulator files and supply your own hook; neither the simulator macro nor its storage format is required.

Root settings

The provisioning steps and required root attributes are maintained in the getting-started guide. Roots are random 32-byte secrets, separate from named ECC signing keys. The default global-root ID is 1, used by ba.tpm.globalkey() and ba.tpm.jsonuser(name, true). The default device-root ID is 2, used by ba.tpm.uniquekey() and the default ba.tpm.jsonuser(name) mode.

Automatic device-root creation requires psa_generate_key() with the required root attributes at the selected location. It uses PSA_KEY_PERSISTENCE_DEFAULT; existing valid roots, including provisioned ones, are reused. Read-only persistent roots are accepted. An OEM that needs two independent device-specific roots can leave the device-root option at 0 and provision a different global root on each device.

The following optional integer build definitions select the objects. They contain no secret material:

Definition Default Purpose
BA_PSA_GLOBALKEY_USE_DEVICE_ROOT 0 Set to 1 to use the device root for global services and skip global-root provisioning.
BA_PSA_GLOBAL_ROOT_ID 1 Global-root ID; ignored when the device-root option is enabled.
BA_PSA_DEVICE_ROOT_ID 2 Device-root ID; must differ from a separately used global ID.
BA_PSA_ROOT_LOCATION PSA_KEY_LOCATION_LOCAL_STORAGE Location of both roots. Select the target's protected location when required.
BA_PSA_DERIVED_KEY_LOCATION BA_PSA_ROOT_LOCATION Location of temporary AES keys used by ba.tpm.jsonuser().

With the device-root option enabled, the same name and returned length produce identical bytes from ba.tpm.globalkey() and ba.tpm.uniquekey(). Both ba.tpm.jsonuser() modes also use that root. No separate global-root object is required.

Use nonzero root IDs reserved for this integration, outside the named-key range 0x00010000 through 0x3fffffff. Persistence means retaining an object; it does not by itself provide hardware protection. The configured location and the provider's storage protection must match the product's requirements.

Root loss and data compatibility

PSA cannot distinguish a device root that was never created from one that was deleted. In either case, the next TPM initialization generates a new root. This changes device-derived keys and makes data encrypted with the old keys unreadable. With BA_PSA_GLOBALKEY_USE_DEVICE_ROOT=1, global-key data is affected too. Preserve root storage across restarts and firmware updates, or plan a data reset after root loss. Named ECC signing keys are separate and are not replaced by this operation.

Only a missing-key result permits generation. Corrupt storage, incompatible attributes and other provider errors fail initialization without replacement. Ordinary key operations never recreate a root. Changing the global-root option also changes global-derived keys; it does not migrate existing encrypted data.

How application keys are derived

The binding uses standard PSA HKDF, with SHA-512 by default or SHA-256 when SHARKSSL_USE_SHA_512=0. Configure the provider to support that hash too; there is no runtime fallback.

The fixed public salt is ASCII BAS-TPM-KEY-v1 for returned key bytes or BAS-TPM-JSON-v1 for JSON encryption, without a terminating null. The stored root supplies PSA_KEY_DERIVATION_INPUT_SECRET; the exact name bytes supply PSA_KEY_DERIVATION_INPUT_INFO, including any embedded nulls. The salt separates the two uses. Roots remain 256-bit secrets with DERIVE-only usage.

The returned key is at least one digest long. HKDF permits at most 255 digest blocks: 16,320 bytes with SHA-512 or 8,160 with SHA-256. Requests outside 0..maximum raise a Lua argument error; zero returns the minimum length. Provider input limits may be smaller. With the same root, salt, hash and name, a shorter result is a prefix of a longer one. Use distinct names for distinct purposes, and keep the hash and requested length consistent across devices.

JSON encryption always derives one 32-byte AES-256 key. The HKDF output limit is independent of the size of the encrypted JSON database.

ba.tpm.globalkey() and ba.tpm.uniquekey() return secret bytes to Lua, so applications must protect those bytes. ba.tpm.jsonuser() instead derives a temporary, non-exportable AES-256-GCM key inside the provider, uses it, and destroys it. Its default is the device root; true selects the global root unless the device-root option is enabled.

Only the Lua API is compatible across backends and derivation versions.

The roots and derived values are not compatible with softTPM or the earlier PSA PBKDF2 and SP 800-108 derivation formats. Switching between HKDF-SHA-512 and HKDF-SHA-256 also changes the root policy and derived bytes. Incompatible roots fail initialization without replacement. Recover any needed encrypted application data before switching, or deliberately reset and recreate it. No automatic root conversion, data migration or software-decryption fallback is supplied.

Named keys, restart, and recovery

The Lua module maps key names into persistent IDs 0x10000 through 0x3fffffff. Reserve this namespace against other provider clients. The trusted Lua module calls ba.hash32() with "ba.tpm\0" .. name and seed. It starts with seed zero, masks with 0x3fffffff, and retries with the next seed if the result is below 0x10000. The prefix, exact name bytes, mask, and retry rule are part of the persistent mapping. Hashing does not guarantee collision-free names; use a controlled set of names.

ba.tpm.createkey() calls psa_generate_key() to create a random persistent P-256 or P-384 ECC key. Its policy allows ECDSA signing (PSA_KEY_USAGE_SIGN_HASH, PSA_ALG_ECDSA(PSA_ALG_ANY_HASH)), without private-key export or copy. Public-key export is needed for certificates and verification. These keys belong to persistent storage and are not deleted by Lua garbage collection.

The optional build definition BA_PSA_SIGNING_KEY_LOCATION selects the PSA location for named ECC keys. It accepts a provider-supported psa_key_location_t value and defaults to PSA_KEY_LOCATION_LOCAL_STORAGE, preserving existing builds. Define it when compiling BAS.c or the source that includes lpsa.c. It is independent of the root and temporary AES-key locations above.

The provider must support persistent key generation, ECDSA signing and public-key export at the selected location. Keys use PSA_KEY_PERSISTENCE_DEFAULT. Creation errors are returned without falling back to another location, and lookups reject stored keys at a different location. Changing the setting does not migrate existing keys; plan provisioning or re-enrollment before switching. Selecting a location alone does not establish hardware protection or FIPS validation.

After a normal restart, the same name locates the same stored key. If that key is erased, the name still produces the same ID, but it cannot recover the original private key. Creating a replacement produces a new identity. softTPM instead derives its key-generation input from the original secrets, name, and options and can recreate the key while those inputs remain intact.

Retain PSA storage together with certificates, ACME account descriptors, and registration state. The Lua ACME engine currently creates a named key when a saved descriptor refers to a missing key; in a PSA build this can create a new identity, not recover the old one. Use a deliberate recovery or re-enrollment procedure after key loss.

API availability

Use the named TPM API for private-key operations. The PSA binding supports P-256 and P-384. Legacy PEM private-key creation, signing and decryption APIs are absent; public-key verification and encryption remain available when the provider supports them. Lua ACME must use ECC. Handle reported provider, storage and policy errors.

Products requiring FIPS validation

Choose a FIPS-validated provider whose approved configuration covers the required cryptographic services, including the selected HKDF hash, AES-GCM, randomness and the enabled TLS operations. Check the exact module version, environment and security policy. Using PSA or an approved algorithm does not by itself make SharkSSL, BAS or the product FIPS-validated. See NIST's validated-module guidance.

Native SharkTrustX C client

The Native SharkTrustX C client is typically used with BWS.c.

The SharkTrustX C manual describes its own PSA key-ID reservation, rotation, and journal recovery. It uses the same SharkSSL consumer but does not load the Lua module or require the Lua derivation roots.

Try the SharkTrustX Windows PSA example with the development provider before integrating your target.

Xedge configuration encryption

Standalone Xedge deliberately uses a global key for AES-GCM encryption of xcfg.bin. softTPM uses SHA256(gpkey); PSA uses the SHA-256 hash of the bytes returned by ba.tpm.globalkey() with name "xedge.config" and length 64. This lets devices with the same global root and derivation settings read the same configuration file. BA_PSA_GLOBALKEY_USE_DEVICE_ROOT=1 makes this configuration device-specific without changing Lua code.

Xedge hosted in Mako uses the host's ba.tpm.uniquekey() followed by SHA-256, so its configuration is device-specific in either backend. It inherits Mako's TPM and does not use standalone Xedge's resource-selection step.

Changing the root, changing the device-root option, or switching from softTPM to PSA changes the encryption key. Recover needed data with the original setup before changing it, or reset the configuration. No automatic migration or software-decryption fallback is supplied. Copying xcfg.bin transfers neither PSA signing keys nor a lost certificate-management identity.

Windows development provider

The Mako Windows quick start and standalone Xedge Windows quick start use examples/misc/PsaSimulator.c, shared by Mako, Xedge, and the native SharkTrustX example. It initializes a software development provider, not a hardware security boundary or production provisioning service.

The host compiles this file once with SHARKSSL_PSA_CONSUMER=1 and PSA_TEST_SIMULATOR=1, links the SharkSSL PSA provider with file storage enabled, and links bcrypt.lib and advapi32.lib. Its startup calls int psaSimulatorInit(void) before the PSA TPM or native client. The function returns PSA_SUCCESS or a PSA error; the host must stop on an error.

On first use, the helper creates psa-simulator/ beside the executable for encrypted key records and a separate psa-simulator.key containing a random 32-byte wrapping secret. It applies owner-only Windows permissions and holds an exclusive storage lock until process exit. A new store receives two independent 256-bit derivation roots at IDs 1 and 2. Both are local to that installation, so this does not share ba.tpm.globalkey() across installations.

Later runs reuse the store, wrapping secret, roots, and named signing keys. Corrupt records, missing wrapping secrets, and incompatible root policies stop startup. A missing global root also stops startup unless the device-root option is enabled. The initializer does not replace missing roots in an existing store; the Lua TPM subsequently recreates a missing device root, with the data-loss consequences described above.

PSA failures are signed status values, defined by the provider's public psa/crypto.h. BAS reports them as PSA error N; the supplied Windows startup hook also returns those values in its initialization error. Use the symbolic name and operation to diagnose a failure, rather than assuming that one number has only one possible cause. The Windows startup error table explains the common codes and the explicit test-store reset procedure. Normal Visual Studio builds do not reset persisted keys. The Windows BuildPsa.cmd state-setup command explicitly deletes the VcMake Debug x64 simulator store and wrapping secret after confirmation, then runs the existing executable to create and verify fresh state. It does not compile code. Hardware providers require their own provisioning and recovery process.

The public mako-psa.vcxproj and xedge-psa.vcxproj both default to PsaTestSimulator=true. Each builds its provider dependency and includes its host's PsaSimInit.c adapter. The Windows SDK supplies obj/mako.zip for Mako and obj/XedgePsaZip.c for Xedge; no sibling resource checkout is required. Mako copies its archive beside the executable; Xedge embeds its resource C file. The release installer preserves both PSA projects when replacing VcMake.

For another provider, set PsaTestSimulator=false, supply the matching headers, libraries, and a makoInitPsa() or xedgeInitPsa() implementation. Both projects accept the same provider properties. Use MakoResourcesZip or XedgeResourcesC to select a different matching resource file.

On Windows, Xedge puts its disk files in HOME/xedge-psa, falling back to USERPROFILE/xedge-psa when HOME is unset. The quick start sets a separate HOME to avoid mixing configurations from different installations. The supplied led.c also keeps Xedge-configuration-file in the working directory. Both configuration copies contain the same AES-GCM ciphertext. Startup first tries that second copy and then xcfg.bin, repairing the other copy on success. Changing only HOME does not isolate the second copy or the simulator keys; use a separate executable/output directory as shown in the quick start.

Building an unstamped development checkout

Released SDK headers already contain the BAS build revision. No version override is needed. If an SVN development checkout's inc/HttpServer.h still contains /*BASLIB_VER*/, set the actual revision in the Developer Command Prompt before building:

rem Development checkout only; run from the BAS root with svn on PATH.
for /f %V in ('svn info --show-item revision') do set "CL=/DBASLIB_VER_NO=%V %CL%"

Require the SVN command to succeed. Use %V in a batch file. Rebuild all objects after changing this definition. Without it, the executable reports version 0; resources that check a minimum BAS version can reject startup with Server 0 < required .... Do not invent a revision or disable the check.

For development checkouts only, build the resources beside BAS before compiling the corresponding project. These commands require a current BAS-Resources checkout alongside BAS and start in the BAS root:

set "PATH=%CD%\bin;%PATH%"
set BA_ENABLE_LUA_OPCUA=0
set IncludeOpcUa=no
set MinifyMakoZip=no
set MinifyXedgeZip=no
set SHARKSSL_PSA_CONSUMER=1
set NO_BIN2C=
pushd ..\BAS-Resources\build
rem Run the command for your host; stop if it fails.
call mako.cmd
call Xedge.cmd
popd

The projects use these sibling resource outputs when the packaged resources are absent. An SDK user can rebuild resources explicitly by setting the resource path property; do not overwrite an existing key store or configuration.

Adapting the original Mako Visual Studio project

Prefer the supplied mako-psa.vcxproj. If you must convert mako.vcxproj, apply these changes to every Debug/Release and Win32/x64 configuration you use:

  1. Add SHARKSSL_PSA_CONSUMER=1 to the C/C++ preprocessor definitions and retain BA_ENABLE_TPM=1 and inherited definitions.
  2. Remove SHARKSSL_OPTIMIZED_GHASH_ASM, SHARKSSL_OPTIMIZED_GCM_ASM, SHARKSSL_OPTIMIZED_GCM_VAES_ASM, SHARKSSL_OPTIMIZED_CHACHA_ASM, SHARKSSL_OPTIMIZED_POLY1305_ASM, and SHARKSSL_X25519_ASM_HOOK. Exclude the assembly and C helper files under SharkSSL's X86_MSVC and X64_MSVC directories. Configure any acceleration in the provider itself.
  3. Add the provider's headers before other PSA headers, link its libraries and system dependencies, and add platform startup and driver sources. Match architecture and C runtime settings to the Mako configuration.
  4. Complete Mako's startup and resource steps, then rebuild all objects with the PSA settings.