A command-line utility for creating, inspecting, and manipulating Bundle Protocol version 7 (BPv7) bundles as defined in RFC 9171.
- Overview
- Building
- Common Workflows
- Subcommand Reference
- Working with Keys
- Piping and Composition
- Exit Codes
- See Also
The bundle tool provides a comprehensive set of subcommands for working with DTN bundles:
- Bundle Creation: Create new bundles from payload data
- Inspection: Examine bundle structure and content
- Block Manipulation: Add, remove, and update extension blocks
- Security Operations: Sign, encrypt, and verify blocks using BPSec (RFC 9172/9173)
- Validation: Verify bundle correctness
cargo build --release -p hardy-bpv7-toolsThe binary will be available at target/release/bundle.
# Create a bundle with inline payload
bundle create \
--source dtn://node1/app \
--destination dtn://node2/app \
--payload "Hello, DTN!" \
--output hello.bundle
# Or from stdin
echo "Hello, DTN!" | bundle create \
--source dtn://node1/app \
--destination dtn://node2/app \
--payload-file - \
--output hello.bundle
# Inspect bundle (human-readable format)
bundle inspect hello.bundle
# Inspect bundle (JSON format)
bundle inspect --format json hello.bundle
# Inspect bundle (pretty-printed JSON)
bundle inspect --format json-pretty hello.bundle# Sign block 1 (payload) using HMAC-SHA256
bundle sign -b 1 \
--keys keys.json \
--kid hmackey \
--output signed.bundle \
input.bundle
# Encrypt block 1 using AES-GCM
bundle encrypt -b 1 \
--keys keys.json \
--kid aesgcmkey \
--output encrypted.bundle \
signed.bundle
# Verify signature
bundle verify -b 1 \
--keys keys.json \
signed.bundle
# Extract payload from encrypted bundle
bundle extract -b 1 \
--keys keys.json \
--output payload.dat \
encrypted.bundle# Add a hop-count block
bundle add-block --type hop-count \
--payload "hop-limit: 32" \
--flags must-replicate \
--output with-hop.bundle \
input.bundle
# Update block 2's payload and flags
bundle update-block -n 2 \
--payload "updated data" \
--flags must-replicate,report-on-failure \
--output updated.bundle \
input.bundle
# Remove block 2
bundle remove-block -n 2 \
--output cleaned.bundle \
input.bundleCreate a new bundle with payload.
Usage:
bundle create [OPTIONS] --source <EID> --destination <EID> (--payload <STRING> | --payload-file <FILE>)Required Arguments:
-s, --source <EID>- Source Endpoint ID (e.g.,dtn://node/serviceoripn:1.2, see RFC 9171 §4.2.5 and RFC 9758)-d, --destination <EID>- Destination Endpoint ID- Payload (one required):
-p, --payload <STRING>- Payload as string--payload-file <FILE>- Payload from file (use-for stdin)
Optional Arguments:
-r, --report-to <EID>- Report-to Endpoint ID-o, --output <OUTPUT>- Output file (default: stdout)-f, --flags <FLAGS>- Bundle processing flags (comma-separated, see RFC 9171 §4.2.3):all,noneadmin-record,admindo-not-fragment,dnfack-requested,ackreport-status-time,timereport-receiption,rcvreport-forwarding,fwdreport-delivery,dlvreport-deletion,del
-c, --crc-type <TYPE>- CRC type for both the primary and payload blocks (see RFC 9171 §4.2.1):crc16,crc32(default:crc32). Note:noneis not allowed for bundle creation because the primary block requires a CRC per RFC 9171 §4.3.1. Useupdate-blockafter creation to set a different CRC type for the payload block (e.g.,bundle update-block -n 1 --crc-type none).-l, --lifetime <DURATION>- Bundle lifetime (default: 24h)-H, --hop-limit <COUNT>- Maximum hop count
Examples:
# Create bundle with payload from file
bundle create \
--source dtn://node1/app \
--destination dtn://node2/app \
--payload-file message.txt \
--flags do-not-fragment \
--crc-type crc32 \
--lifetime 48h \
--output bundle.cbor
# Create bundle with inline payload
bundle create \
--source dtn://node1/app \
--destination dtn://node2/app \
--payload "Hello, DTN!" \
--output bundle.cborInspect and display bundle information in various formats.
Usage:
bundle inspect [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
--format <FORMAT>- Output format (default:markdown):markdown- Human-readable markdown format (default)json- Machine-readable JSON formatjson-pretty- Pretty-printed JSON format
-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for decrypting blocks during inspection (JSON string or file path)
Examples:
# Display bundle in human-readable format
bundle inspect bundle.cbor
# Output as JSON for machine processing
bundle inspect --format json bundle.cbor
# Pretty-print JSON and save to file
bundle inspect --format json-pretty -o bundle.json bundle.cbor
# Inspect bundle with encrypted blocks (requires keys)
bundle inspect --keys keys.json encrypted.bundleCheck one or more bundles for validity.
Usage:
bundle validate [OPTIONS] [INPUT]...Arguments:
<INPUT>...- One or more bundle files to validate
Optional Arguments:
--keys <JWKS>- Key set for validating encrypted bundles (JSON string or file path)
Example:
# Validate multiple bundles
bundle validate bundle1.cbor bundle2.cbor bundle3.cbor
# Validate bundle with encrypted blocks
bundle validate --keys keys.json encrypted.bundleRewrite a bundle, removing unsupported blocks and canonicalizing.
Usage:
bundle rewrite [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for processing encrypted bundles (JSON string or file path)
Example:
bundle rewrite --output clean.bundle bundle.cborExtract the payload or data from a specific block. If the block is encrypted, it will be automatically decrypted if keys are provided.
Usage:
bundle extract [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
-b, --block <NUMBER>- Block number to extract (default: 1 - payload). Block 0 is the primary block, block 1 is the payload, blocks 2+ are extension blocks (see RFC 9171 §4.1)-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for decrypting encrypted blocks (JSON string or file path)
Example:
# Extract payload
bundle extract bundle.cbor > payload.dat
# Extract block 3 data
bundle extract -b 3 bundle.cbor > block3.dat
# Extract encrypted payload
bundle extract --keys keys.json encrypted.bundle > payload.datVerifying Encryption Before Extraction:
The extract command will transparently decrypt blocks if keys are provided, regardless of whether the block was actually encrypted. To verify that a block is protected by a BCB before extracting (strict decryption), use inspect with jq:
# Check if block 1 is encrypted (has a bcb field), then extract
if bundle inspect --format json input.bundle | jq -e '.blocks["1"].bcb' > /dev/null 2>&1; then
bundle extract --keys keys.json input.bundle > payload.dat
else
echo "Error: Block 1 is not encrypted" >&2
exit 1
fiAdd an extension block to a bundle.
Usage:
bundle add-block [OPTIONS] --type <TYPE> [INPUT]Required Arguments:
-t, --type <TYPE>- Block type (see RFC 9171 §4.1):bundle-age(alias:age)hop-count(alias:hop)previous-node(alias:prev)block-integrity(alias:bib)block-security(alias:bcb)- Numeric type code (e.g.,
192for custom block types)
Block Content (one required):
-p, --payload <STRING>- Payload as string--payload-file <FILE>- Payload from file (use-for stdin)
Optional Arguments:
-o, --output <OUTPUT>- Output file (default: stdout)-f, --flags <FLAGS>- Block processing flags (comma-separated, see RFC 9171 §4.2.4):all,nonemust-replicate,replicatereport-on-failure,reportdelete-bundle-on-failure,delete-bundledelete-block-on-failure,delete-block
-c, --crc-type <TYPE>- CRC type for the block--force- Replace existing block of same type if present--keys <JWKS>- Key set for parsing bundles with encrypted blocks (JSON string or file path)
Example:
# Add a bundle-age block
bundle add-block --type bundle-age \
--payload "12345" \
--flags must-replicate \
--output with-age.bundle \
input.bundle
# Add block with force (replace if exists)
bundle add-block --type hop-count \
--payload "data" \
--force \
input.bundleUpdate an existing block's payload, flags, or CRC. If the block is a security target of a BIB or BCB, it will be automatically removed from those target lists (since the signature/encryption would be invalid after modification). If the BIB itself is encrypted, use remove-integrity first.
Usage:
bundle update-block [OPTIONS] --block-number <NUMBER> [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Required Arguments:
-n, --block-number <NUMBER>- Block number to update
Update Options (at least one):
-p, --payload <STRING>- New payload as string--payload-file <FILE>- New payload from file (use-for stdin)-f, --flags <FLAGS>- New block processing flags (comma-separated, same as add-block)-c, --crc-type <TYPE>- New CRC type
Other Arguments:
-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for parsing bundles with encrypted blocks (JSON string or file path)
Example:
# Update payload and flags of block 2
bundle update-block -n 2 \
--payload "new data" \
--flags must-replicate,report-on-failure \
--output updated.bundle \
input.bundle
# Update only CRC type
bundle update-block -n 3 \
--crc-type crc32 \
input.bundleUpdate the primary block of a bundle, including lifetime, creation timestamp, source, destination, report-to EID, flags, or CRC type. If the primary block is protected by a BIB, use remove-integrity first to remove the signature.
Usage:
bundle update-primary [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Update Options (at least one required):
-l, --lifetime <DURATION>- New lifetime duration (e.g.,1year,30days,24h)-t, --reset-timestamp- Reset creation timestamp to now-s, --source <EID>- New source Endpoint ID-d, --destination <EID>- New destination Endpoint ID-r, --report-to <EID>- New report-to Endpoint ID-f, --flags <FLAGS>- Bundle processing flags (comma-separated, replaces existing flags)-c, --crc-type <TYPE>- CRC type for the primary block
Other Arguments:
-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for parsing bundles with encrypted blocks (JSON string or file path)
Examples:
# Extend lifetime to 1 year and reset timestamp
bundle update-primary \
--lifetime 1year \
--reset-timestamp \
--output refreshed.bundle \
input.bundle
# Update just the lifetime (keep original timestamp)
bundle update-primary \
--lifetime 30days \
--output updated.bundle \
input.bundle
# Change destination EID
bundle update-primary \
--destination dtn://newnode/app \
--output redirected.bundle \
input.bundle
# Update flags
bundle update-primary \
--flags do-not-fragment,report-delivery \
--output updated.bundle \
input.bundleNote: If the primary block is signed by a BIB, you must first remove the integrity protection:
bundle remove-integrity -b 0 input.bundle | \
bundle update-primary --lifetime 1year - \
> updated.bundleRemove an extension block from a bundle. If the block is a security target of a BIB or BCB, it will be automatically removed from those target lists. If the BIB itself is encrypted, use remove-integrity first.
Usage:
bundle remove-block [OPTIONS] --block-number <NUMBER> [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Required Arguments:
-n, --block-number <NUMBER>- Block number to remove (cannot remove block 0 or 1)
Optional Arguments:
-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for parsing bundles with encrypted blocks (JSON string or file path)
Example:
bundle remove-block -n 2 --output cleaned.bundle input.bundleSign a block using BPSec Block Integrity Block (BIB) with HMAC-SHA2 (see RFC 9173 §3).
Usage:
bundle sign [OPTIONS] (--key <KEY> | --keys <KEYS> --kid <KEY_ID>) [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Key Specification (one required):
--key <KEY>- Single JWK key (JSON string or file path)--keys <KEYS> --kid <KEY_ID>- Select key from JWKS by key ID. The full keyset is also used to parse bundles with encrypted blocks.
Optional Arguments:
-b, --block <NUMBER>- Block number to sign (default: 1)-o, --output <OUTPUT>- Output file (default: stdout)-s, --source <EID>- Security source EID (default: bundle source)-f, --flags <FLAGS>- BPSec scope flags control Additional Authenticated Data (comma-separated, see RFC 9172 §3.6):all- Include all in AAD (default when no flags specified)none- Clear all flagsprimary- Include primary blocktarget- Include target headersource- Include security source header
Examples:
# Using a key from a JWKS file
bundle sign -b 1 \
--keys keys.json \
--kid hmackey \
--flags primary,target \
--output signed.bundle \
input.bundle
# Using a single JWK key
bundle sign -b 1 \
--key '{"kty":"oct","k":"..."}' \
--output signed.bundle \
input.bundleVerify the integrity signature of a block.
Usage:
bundle verify [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
-b, --block <NUMBER>- Block number to verify (default: 1)--keys <JWKS>- Key set for verification (JSON string or file path)
Example:
bundle verify -b 1 \
--keys keys.json \
signed.bundleRemove a block from BIB protection. This command removes the specified block from the BIB's security target list, discarding its integrity signature. The BIB itself is only removed from the bundle if it has no remaining security targets (see RFC 9172 §3.4).
Usage:
bundle remove-integrity [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
-b, --block <NUMBER>- Block number to remove integrity protection from (default: 1)-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for processing (JSON string or file path)
Example:
bundle remove-integrity -b 1 --output unsigned.bundle signed.bundleEncrypt a block using BPSec Block Confidentiality Block (BCB) with AES-GCM (see RFC 9173 §4).
Usage:
bundle encrypt [OPTIONS] (--key <KEY> | --keys <KEYS> --kid <KEY_ID>) [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Key Specification (one required):
--key <KEY>- Single JWK key (JSON string or file path)--keys <KEYS> --kid <KEY_ID>- Select key from JWKS by key ID. The full keyset is also used to parse bundles with encrypted blocks.
Optional Arguments:
-b, --block <NUMBER>- Block number to encrypt (default: 1)-o, --output <OUTPUT>- Output file (default: stdout)-s, --source <EID>- Security source EID (default: bundle source)-f, --flags <FLAGS>- BPSec scope flags (comma-separated, see RFC 9172 §3.6, same as sign)
Example:
bundle encrypt -b 1 \
--keys keys.json \
--kid aesgcmkey \
--output encrypted.bundle \
input.bundleDecrypt a block and remove it from BCB protection. This command removes the specified block from the BCB's security target list and restores the block's plaintext data. The BCB itself is only removed from the bundle if it has no remaining security targets (see RFC 9172 §3.4).
Usage:
bundle remove-encryption [OPTIONS] [INPUT]Arguments:
<INPUT>- Bundle file path (use-for stdin)
Optional Arguments:
-b, --block <NUMBER>- Block number to remove encryption from (default: 1)-o, --output <OUTPUT>- Output file (default: stdout)--keys <JWKS>- Key set for decryption (JSON string or file path)
Example:
bundle remove-encryption -b 1 \
--keys keys.json \
--output decrypted.bundle \
encrypted.bundleBPSec operations use JSON Web Key (JWK) format as defined in RFC 7517.
A JWK key consists of several fields that determine its usage:
Required Fields:
kty(Key Type):"oct"for symmetric keys (most common for BPSec)k(Key Value): Base64url-encoded key material
Important Optional Fields:
kid(Key ID): String identifier for the key (e.g.,"hmackey")alg(Algorithm): Key management/signing algorithm (see below)enc(Encryption Algorithm): Content encryption algorithm (for encryption operations)key_ops(Key Operations): Array of permitted operations (e.g.,["sign", "verify"])use(Public Key Use):"sig"for signature or"enc"for encryption
Key/Signing Algorithms (alg):
dir- Direct use (key used as-is)HS256,HS384,HS512- HMAC with SHA-256/384/512A128KW,A192KW,A256KW- AES Key Wrap (128/192/256-bit)- Combined:
HS256+A128KW,HS384+A256KW, etc.
Content Encryption Algorithms (enc):
A128GCM- AES-128-GCMA256GCM- AES-256-GCM
Key Operations (key_ops):
sign- Create digital signatures (BIB)verify- Verify digital signaturesencrypt- Encrypt content (BCB)decrypt- Decrypt contentwrapKey/unwrapKey- Key wrapping operations
You can generate JWK keys using standard Linux utilities like openssl. The key material must be base64url-encoded (RFC 4648 §5).
Generate HMAC-SHA256 Key (256-bit for signing):
# Generate 32 random bytes and convert to base64url
K=$(openssl rand 32 | base64 | tr '+/' '-_' | tr -d '=')
# Create JWK
cat > hmac-key.json <<EOF
{
"kty": "oct",
"kid": "hmackey",
"alg": "HS256",
"key_ops": ["sign", "verify"],
"k": "$K"
}
EOFGenerate AES-128-GCM Key (128-bit for encryption):
# Generate 16 random bytes and convert to base64url
K=$(openssl rand 16 | base64 | tr '+/' '-_' | tr -d '=')
# Create JWK
cat > aes128-key.json <<EOF
{
"kty": "oct",
"kid": "aes128key",
"alg": "dir",
"enc": "A128GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "$K"
}
EOFGenerate AES-256-GCM Key (256-bit for encryption):
# Generate 32 random bytes and convert to base64url
K=$(openssl rand 32 | base64 | tr '+/' '-_' | tr -d '=')
# Create JWK
cat > aes256-key.json <<EOF
{
"kty": "oct",
"kid": "aes256key",
"alg": "dir",
"enc": "A256GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "$K"
}
EOFCreate a JWKS Key Set:
# Combine multiple keys into a key set using jq
jq -n --slurpfile hmac hmac-key.json \
--slurpfile aes128 aes128-key.json \
--slurpfile aes256 aes256-key.json \
'{keys: [$hmac[0], $aes128[0], $aes256[0]]}' > keys.json
# Or manually create the key set
cat > keys.json <<'EOF'
{
"keys": [
{
"kty": "oct",
"kid": "hmackey",
"alg": "HS256",
"key_ops": ["sign", "verify"],
"k": "YOUR_BASE64URL_HMAC_KEY_HERE"
},
{
"kty": "oct",
"kid": "aes128key",
"alg": "dir",
"enc": "A128GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "YOUR_BASE64URL_AES128_KEY_HERE"
},
{
"kty": "oct",
"kid": "aes256key",
"alg": "dir",
"enc": "A256GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "YOUR_BASE64URL_AES256_KEY_HERE"
}
]
}
EOFNote: Base64url encoding differs from standard base64:
- Uses
-instead of+ - Uses
_instead of/ - Omits padding
=characters
Complete Example - Generate Keys and Use Them:
# 1. Generate an HMAC key for signing
K=$(openssl rand 32 | base64 | tr '+/' '-_' | tr -d '=')
cat > test-hmac.json <<EOF
{
"kty": "oct",
"kid": "test-hmac",
"alg": "HS256",
"key_ops": ["sign", "verify"],
"k": "$K"
}
EOF
# 2. Generate an AES key for encryption
K=$(openssl rand 16 | base64 | tr '+/' '-_' | tr -d '=')
cat > test-aes.json <<EOF
{
"kty": "oct",
"kid": "test-aes",
"alg": "dir",
"enc": "A128GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "$K"
}
EOF
# 3. Create a key set
jq -n --slurpfile hmac test-hmac.json \
--slurpfile aes test-aes.json \
'{keys: [$hmac[0], $aes[0]]}' > test-keys.json
# 4. Use the keys with bundle tool
echo "Test message" | \
bundle create -s dtn://alice -d dtn://bob --payload-file - | \
bundle sign -b 1 --keys test-keys.json --kid test-hmac - | \
bundle encrypt -b 1 --keys test-keys.json --kid test-aes - | \
bundle remove-encryption -b 1 --keys test-keys.json - | \
bundle verify --keys test-keys.json - | \
bundle extract -For signing/encryption operations:
- With
--key <JWK>: The provided key is used directly - With
--keys <JWKS> --kid <KEY_ID>: The key with matchingkidis selected
For verification/decryption operations:
- With
--keys <JWKS>: The tool automatically selects keys that:- Have the required operation in their
key_opsfield - Match the algorithm specified in the security block
- Have the required operation in their
HMAC-SHA256 Key for Signing:
{
"kty": "oct",
"kid": "hmackey",
"alg": "HS256",
"key_ops": ["sign", "verify"],
"k": "AES256...base64url..."
}AES-GCM Key for Encryption:
{
"kty": "oct",
"kid": "aesgcmkey",
"alg": "dir",
"enc": "A128GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "AES128...base64url..."
}JWKS Key Set:
{
"keys": [
{
"kty": "oct",
"kid": "hmackey",
"alg": "HS256",
"key_ops": ["sign", "verify"],
"k": "..."
},
{
"kty": "oct",
"kid": "aesgcmkey",
"alg": "dir",
"enc": "A256GCM",
"key_ops": ["encrypt", "decrypt"],
"k": "..."
}
]
}# Using a single JWK from a file
bundle sign -b 1 --key mykey.jwk input.bundle
# Using a single JWK as JSON string
bundle sign -b 1 --key '{"kty":"oct","alg":"HS256","k":"..."}' input.bundle
# Using a key from a JWKS file by kid
bundle sign -b 1 --keys keys.json --kid hmackey input.bundle
# Verification automatically finds the right key based on key_ops
bundle verify -b 1 --keys keys.json signed.bundle
# Encryption with specific key
bundle encrypt -b 1 --keys keys.json --kid aesgcmkey input.bundle
# Extract from encrypted bundle (automatically decrypts with matching key)
bundle extract -b 1 --keys keys.json encrypted.bundleThe tool is designed for Unix-style composition using pipes:
# Create, sign, encrypt, and save in one pipeline
echo "Secret message" | \
bundle create -s dtn://alice/app -d dtn://bob/app --payload-file - | \
bundle sign -b 1 --keys keys.json --kid sign-key | \
bundle encrypt -b 1 --keys keys.json --kid encrypt-key \
> secure.bundle
# Remove encryption, verify, and extract
bundle remove-encryption -b 1 --keys keys.json secure.bundle | \
bundle verify --keys keys.json - | \
bundle extract - > message.txt0- Success- Non-zero - Error (with diagnostic message to stderr)