Protected Data

Overview

ProtectedByteBlock keeps application-lifetime secrets in an opaque authenticated envelope. The generic key-agreement API uses this storage for X25519 private keys and shared secrets.

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.

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.

Throws:
inline bool isEmpty() const noexcept

Test whether this protected block contains no data.

inline unit::ByteLength 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.