Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

askardedum

A command-line tool for exporting and importing Aries Askar PostgreSQL wallet data.

Overview

askardedum export connects to PostgreSQL databases containing Aries Askar encrypted wallet data, decrypts all items using the provided passkey, and streams the decrypted data to JSON output. askardedum import reads such a dump back, re-encrypts it under the target store's own keys, and writes it into one of its profiles.

Together they move a wallet's contents between stores whose passkeys, key derivation methods and layouts differ — for example from a store-per-wallet deployment into one profile of a multi-tenant store, or from one agent's database into another's.

Features

  • Decrypt Askar wallet data stored in PostgreSQL
  • Load a dump into another store, including key manager entries (kind=1)
  • Support for multi-tenant wallets with sub-wallet databases
  • Streaming output to handle large datasets with minimal memory usage
  • JSON and JSON Lines (JSONL) formats, auto-detected on import (--format overrides)
  • Profile filtering for targeted extraction and loading
  • Credentials can be passed via ASKARDEDUM_DB_URI / ASKARDEDUM_PASSKEY instead of argv

Prerequisites

  • Access to the PostgreSQL database containing the Askar wallet
  • The wallet passkey used during wallet provisioning
  • Network connectivity to the database server

Building

# Native build
cargo build --release

# Cross-compile for Linux (from macOS)
cargo zigbuild --release --target x86_64-unknown-linux-musl

The binary will be located at:

  • Native: target/release/askardedum
  • Linux: target/x86_64-unknown-linux-musl/release/askardedum

Export

Single Wallet

# Output to stdout
askardedum export --db-uri "postgresql://user:pass@host:5432/wallet_db" \
                  --passkey "your_wallet_passkey"

# Output to file
askardedum export --db-uri "postgresql://user:pass@host:5432/wallet_db" \
                  --passkey "your_wallet_passkey" \
                  --output wallet_data.json

Multi-Tenant Wallets

For ACA-Py multi-tenant deployments with separate databases per sub-wallet:

askardedum export --db-uri "postgresql://user:pass@host:5432/base_wallet" \
                  --passkey "your_wallet_passkey" \
                  --sub-wallets "tenant1_db,tenant2_db,tenant3_db"

For deployments that keep every tenant as a profile of one store, export the profile of interest. Its store key is the base wallet passkey:

askardedum export --db-uri "postgresql://user:pass@host:5432/multitenant_sub_wallet" \
                  --passkey "base_wallet_passkey" \
                  --profile "<wallet_id>" \
                  --format jsonl --output tenant.jsonl

Limit Items

# Export only first 1000 items per profile
askardedum export --db-uri "postgresql://user:pass@host:5432/wallet_db" \
                  --passkey "your_wallet_passkey" \
                  --limit 1000

Export Options

Option Description
--db-uri PostgreSQL connection URI (required; env ASKARDEDUM_DB_URI)
--passkey Wallet passkey/password (required; env ASKARDEDUM_PASSKEY)
--output, -o Output file path (default: stdout)
--sub-wallets Comma-separated list of sub-wallet database names
--schema Database schema (default: "public"; plain identifiers only)
--profile Filter by specific profile name
--limit Maximum items per profile in id order (0 = unlimited; negative values are rejected)
--format Output format: json or jsonl (default: json)
--include-raw Include raw encrypted hex values for debugging

Items are exported in item id order, so --limit takes a stable prefix and two exports of the same profile can be compared line by line.

If a wallet fails part-way — a row that cannot be fetched, an item or tag that does not decrypt — the export fails rather than writing a dump that silently misses data. With --sub-wallets, the remaining wallets still run, but the exit status reports the failure.

Import

Import writes into an existing profile of an existing store: create the wallet with the owning agent first (for ACA-Py, POST /multitenancy/wallet) so that its wallet record, store and profile are set up the way that agent expects, then load the items.

# Inspect the dump and verify the target without writing anything
askardedum import --db-uri "postgresql://user:pass@host:5432/target_db" \
                  --passkey "target_passkey" \
                  --input tenant.jsonl --dry-run

# Load it
askardedum import --db-uri "postgresql://user:pass@host:5432/target_db" \
                  --passkey "target_passkey" \
                  --input tenant.jsonl

Import always reports what the input holds — item count, source wallet/profile, entry kinds and categories — before touching the target.

Choosing what to load

A dump may hold several source profiles, for instance when it was written with --sub-wallets. Since import writes to a single profile it refuses to merge them, so narrow the input first:

askardedum import --db-uri "..." --passkey "..." --input dump.jsonl \
                  --source-wallet multitenant_sub_wallet \
                  --source-profile "<wallet_id>" \
                  --skip-category acapy_version \
                  --skip-category acapy_storage_type

Skipping acapy_version and acapy_storage_type lets the target agent keep its own version bookkeeping instead of inheriting the source's.

Import Options

Option Description
--db-uri PostgreSQL connection URI of the target store (required; env ASKARDEDUM_DB_URI)
--passkey Target store passkey/password (required; env ASKARDEDUM_PASSKEY)
--input, -i Dump written by export, json or jsonl (required)
--format Input format, when the auto-detection should not decide
--schema Database schema (default: "public"; plain identifiers only)
--profile Target profile name (default: the store's default_profile)
--source-profile Load only items exported from this source profile
--source-wallet Load only items exported from this source wallet
--skip-category Category to leave behind, repeatable
--batch-size Items per transaction (default: 500)
--on-conflict error (default), skip, or replace when the item already exists
--dry-run Decode and re-encrypt every selected item and verify the target, without writing

The input is read exactly once, before anything is written: pipes and process substitutions work as --input, and the items that get written are the items that were scanned and validated. Every item's value is decoded before the first row is inserted, so a malformed item aborts a clean target instead of a half-written one. If a later batch still fails (for instance on a conflict), the error reports how many items had already been committed.

If the input itself holds the same (kind, category, name) twice — say, two concatenated exports of one wallet — the default --on-conflict error refuses up front and says so, instead of blaming the target store; skip keeps the first occurrence of each, replace the last. --on-conflict replace also clears any expiry left on the replaced row, which would otherwise keep the item invisible to Askar.

Verifying an import

Export the target and compare it with the input:

askardedum export --db-uri "postgresql://...target_db" --passkey "target_passkey" \
                  --format jsonl --output target.jsonl

diff <(jq -r '[.kind, .category] | @tsv' tenant.jsonl | sort | uniq -c) \
     <(jq -r '[.kind, .category] | @tsv' target.jsonl | sort | uniq -c)

Output Format

JSON (default)

[
  {
    "dump_version": 2,
    "wallet_name": "wallet_db",
    "profile_name": "e4b5c6d7-...",
    "profile_id": 1,
    "item_id": 123,
    "kind": 2,
    "category": "connection",
    "name": "conn-abc123",
    "value": {
      "connection_id": "abc123",
      "state": "active"
    },
    "value_text": "{\"connection_id\": \"abc123\", \"state\": \"active\"}",
    "tags": [
      {"name": "state", "value": "active", "plaintext": true}
    ]
  }
]

JSON Lines (--format jsonl)

Each item is output as a single line, useful for streaming processing:

{"wallet_name":"wallet_db","profile_name":"...","item_id":1,...}
{"wallet_name":"wallet_db","profile_name":"...","item_id":2,...}

Fields

Field Meaning
dump_version Dump schema version. Items without it (older dumps, or dumps reshaped by hand) are imported with a warning: their values are reconstructed on a best-effort basis.
wallet_name, profile_name Where the item was read from. Import reads these to keep two source profiles from being merged into one target; items missing the labels are kept apart from every labelled source and can only be imported on their own.
profile_id, item_id Source-side row ids, for reference only. Import assigns its own.
kind 2 for a stored item, 1 for a key manager entry (a key, whose value is CBOR and therefore appears hex-marked)
category, name The item's identity, unique per profile together with kind
value The value as JSON when it parses as JSON, as a string otherwise, and as "(binary:<hex>)" when it is not text
value_text The value verbatim. Written only when value alone would not reproduce the stored bytes — for instance when a JSON value carries spacing that parsing drops. Authoritative on import.
tags Decrypted tags. plaintext tags are stored unencrypted by Askar and can be queried directly; the ~ prefix used by the Askar language wrappers is not part of the stored name.
raw_category, raw_name, raw_value The still-encrypted bytes, with --include-raw, for debugging

Values round-trip byte for byte: export checks each rendering against the bytes it read and falls back to value_text whenever the rendering alone would lose anything.

Piping and Processing

# Pretty print with jq
askardedum export --db-uri "..." --passkey "..." | jq .

# Filter specific categories
askardedum export --db-uri "..." --passkey "..." | jq '[.[] | select(.category == "credential")]'

# Count items by category
askardedum export --db-uri "..." --passkey "..." | jq 'group_by(.category) | map({category: .[0].category, count: length})'

# Save to file while viewing logs
askardedum export --db-uri "..." --passkey "..." > output.json

Notes

  • Logs are written to stderr, JSON output to stdout
  • Export streams items and never holds a wallet in memory. Import reads the whole input once, up front, so it needs memory proportional to the dump; in exchange, pipes work as input and nothing is written until every item has been decoded.
  • Item expiry is not carried through: ACA-Py does not set it, imported items are written without one, and --on-conflict replace clears any expiry left on the replaced row
  • Items are written in batches of --batch-size, each batch as a couple of multi-row statements, so imports are not bound by per-item network round trips
  • Stop the agents using a store before importing into it, so that nothing writes to the profile concurrently
  • Database connection URIs should be properly escaped if they contain special characters

Compatibility with Askar

Entry categories, names and tags are encrypted with a nonce derived from the message itself, which is what makes Askar's lookups by category, name or tag work: equal plaintexts must encrypt to equal bytes. Entry values are the one field never looked up by ciphertext, and there import uses a random nonce — exactly as Askar does — so that equal or unchanged values do not reveal themselves to anyone watching the stored bytes. Askar reads the nonce prefixed to every field either way.

The encryption is covered by known-answer tests taken from an independent implementation of the construction in askar-storage/src/protect/profile_key.rs — in the encrypt direction for the deterministic fields, in the decrypt direction for values:

cargo test

Security Considerations

This tool requires:

  1. Database access credentials
  2. The wallet encryption passkey

Both are sensitive, and so is the output: a dump holds private keys, link secrets and credential attributes in the clear. Ensure you:

  • Pass credentials via the ASKARDEDUM_DB_URI and ASKARDEDUM_PASSKEY environment variables rather than argv — command-line arguments are visible to every local user in ps for as long as the run takes
  • Run on trusted systems only
  • Secure or delete output files containing decrypted data

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages