Haiku BKeyStore: Exact Credential Lookup, Synchronous IPC, and Secret Lifetimes
Integrate Haiku BKeyStore with exact key identifiers, responsive worker calls, distinct error handling, borrowed password lifetimes, and safe diagnostics.
BKeyStore is Haiku’s Application Kit client for storing and retrieving keys through keystore_server. BKey represents a generic key and BPasswordKey specializes that value for NUL-terminated password text. The API includes keyrings, primary and secondary identifiers, key purposes, application access management, and enumeration.
The important operational boundary is the server call. A BKeyStore object is cheap to construct, but operations synchronously communicate with another process and may interact with authorization. A password lookup should therefore be an explicit application task with defined completion and error states, rather than a hidden operation inside drawing, layout, or a repeatedly invoked UI accessor.
Treat the deployed build as the implementation boundary
The Haiku Book marks several locking, master-unlock, password-generation, and password-strength interfaces as future or unimplemented functionality. In the inspected upstream revision, GeneratePassword returns B_ERROR and PasswordStrength returns zero. A method appearing in a public header does not establish that it implements the feature its name suggests.
The client implementation also forwards several operations to the server. Forwarding a message proves that a protocol entry exists, not that a particular release provides encrypted storage or a complete locking policy. Verify the deployed server and release before making security guarantees. This article describes observable client behavior and documented API contracts; it does not infer hardware protection, secure erasure, or isolation against a compromised same-user process.
Design identifiers to avoid ambiguous lookup
A primary identifier can name the service, and a secondary identifier can distinguish the account or connection. For example, sync.example.invalid:443 and operator-a can represent a service/account pair in a test environment. Those labels are illustrative metadata, not a live service or a secret.
Use an explicit keyring and require both identifiers to match when selecting a specific credential. The overload accepting secondaryIdentifierOptional makes that choice visible. Passing true allows a result even if the secondary identifier does not match, which is inappropriate for an account-specific lookup unless the application explicitly intends that fallback.
The inspected client routes shorter overloads without a secondary identifier through an optional-secondary lookup. Some reference prose describes their defaults differently, so using the full overload with false avoids dependence on that ambiguity. Do not let a convenience overload silently select a password for another account that shares the same primary service identifier.
Also keep the purpose and receiving object type consistent with what was stored. GetKey sends the requested type to the server, but the reference notes that server-side type filtering is not guaranteed by the documented implementation. The receiving key’s Unflatten checks its own type. Inspect the successful result and do not assume a requested enum alone proves every field follows the application’s expected schema.
Perform lookup away from the visible window loop
_SendKeyMessage creates a BMessenger, attempts to launch the server through the roster if necessary, sends a message, and waits for a reply. A server-launch failure, unavailable messenger, communication failure, or malformed reply can produce B_ERROR; server result codes are returned where available.
Run such work on an application-controlled worker with a valid Haiku application/roster environment. Communicate a sanitized completion message back to the UI. Include a request generation or connection identifier so a result for an old account selection is not applied after the user switches accounts or closes the window.
UI cancellation does not automatically cancel the synchronous server operation. It can mean that the application discards its eventual result. Keep the worker’s state alive until the call finishes and avoid dereferencing a destroyed window. Never resolve a timeout by launching an unlimited number of parallel credential requests.
Use password bytes only while their owner lives
BPasswordKey::Password() returns the pointer exposed by the key’s underlying data buffer. It is borrowed storage, not a new allocation. Destruction, mutation, or replacement of the key can invalidate it. A safe synchronous consumer uses the password before the key goes out of scope and does not save that pointer for a later callback.
The following helper reads a password from an explicit keyring, checks its basic representation and expected purpose, and invokes a caller-supplied synchronous consumer. It belongs on the worker described above. The consumer must not retain the pointer, and the identifiers must be validated against the application’s configuration before calling it.
#include <KeyStore.h>
#include <cstring>
status_t WithNetworkPassword(
const char* keyring, const char* service, const char* account,
void (*consume)(const char*, void*), void* context)
{
if (keyring == nullptr || service == nullptr || account == nullptr
|| consume == nullptr) {
return B_BAD_VALUE;
}
BKeyStore store;
BPasswordKey key;
status_t status = store.GetKey(keyring, B_KEY_TYPE_PASSWORD,
service, account, false, key);
if (status != B_OK)
return status;
const uint8* data = key.Data();
const size_t length = key.DataLength();
if (key.Purpose() != B_KEY_PURPOSE_NETWORK || data == nullptr
|| length == 0 || data[length - 1] != 0
|| std::memchr(data, 0, length - 1) != nullptr) {
return B_BAD_DATA;
}
consume(key.Password(), context);
return B_OK;
}
An empty password is representable as a single terminating byte; whether to permit it is a separate service policy. If authentication must continue asynchronously, establish an owned credential representation whose lifetime spans the operation and whose cleanup policy is explicit. Making copies into multiple messages, strings, and logs unnecessarily increases secret exposure.
The source does not establish guaranteed zeroization when a key is destroyed. Do not promise secure memory erasure based solely on scope exit. Similarly, locking a keyring cannot be assumed to revoke a copy of password bytes that the application already holds.
Distinguish missing data from denied access
For the documented GetKey operation, B_ENTRY_NOT_FOUND means no key matched the identifiers, B_NOT_ALLOWED means access was not granted, and B_BAD_VALUE can indicate a nonexistent keyring. Other failures can reflect communication or decoding problems. Preserve those differences in control flow and diagnostics.
Do not respond to every failure by asking the user to reenter and overwrite a password. A denied lookup may need an authorization decision; a communication failure may need retry; a missing key may need initial setup. A generic error does not prove absence. This distinction prevents transient server failures from destroying existing credentials.
When adding a key, handle duplicate-identifier results such as B_NAME_IN_USE explicitly. Define whether an operation is initial creation, replacement, or rotation. The separate add/remove APIs do not imply an atomic replace transaction. Removing a working key before a new one is successfully stored creates a failure gap, so plan migration and rollback deliberately.
Keep secret-bearing debug helpers out of logs
BPasswordKey::PrintToStream() prints the password in the inspected implementation. It is unsuitable for ordinary diagnostics, support bundles, or CI test output containing real credentials. Generic metadata can also reveal service names and account identities, so log only what the support workflow needs.
Record an application request ID, operation name, keyring alias, sanitized identity, duration, and numeric status. Never print the returned password to prove that lookup worked. Authentication success, a fixture comparison inside a test, or a redacted result flag can establish the relevant behavior without exposing bytes.
Acceptance tests and recovery
Use a disposable test keyring and synthetic credentials to test exact primary/secondary matches, a missing account, a missing keyring, duplicate insertion, denied authorization, server unavailability, malformed/type-mismatched data, and closing the UI while a request is active. Confirm that no passwords appear in logs and that stale completions cannot update a newly selected account.
Verify the actual release’s supported operations rather than treating future API names as implemented capabilities. Retain existing credentials on recoverable failures, make destructive removal an explicit operation, and keep the secret’s in-memory lifetime tied to the operation that needs it. Those boundaries make the client predictable even when server behavior or authorization interrupts the normal path.
Related:
- Haiku BMessenger Delivery: Target Identity, Replies, Timeouts, and Shutdown
- Haiku’s BRoster and registrar: How Applications Are Found and Launched
Sources: