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.

inline unit::ByteCount byteLength() const noexcept

Get the plaintext byte length without decrypting the block.

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.

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

Public Static Functions

static inline constexpr unit::ByteCount maximumPayloadLength() noexcept

Get the common maximum plaintext payload length.

static bool isValidName(const text::String &name) noexcept

Validate portable namespace and item syntax without accessing a vault.

Parameters:

name – The namespace or item name.

Returns:

Whether the UTF-8 name is nonempty, bounded, and free of control characters.

struct SecureStorageCapabilities

Persistent storage capabilities, independent of the current authorization state.

Public Members

bool supported = {false}

Persistent storage is implemented on this platform.

bool authorizationUi = {false}

Explicit interaction opt-in can display authorization UI.

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.

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.

enum class erbsland::cryptology::SecureStorageInteraction : uint8_t

Whether a persistent secret operation may display native authorization UI.

Values:

enumerator Forbid

Fail rather than displaying authorization UI.

enumerator Allow

Allow native authorization UI where the platform supports it.