Credential Storage
Harden token and mTLS credential storage with macOS Keychain and PKCS#11.
rstream clients can authenticate an agent control-channel connection with either a token or an mTLS client certificate, depending on the engine edition and enabled runtime features. The default configuration stores tokens inline and stores mTLS material as PEM files or inline PEM blocks. Advanced deployments can move the sensitive material out of the YAML file.
Use macOS Keychain when a signed macOS binary should be the authority allowed to read a stored token or use a private key. Use PKCS#11 when an mTLS private key should stay inside a hardware token, HSM, TPM-backed module, smart card, or SoftHSM test token. Self-hosted CE uses token-based agent authentication; hardened mTLS agent authentication belongs to hosted rstream or private deployments that enable certificate-backed agent authentication.
Do not use PKCS#11 for bearer tokens. A bearer token is a string secret; PKCS#11 is designed for key operations where the private key does not leave the token. Store bearer tokens in macOS Keychain on macOS, or inject them from the platform secret manager on Linux and server environments.
For a full operational walkthrough, see Secure rstream Agent Authentication with Keychain and PKCS#11.
Support matrix
| Credential storage | Go CLI / Go SDK | C++ SDK | JS SDK |
|---|---|---|---|
Token inline | Supported | Supported | Supported |
Token keychain with provider: macos | Supported on macOS release or signed builds | Not supported | Not supported |
mTLS certificateFile / keyFile | Supported | Supported | Supported |
mTLS inline certificate / key | Supported | Supported | Supported |
mTLS keychain with provider: macos | Supported on macOS release or signed builds | Not supported | Not supported |
mTLS pkcs11 | Supported | Supported when built with OpenSSL PKCS#11 support | Not supported |
Unsupported SDKs still parse the shared configuration shape and fail explicitly. They do not silently ignore a configured hardened storage backend.
macOS Keychain model
macOS Keychain support is explicit:
kind: keychain
provider: macosThe provider is not portable to Linux or Windows. Its security value comes from Keychain access control: the secret is associated with the trusted binary identity, so a properly signed and notarized rstream binary can access or use the item while an unrelated binary cannot.
This is only meaningful when the binary identity is stable. Use official signed releases, or sign internal builds with the certificate identity used in your deployment policy. Unsigned development binaries are useful for tests, but they are not the security boundary you should rely on in production.
Token storage in macOS Keychain
The CLI can store a login token directly in Keychain:
rstream login --token-storage macos-keychainFor a non-browser login, keep the token out of shell history:
printf "%s" "$RSTREAM_AUTHENTICATION_TOKEN" \
| rstream login --token-stdin --token-storage macos-keychainDirect engine contexts can also store their token in Keychain. This shape is used for self-hosted CE and for advanced deployments where the engine address is already resolved:
printf "%s" "$RSTREAM_AUTHENTICATION_TOKEN" \
| rstream context create prod \
--engine "<engine-host:port>" \
--token-stdin \
--token-storage macos-keychain \
--defaultThe resulting configuration stores only a Keychain reference:
contexts:
- name: prod
engine: "<engine-host:port>"
auth:
token:
storage:
kind: keychain
provider: macos
service: io.rstream.auth
account: context:https://rstream.io:prodFor environment-level login storage, the default account shape is api:<api-url>, for example api:https://rstream.io. For context storage, the default account shape is context:<api-url>:<context-name>. An unlinked local context uses context:<context-name>.
Hosted project contexts normally use a project endpoint instead of a manually configured engine address; the Keychain storage block is the same once the context is written.
mTLS identities in macOS Keychain
macOS Keychain can also hold an mTLS identity. The configuration references the certificate SHA-256 fingerprint:
contexts:
- name: prod-mtls-keychain
engine: "<engine-host:port>"
auth:
mtls:
storage:
kind: keychain
provider: macos
certificateSHA256: "ab12cd34..."The certificate must be registered as an mTLS Client Certificate (MTLS) credential in the Dashboard. rstream stores the certificate fingerprint and authorizes that fingerprint at runtime; the private key remains in Keychain.
To import an existing key pair into the login keychain, create a PKCS#12 bundle and restrict the imported item to the signed rstream binary:
openssl pkcs12 \
-export \
-inkey rstream-client.key \
-in rstream-client.crt \
-out rstream-client.p12 \
-name rstream-client-prod
security import rstream-client.p12 \
-k "$HOME/Library/Keychains/login.keychain-db" \
-T "$(command -v rstream)"Compute the fingerprint for the config file:
openssl x509 \
-in rstream-client.crt \
-noout \
-fingerprint \
-sha256The config accepts either colon-separated or plain hexadecimal SHA-256 values.
PKCS#11 mTLS storage
PKCS#11 storage keeps the mTLS private key inside a token or module. The SDK asks the module to sign TLS handshake data; it does not read or export the private key.
Use this shape for Go and C++:
contexts:
- name: prod-mtls-hsm
engine: "<engine-host:port>"
auth:
mtls:
storage:
kind: pkcs11
module: /opt/homebrew/lib/libykcs11.dylib
# C++ only, when the OpenSSL provider is not named pkcs11prov:
# opensslProvider: pkcs11
tokenLabel: "YubiKey PIV"
keyLabel: "Private key for PIV Authentication"
certificateFile: /etc/rstream/client.crt
pinEnv: RSTREAM_PKCS11_PINmodule is the vendor PKCS#11 shared library. Common examples are libykcs11 for YubiKey PIV, opensc-pkcs11 for smart cards supported by OpenSC, and libsofthsm2 for SoftHSM tests.
opensslProvider is optional and only used by the C++ SDK. Set it when the OpenSSL provider name differs from the default pkcs11prov. Ubuntu/Debian pkcs11-provider, for example, exposes the provider as pkcs11.
pinEnv names the environment variable that contains the PIN. The PIN value is not stored in the configuration and must not be logged. Set it in the process manager, secret manager, shell environment, or CI secret store that starts the agent.
Select exactly one token:
| Field | Use |
|---|---|
tokenLabel | Human-readable token label. Best for simple deployments. |
tokenSerial | Stable token serial. Better when labels are reused. |
slot | Go-only selector. Prefer tokenLabel or tokenSerial for portable Go/C++ configs. |
Select exactly one private key:
| Field | Use |
|---|---|
keyLabel | Human-readable private key label. |
keyIdHex | Hexadecimal PKCS#11 object ID. Best when labels are reused. |
Provide exactly one certificate source:
| Field | Use |
|---|---|
certificateFile | PEM certificate chain on disk. Recommended for portable Go/C++ configs. |
certificate | Inline PEM certificate chain. Useful for generated configs. |
certificateLabel | Certificate object label in the token. |
certificateIdHex | Certificate object ID in the token. |
Do not mix PKCS#11 storage with top-level certificate, key, certificateFile, or keyFile aliases. The private key must come from the PKCS#11 storage block.
SoftHSM setup for validation
SoftHSM is useful for CI and local validation because it behaves like a PKCS#11 module without requiring hardware.
On macOS:
brew install softhsm opensc libp11Create an isolated SoftHSM store:
export SOFTHSM2_CONF="$PWD/softhsm2.conf"
mkdir -p "$PWD/softhsm-tokens"
cat > "$SOFTHSM2_CONF" <<EOF
directories.tokendir = $PWD/softhsm-tokens
objectstore.backend = file
log.level = ERROR
slots.removable = false
EOFInitialize a token and generate a non-exportable key:
export RSTREAM_PKCS11_PIN="123456"
softhsm2-util \
--init-token \
--free \
--label RSTREAM \
--pin "$RSTREAM_PKCS11_PIN" \
--so-pin 12345678
pkcs11-tool \
--module /opt/homebrew/lib/softhsm/libsofthsm2.so \
--login \
--pin "$RSTREAM_PKCS11_PIN" \
--keypairgen \
--key-type EC:prime256v1 \
--label rstream-client \
--id 01 \
--usage-signWith OpenSSL 3, create a certificate using the token key:
export OPENSSL_MODULES=/opt/homebrew/lib/ossl-modules
export OPENSSL_PKCS11_PROVIDER=pkcs11prov
export PKCS11_MODULE_PATH=/opt/homebrew/lib/softhsm/libsofthsm2.so
export PKCS11_PIN="$RSTREAM_PKCS11_PIN"
openssl list -providers -provider default -provider "$OPENSSL_PKCS11_PROVIDER"
openssl req \
-new \
-x509 \
-provider "$OPENSSL_PKCS11_PROVIDER" \
-provider default \
-key "pkcs11:token=RSTREAM;object=rstream-client;type=private" \
-sha256 \
-days 397 \
-subj "/CN=rstream-agent-prod-01" \
-out rstream-client.crtRegister rstream-client.crt as an mTLS Client Certificate (MTLS) credential, then reference the token key from the rstream config:
contexts:
- name: softhsm
engine: "<engine-host:port>"
auth:
mtls:
storage:
kind: pkcs11
module: /opt/homebrew/lib/softhsm/libsofthsm2.so
opensslProvider: pkcs11prov
tokenLabel: RSTREAM
keyLabel: rstream-client
certificateFile: /absolute/path/to/rstream-client.crt
pinEnv: RSTREAM_PKCS11_PINC++ OpenSSL requirements
The C++ SDK uses OpenSSL for TLS. For PKCS#11, the OpenSSL runtime must be able to load a PKCS#11 provider or engine.
For OpenSSL 3 provider deployments, install a provider and make it discoverable:
# macOS Homebrew libp11.
export OPENSSL_MODULES=/opt/homebrew/lib/ossl-modules
export OPENSSL_PKCS11_PROVIDER=pkcs11prov
openssl list -providers -provider default -provider "$OPENSSL_PKCS11_PROVIDER"
# Ubuntu / Debian pkcs11-provider.
export OPENSSL_PKCS11_PROVIDER=pkcs11
openssl list -providers -provider default -provider "$OPENSSL_PKCS11_PROVIDER"Linux distributions usually place OpenSSL providers under a system module directory, or configure them through openssl.cnf. Embedded images should make the provider path part of the runtime image instead of relying on an interactive shell profile. When the provider is not named pkcs11prov, set opensslProvider in the PKCS#11 storage block. On OpenSSL 3.0 through 3.4, provider-specific module and PIN settings must come from OpenSSL configuration; OpenSSL 3.5 and later allow rstream to pass those provider parameters at runtime.
For older deployments that still use the OpenSSL engine path, the engine must also be discoverable:
export OPENSSL_ENGINES=/opt/homebrew/lib/engines-3The C++ SDK reads module from the rstream configuration as the PKCS#11 vendor module path. OPENSSL_MODULES and OPENSSL_ENGINES identify OpenSSL plugin directories, not the HSM module itself.
Linux and server environments
Linux desktop secret stores such as Secret Service or KWallet are not equivalent to macOS Keychain's signed-binary ACL model. They can be useful for desktop applications, but they are not the hardened portable boundary rstream uses for advanced agent credentials.
For Linux servers and embedded devices, prefer:
| Credential | Recommended storage |
|---|---|
| Bearer token | Platform secret manager, CI secret, Kubernetes Secret, systemd credentials, or process environment. |
| mTLS private key | PKCS#11 module backed by HSM, TPM, smart card, YubiKey, or SoftHSM for tests. |
If a Linux service needs a token, inject the token into RSTREAM_AUTHENTICATION_TOKEN or write an inline-token config from a trusted deployment pipeline. If a Linux service needs a hardware-backed identity, use PKCS#11 mTLS instead of trying to emulate macOS Keychain semantics.