canaad-core 2.0.0

Core library for AAD canonicalization per RFC 8785
Documentation

canaad-core

crates.io MSRV

Parse, validate, and canonicalize AAD contexts per RFC 8785. Produces deterministic bytes for use as AEAD additional authenticated data — same input, same bytes, across Rust, WASM, and any conforming implementation.

Use it

Default profile

Parse existing JSON:

use canaad_core::parse_default;

let json = r#"{"v":1,"tenant":"org_abc","resource":"secrets/db","purpose":"encryption"}"#;
let ctx = parse_default(json)?;
let canonical = ctx.canonicalize_string()?;

Build from scratch:

use canaad_core::AadContext;

let ctx = AadContext::new("org_abc", "secrets/db", "encryption")?
    .with_timestamp(1706400000)?
    .with_string_extension("x_vault_cluster", "us-east-1")?;

let bytes = ctx.canonicalize()?;

Use the builder if you prefer:

use canaad_core::AadContext;

let ctx = AadContext::builder()
    .tenant("org_abc")
    .resource("secrets/db")
    .purpose("encryption")
    .extension_string("x_vault_cluster", "us-east-1")
    .build()?;

Builder defers all validation to build() — invalid extensions surface as errors, not silent drops.

Generic object layer

Canonicalize any valid JSON object without profile validation — no required fields, no version check:

use canaad_core::canonicalize_object;

let bytes = canonicalize_object(r#"{"z":"last","a":"first"}"#)?;
// → {"a":"first","z":"last"} (keys sorted per JCS)

Use this layer to build custom profiles on top of canaad-core.

What it checks

  • v must be 1
  • tenant: 1–256 bytes, no NUL
  • resource: 1–1024 bytes, no NUL
  • purpose: 1+ bytes, no NUL
  • ts: optional, 0 to 2^53−1
  • extensions: x_<app>_<field> pattern, values are strings or integers
  • no duplicate keys (custom single-pass scanner, not serde_json)
  • 16 KiB max serialized size

All 17 error variants are strongly typed via AadError. See docs.rs/canaad-core for the full API.

Spec

gnu.foo/specs/aad-spec — field constraints, extension patterns, test vectors.