Protected Data
Overview
ProtectedByteBlock keeps application-lifetime secrets in an
opaque authenticated envelope.
The key-agreement API uses this storage for private keys and shared secrets.
SecureStorage stores secrets across application runs through the current user’s operating-system credential store.
Reading a stored secret returns a protected block for use in the current application.
Application Configuration
setProtectedDataMode() selects how ProtectedByteBlock
initializes its application-wide protection provider.
Call validateProtectedDataSupport() during startup when provider availability must become an explicit startup
failure.
Validation or the first non-empty protected block locks the mode; reset() preserves a locked mode.
The following sections describe provider behavior and lifecycle restrictions.
Protected Blocks
A protected block stores only provider-owned ciphertext and the authenticated plaintext length.
It never retains a provider pointer, key, native key reference, or registry entry.
Copying, moving, destroying, or reading its metadata therefore does not resolve the application protection service.
Use unprotect() when you need an owning sensitive byte block, or withUnprotectedData() to limit plaintext access
to one callback.
The callback storage is erased after a normal return and while an exception unwinds.
Protection is memory hardening for the lifetime of one Application.
It is not a persistent file format, a process isolation boundary, or protection from arbitrary code already executing
inside the process.
An envelope cannot be decrypted after its application is destroyed or by a later application instance.
Empty protected blocks do not require an application and never initialize a provider.
Provider Selection
ProtectedDataMode controls provider selection before the first
non-empty block or an explicit validateProtectedDataSupport() call locks the choice.
Automatic prefers the native provider on macOS and Windows, performs an authenticated round-trip self-test, and
falls back to internal AES-256-GCM only during initialization.
InternalOnly avoids native detection entirely.
PlatformOnly turns an unavailable or failing native provider into a
CryptologyError.
After lock-in, provider errors fail closed and never switch the provider behind existing envelopes.
On macOS, native protection uses an application-lifetime Secure Enclave P-256 key and Security framework ECIES/AES-GCM.
The process must have access to the required keychain facilities; otherwise Automatic uses the internal provider.
On Windows, native protection uses DPAPI-NG with a logon-local descriptor held by the application service.
The provider loads ncrypt.dll from the Windows system directory only when selected, resolves the four required entry
points explicitly, and releases the library after closing its descriptor.
Linux deliberately uses the bundled AES-256-GCM provider for Automatic and InternalOnly.
It does not probe AF_ALG, kernel keyrings, TPM services, or desktop key stores, and PlatformOnly fails.
The internal provider generates one AES-256 key per application, assigns a unique counter nonce to each envelope, and authenticates envelope metadata. Application destruction releases or deletes native references and securely erases an internally owned key.
Persistent Secret Storage
SecureStorage provides synchronous read, write, and removal operations within an application namespace.
Namespace and item names are nonempty UTF-8 text, limited to 256 bytes each and without control characters.
The namespace separates application item names; operating-system access controls determine who may read them.
It does not isolate processes running as the same user.
read() returns an optional ProtectedByteBlock.
A missing item returns no value, while an existing empty item returns an empty protected block.
write() creates or replaces an item from a protected block or borrowed plaintext bytes.
The borrowed overload leaves cleanup of the input with the caller.
The stored value is independent of the application’s temporary protected-memory envelope, so it remains available to
later application instances.
remove() reports whether the item existed.
Platform Support and Limits
capabilities() reports platform support without accessing or unlocking storage.
macOS uses generic-password items in the current user’s default standard Keychain, available to command-line and desktop
applications without data-protection-Keychain entitlements.
Items are local and are not marked for synchronization.
Windows uses generic Credential Manager items with current-user, local-computer persistence.
Linux reports unsupported.
The common payload limit is 2,560 bytes, including values read from items written by another program.
The operating system owns persistence, access controls, and any copies made by its security service. The library erases scoped decrypted copies and mutable native password buffers when it releases them. A failed update does not delete the previously stored value.
Interaction and Errors
The default SecureStorageInteraction::Forbid policy suppresses authorization dialogs.
SecureStorageInteraction::Allow permits them where supported; Windows generic-credential operations do not offer an
authorization dialog.
SecureStorageError distinguishes unsupported platforms, denied access, required interaction, cancellation, size
limits, invalid names, and native failures.
A missing item is not an error, but a retrieval failure is never treated as an empty value.
Operations may block, so call them during setup or on a worker when responsiveness matters. On macOS, standard-Keychain interaction policy is process-wide. The library serializes its own Keychain calls and restores the previous policy after each operation. Applications making independent Security.framework calls must coordinate those calls and interaction-policy changes.
Configuration Integration
text::placeholder::SecureStorageSource exposes items from one storage namespace as ${secure:item} placeholders.
See Text Placeholders for resolution and error behavior, and
Configuring TLS Profiles for a key-password configuration example.
Interface
-
class ProtectedByteBlock
An opaque byte block encrypted by the current application’s protected-data provider.
This type stores no key or provider reference. It is intended as application-lifetime memory hardening, not as a persistent encrypted format or an authorization boundary against code executing inside the process.
See: Protected Data
Public Types
-
using UnprotectedDataFn = std::function<void(mem::ConstByteSpan)>
A callback receiving a temporary unprotected byte view.
Public Functions
-
ProtectedByteBlock() noexcept = default
Create an empty protected block without accessing the application.
-
explicit ProtectedByteBlock(mem::ConstByteSpan data)
Protect a copy of the supplied bytes with the current application provider.
- Parameters:
data – The plaintext bytes to protect. An empty span creates an empty block.
- Throws:
CryptologyError – If provider initialization or encryption fails.
-
explicit ProtectedByteBlock(const mem::ByteBlock &data)
Protect an owning byte block with the current application provider.
- Parameters:
data – The plaintext bytes to protect. An empty block creates an empty protected block.
- Throws:
CryptologyError – If provider initialization or encryption fails.
-
ProtectedByteBlock(ProtectedByteBlock &&other) noexcept
Move a protected byte block.
-
ProtectedByteBlock &operator=(ProtectedByteBlock &&other) noexcept
Move a protected byte block.
-
void secureErase() noexcept
Erase this block’s encrypted envelope and restore the empty state.
-
mem::ByteBlock unprotect() const
Decrypt the block into sensitive storage.
- Throws:
CryptologyError – If the current application cannot authenticate or decrypt the envelope.
- Returns:
The authenticated plaintext, or an empty block for an empty protected block.
-
void withUnprotectedData(const UnprotectedDataFn &callback) const
Invoke a callback with an authenticated temporary plaintext view.
The temporary is securely erased before this method returns or propagates an exception.
- Parameters:
callback – The callback to invoke exactly once. Must not be empty.
- Throws:
CryptologyError – If the current application cannot authenticate or decrypt the envelope.
-
inline bool isEmpty() const noexcept
Test whether this protected block contains no data.
-
using UnprotectedDataFn = std::function<void(mem::ConstByteSpan)>
-
enum class erbsland::cryptology::ProtectedDataMode : uint8_t
Select how application-scoped protected data is encrypted.
Values:
-
enumerator Automatic
Prefer native protection and fall back to the internal provider during initialization.
-
enumerator PlatformOnly
Require the native platform provider.
-
enumerator InternalOnly
Use the bundled AES-256-GCM provider without probing native facilities.
-
enumerator Automatic
-
class SecureStorage
Synchronous current-user persistent secret storage in an application namespace.
See: Protected Data
Public Functions
-
explicit SecureStorage(text::String applicationNamespace, SecureStorageInteraction interaction = SecureStorageInteraction::Forbid)
Bind storage to an application namespace without accessing the vault.
- Parameters:
applicationNamespace – A nonempty UTF-8 name of at most 256 bytes without control characters.
interaction – Whether native authorization UI is allowed.
- Throws:
SecureStorageError – If the namespace is invalid.
-
inline const text::String &applicationNamespace() const noexcept
Get the application namespace; it is a collision boundary, not an authorization boundary.
-
SecureStorageCapabilities capabilities() const noexcept
Get capabilities without accessing or unlocking the vault.
-
std::optional<ProtectedByteBlock> read(const text::String &item) const
Read an exact item; missing and present-but-empty are distinct.
- Parameters:
item – A nonempty UTF-8 name of at most 256 bytes without control characters.
- Throws:
SecureStorageError – If access, authorization, or retrieval fails.
CryptologyError – If application-lifetime protection fails.
- Returns:
Protected plaintext, or no value if the item does not exist.
-
void write(const text::String &item, mem::ConstByteSpan data) const
Create or replace an item from explicitly borrowed plaintext bytes.
- Parameters:
item – The exact item name.
data – Borrowed plaintext, at most 2,560 bytes; the caller retains erasure responsibility.
- Throws:
SecureStorageError – If validation, authorization, or persistence fails.
-
void write(const text::String &item, const ProtectedByteBlock &data) const
Create or replace an item from protected plaintext, never persisting its memory envelope.
- Parameters:
item – The exact item name.
data – Protected bytes to decrypt only for the synchronous native operation.
- Throws:
SecureStorageError – If validation, authorization, or persistence fails.
CryptologyError – If application-lifetime decryption fails.
-
bool remove(const text::String &item) const
Remove an item.
- Parameters:
item – The exact item name.
- Throws:
SecureStorageError – If validation, authorization, or removal fails.
- Returns:
Whether the item existed.
-
explicit SecureStorage(text::String applicationNamespace, SecureStorageInteraction interaction = SecureStorageInteraction::Forbid)
-
struct SecureStorageCapabilities
Persistent storage capabilities, independent of the current authorization state.
-
class SecureStorageError : public erbsland::err::RuntimeError
A persistent secret failure without secret contents in its diagnostics.
Public Functions
-
inline SecureStorageError(SecureStorageErrorCategory category, text::String reason)
Create a categorized error.
- Parameters:
category – The failure category.
reason – The non-secret diagnostic text.
-
inline SecureStorageErrorCategory category() const noexcept
Get the failure category.
-
inline SecureStorageError(SecureStorageErrorCategory category, text::String reason)
-
enum class erbsland::cryptology::SecureStorageErrorCategory : uint8_t
A persistent secret operation failure.
Values:
-
enumerator Unsupported
No supported persistent storage backend exists.
-
enumerator AccessDenied
The operating system denied access.
-
enumerator InteractionRequired
Authorization requires forbidden interaction.
-
enumerator Cancelled
The user cancelled authorization.
-
enumerator SizeLimit
The portable 2,560-byte payload limit was exceeded.
-
enumerator InvalidName
The namespace or item name is invalid.
-
enumerator NativeFailure
The native provider failed for another reason.
-
enumerator Unsupported