reVault for Dart

reVault is a fast, local toolkit for creating secure portable archives called Lockboxes. Each Lockbox is encrypted, compressed, and signed. It can store files and directory trees, variables such as API keys, and forms such as login details.

Lockboxes are easy to copy, share, and back up, and they do not require a hosted service. The engine is designed for speed and effective compression. Applications can read, write, and seek within stored files without extracting the archive, and recover data from partial corruption. reVault provides a command line tool for everyday work and APIs for application code.

Read the reVault manual for the quick start, core concepts, and security model.

Your Vault holds your profile and contacts. The CLI protects a new Lockbox for your profile by default, and you can grant access to contacts using their public keys. Use password access when you do not have a recipient's contact (public key) details.

revault_api provides typed Dart classes for Lockboxes, the Vault, and the optional session agent. The native core is implemented in Rust, while the public API uses Dart domain objects and explicitly owned secret values.

The Dart package supports Dart and Flutter desktop applications on Linux, macOS, and Windows, on both x86-64 and ARM64. It does not currently support Dart web applications: this binding uses dart:ffi and dart:io. Projects targeting browsers or WebAssembly should use the JavaScript/WASM package.

Installation

Add the current package from pub.flutter-io.cn:

dart pub add revault_api

Then load the bundled native runtime once during application startup:

import 'package:revault_api/revault_api.dart';

Future<void> main() async {
  await Revault.load();
  // Vault, Lockbox, and AgentSession are now ready to use.
}

The current release is 0.3.15. Normal applications do not need to download a native library or set an environment variable; the package build hook bundles the correct native library.

Create a Vault and Lockbox in Dart

The following setup creates or opens a Vault, creates a Lockbox protected with a password, and stores the Lockbox password inside the encrypted Vault. It uses SecretString.fromString to keep the example complete on its own. A production password prompt should supply owned UTF-8 bytes to SecretString.takeUtf8 so the original input buffer can also be wiped.

import 'dart:io';

import 'package:revault_api/revault_api.dart';

Future<void> main() async {
  await Revault.load();

  final workspace = Directory('.revault-example')..createSync();
  final vaultPassphrase = SecretString.fromString('replace-this-passphrase');
  final lockboxPassword = SecretString.fromString('replace-this-password');
  final vault = Vault.openOrCreate(
    pathTo: '${workspace.path}/vault',
    passphrase: vaultPassphrase,
  );

  try {
    final path = '${workspace.path}/team-secrets.lbox';
    final lockbox = File(path).existsSync()
        ? Lockbox.open(path, password: lockboxPassword)
        : Lockbox.create(path, password: lockboxPassword);
    try {
      lockbox.setDescription('Team deployment credentials');
      lockbox.commit();
      vault.rememberPassword(lockbox.id, lockboxPassword);
    } finally {
      lockbox.close();
    }
  } finally {
    vault.close();
    lockboxPassword.close();
    vaultPassphrase.close();
  }
}

A complete runnable version with interactive password prompts is included in example/getting_started.dart.

Create a Vault and Lockbox with the CLI

Install the reVault CLI, initialize the default Vault and profile, and create a Lockbox for that profile:

cargo install revault_cli
lbx vault init
lbx team-secrets.lbox create \
  --description 'Team deployment credentials'

The CLI prints recovery material during Vault initialization. Store that material securely: losing both the Vault and its recovery material can make Lockboxes protected by a profile unrecoverable.

The Dart application can then open the Vault and Lockbox created with the CLI using the Vault passphrase:

import 'package:revault_api/revault_api.dart';

Future<void> openExisting(SecretString vaultPassphrase) async {
  await Revault.load();
  final vault = Vault.open(passphrase: vaultPassphrase);
  try {
    final lockbox = Lockbox.open('team-secrets.lbox', vault: vault);
    try {
      print(lockbox.description);
      print(lockbox.list('/', recursive: true));
    } finally {
      lockbox.close();
    }
  } finally {
    vault.close();
  }
}

Core API concepts

  • Revault loads the native runtime shared by the process and provides key generation, import, export, and format utilities.
  • Vault is the persistent encrypted store for profiles, private keys, contacts, signing keys, and remembered Lockbox credentials and metadata.
  • Lockbox is a portable encrypted .lbox archive containing files, variables, secrets, and structured forms.
  • AgentSession controls the optional Session Agent process and its temporary cache of decrypted Lockbox content keys.

Passwords and passphrases use SecretString; binary keys use SecretBytes. Both own wipeable byte storage and must be closed. SecretString.fromString cannot erase the immutable Dart String used to create it, so password input adapters should return owned UTF-8 bytes for SecretString.takeUtf8 whenever possible.

Use the optional Session Agent

Ordinary Lockbox.open calls keep their state in this process and never start or contact the agent. Use AgentSession when Lockbox keys need to be shared across processes or remain available after the process that opened the Lockbox exits:

final agent = AgentSession.instance;
agent.start();
agent.keepOpenWithPassword(
  'team-secrets.lbox',
  lockboxPassword,
  duration: const Duration(minutes: 30),
);

final lockbox = agent.acquireOpenLockbox('team-secrets.lbox');
try {
  print(lockbox.list('/', recursive: true));
} finally {
  lockbox.close();
}

agent.closeLockbox('team-secrets.lbox');

The agent stores a temporary content key, not an open file handle. Acquiring a Lockbox does not extend the agent entry's lifetime. The returned handle owns an independent key and remains usable after agent expiry until it is closed.

Lockbox descriptions

A Lockbox can carry a readable description of its purpose. The text is stored inside the encrypted archive rather than its public header, so it can be read only after the Lockbox is opened.

lockbox.setDescription(
  'Production deployment credentials and recovery material',
);
lockbox.commit();
print(lockbox.description);

lockbox.clearDescription();
lockbox.commit();

Platform credential store

Vault.rememberPassphrase stores the Vault passphrase in the operating system credential store. The user's operating system login normally unlocks that store. After login, another process running as that user may be able to retrieve the passphrase if the access policy applied to the saved Vault passphrase does not require approval for each retrieval. Exact access depends on the operating system, the credential store configuration, and that access policy.

A process that retrieves the Vault passphrase can open the Vault. The Vault can then provide access to Lockboxes through profile keys or remembered Lockbox passwords. Both remain encrypted inside the Vault; they are not copied to the operating system credential store.

With no explicit credential, Lockbox.open(path) opens the default Vault using the passphrase stored by the platform and asks the Vault for a matching profile key or remembered Lockbox password. It does not contact the Session Agent.

Agent expiry and AgentSession.closeAll() are therefore not authentication boundaries after login if the saved Vault passphrase can be retrieved without approval. The agent provides stronger isolation when the user unlocks the Vault interactively, retains only selected Lockbox keys in the agent, and then closes the Vault.

For services launched with sudo, see Opening a reVault Vault after sudo.

Native runtime distribution

The pub.flutter-io.cn package uses Dart native assets. Its build hook selects the library for the application's operating system and architecture and bundles it into Dart CLI and Flutter desktop builds. Revault.load() opens that bundled library. An application that maintains its own copy can provide its path explicitly:

await Revault.load(
  nativeLibraryPath: '/opt/my_app/lib/librevault_api.so',
);

Resolution order is an explicit path, a nonempty inherited REVAULT_LIBRARY, and then the library bundled with the package. A library name rather than a path uses the operating system's normal search rules.

More information

Libraries

revault_api
Encrypt files, variables, and typed form records in portable reVault lockboxes, and manage keys and local vault metadata.