Credential Storage

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 storageGo CLI / Go SDKC++ SDKJS SDK
Token inlineSupportedSupportedSupported
Token keychain with provider: macosSupported on macOS release or signed buildsNot supportedNot supported
mTLS certificateFile / keyFileSupportedSupportedSupported
mTLS inline certificate / keySupportedSupportedSupported
mTLS keychain with provider: macosSupported on macOS release or signed buildsNot supportedNot supported
mTLS pkcs11SupportedSupported when built with OpenSSL PKCS#11 supportNot 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: macos

The 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-keychain

For a non-browser login, keep the token out of shell history:

printf "%s" "$RSTREAM_AUTHENTICATION_TOKEN" \
  | rstream login --token-stdin --token-storage macos-keychain

Direct 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 \
      --default

The 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:prod

For 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 \
  -sha256

The 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_PIN

module 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:

FieldUse
tokenLabelHuman-readable token label. Best for simple deployments.
tokenSerialStable token serial. Better when labels are reused.
slotGo-only selector. Prefer tokenLabel or tokenSerial for portable Go/C++ configs.

Select exactly one private key:

FieldUse
keyLabelHuman-readable private key label.
keyIdHexHexadecimal PKCS#11 object ID. Best when labels are reused.

Provide exactly one certificate source:

FieldUse
certificateFilePEM certificate chain on disk. Recommended for portable Go/C++ configs.
certificateInline PEM certificate chain. Useful for generated configs.
certificateLabelCertificate object label in the token.
certificateIdHexCertificate 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 libp11

Create 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
EOF

Initialize 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-sign

With 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.crt

Register 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_PIN

C++ 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-3

The 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:

CredentialRecommended storage
Bearer tokenPlatform secret manager, CI secret, Kubernetes Secret, systemd credentials, or process environment.
mTLS private keyPKCS#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.