API KID Management
Crate: kinetic-daemon Stage: 9 Reading time: 15 minutes Depends on: kinetic-kid (Stage 3), kinetic-core (Stage 7), kinetic-network (Stage 8)
What Is This?
This file (src/api/kid.rs) provides the REST API endpoints that external clients use to manage Kinetic Identity Documents (KIDs). It acts as the critical bridge between standard HTTP JSON requests and the complex cryptographic and network operations required to participate in the Kinetic identity system.
Instead of requiring every client application (like a CLI tool, a web dashboard, or an automated service) to understand ML-DSA cryptography, they simply talk to this API over HTTP. External clients do not need to generate proper Decentralized Identifiers (DIDs) locally. External clients do not need to construct raw cryptographic signatures. External clients do not need to communicate with the peer-to-peer network directly. When a user wants to create a new identity on the network, they simply send a structured JSON POST request with their desired domain name to the daemon. The daemon handles all of the heavy lifting on their behalf. It generates the quantum-resistant keys internally. It constructs the standard-compliant KID document according to the strict schema. It double-signs the payload to prove authorization and data integrity. It propagates the final document across the Distributed Hash Table (DHT) so other nodes can discover it.
This file essentially turns the daemon into an Identity Provider (IdP) agent for the local node. It is the entry point for all identity creation and management on a given Kinetic node.
An Analogy for the Daemon’s API
Think of the Kinetic network as a secure corporate building. You cannot just walk in and claim you are the new security guard. You need a cryptographic badge, issued by the central HR department. In this analogy, the external client application is the new employee. The Kinetic daemon is the HR department. This API is the front desk of the HR department. The employee walks up to the desk (POST /kids) and says “My name is admin.saif.kin”. The HR department generates the secure cryptographic badge (KidDocument). The HR department signs it with their master stamp to authorize it (AuthorizedKid). Finally, the HR department logs this new badge into the global corporate directory (the DHT). The employee never had to know how to print the badge or use the master stamp themselves.
Why Kinetic Needs This
Kinetic is fundamentally an identity-driven network. Every action, message, and piece of data in the system is cryptographically tied to a KID. However, the operations required to create and manage a KID are specialized and computationally delicate.
If this API module did not exist, any application wanting to interact with Kinetic would be forced to reimplement a massive amount of complex logic. They would have to bundle the post-quantum cryptography libraries, specifically the ml-dsa crate. They would have to implement the exact SHA-256 hashing logic required to derive a valid KineticDid from a raw public key. They would have to understand the strict, evolving schema of the KidDocument and the AuthorizedKid wrapper. They would have to maintain a persistent, raw connection to the P2P swarm to publish documents to the DHT reliably. Most dangerously, they would have to store and manage their own private key material on the filesystem.
By centralizing KID management in the daemon’s API, Kinetic ensures that cryptographic operations are performed in a secure, uniform environment. The daemon acts as the secure vault that holds the private keys safely in its configuration directory. The daemon acts as the reliable, always-on network node that guarantees data propagation to the DHT. The daemon also ensures that all identity operations are guarded by its internal Role-Based Access Control (RBAC) system. This RBAC integration guarantees that only authorized users or processes can create or rotate identities on the node. Without this centralized API, rogue scripts or compromised client applications could easily spam the network with invalid or unauthorized identities.
How It Works
The KID API lifecycle involves local filesystem storage, quantum-resistant key generation, dual-layered cryptographic signing, and asynchronous network propagation. This module uses the axum web framework to expose these workflows as HTTP endpoints. Here is the step-by-step breakdown of how these mechanisms operate under the hood.
1. Local Storage Management
All KIDs are stored locally on the filesystem within a dedicated kids directory. This directory is located inside the daemon’s base configuration path, which is resolved dynamically at runtime.
-> See: kinetic-daemon/src/api/kid.rs — Lines 23 to 25 (get_kids_dir)
For every KID managed by the daemon, exactly two files are stored side-by-side:
{fqdn}.json: The public KID document, serialized as human-readable JSON. This can be safely shared.{fqdn}.key: The private ML-DSA key seed, stored as raw, unencrypted bytes. This must be protected.
When listing or retrieving KIDs (handle_list_kids, handle_get_kid), the daemon simply reads these JSON files directly from the disk. It acts like a local, file-based database for identity documents, avoiding the overhead of a full SQL database for simple identity storage.
2. The Generation Flow
Generating a new KID is a multi-step orchestration handled by the handle_generate_kid function. This process ensures the identity is cryptographically sound, bound to the correct domain, and network-ready.
Step A: Authorization and Normalization
The daemon first intercepts the request and checks if the caller has the Publish role. If the caller lacks permission, it immediately rejects the request with an HTTP 403 Forbidden. If authorized, it normalizes the requested domain name. It combines the base name and an optional sub-name into a single Fully Qualified Domain Name (FQDN).
-> See: kinetic-daemon/src/api/kid.rs — Lines 81 to 93
Step B: Key Generation and DID Derivation
The daemon invokes the ml-dsa library to generate a new quantum-resistant keypair. Specifically, it uses the MlDsa65 parameter set. It extracts the public verifying key from this pair. It encodes this public key in URL-safe Base64 without padding. It then hashes the raw public key bytes using the SHA-256 algorithm. This hashing step creates a deterministic fingerprint of the key. This hash is then converted to a hexadecimal string. It is prefixed with the standard DID prefix (did:kin:) to create a globally unique KineticDid.
-> See: kinetic-daemon/src/api/kid.rs — Lines 98 to 113
Step C: Document Construction and Inner Signature
A KidDocument struct is constructed in memory. The newly generated DID is set as the document’s canonical kid. The URL-safe Base64 public key is added as the primary ControllerKey. The daemon then signs this entire document struct using the newly generated ML-DSA private signing key. This inner signature is critical: it proves that the document was created by the actual holder of the controller key, preventing tampering.
-> See: kinetic-daemon/src/api/kid.rs — Lines 114 to 134
Step D: Filesystem Persistence
The daemon serializes the signed KidDocument to pretty-printed JSON and writes it to disk. It simultaneously writes the raw private key bytes to the corresponding .key file in the same directory.
-> See: kinetic-daemon/src/api/kid.rs — Lines 135 to 141
Step E: The Outer Signature (Authorization Wrap)
This is a core architectural concept in Kinetic’s identity system. Just because a KID document has a valid internal signature doesn’t mean it has the right to claim a specific domain name (like admin.saif.kin). To prove authority over the domain name, the signed KidDocument is wrapped in an AuthorizedKid struct. The daemon loads its root identity key (identity.key) from the base configuration directory. It signs the AuthorizedKid wrapper using this root identity key. This outer signature acts as an attestation. It tells the network: “The owner of this daemon authorizes this specific KID document to operate under this specific domain name.”
-> See: kinetic-daemon/src/api/kid.rs — Lines 143 to 157
Step F: Network Publication
Finally, the double-signed AuthorizedKid struct is serialized into a raw byte vector. It is pushed to the DHT using the network layer’s publish_redundant_payload function. This makes the identity discoverable by any other node in the Kinetic network.
-> See: kinetic-daemon/src/api/kid.rs — Lines 158 to 160
3. The Rotation Flow
Keys can be compromised, or they may simply age out of a security policy, requiring rotation. The rotation flow (handle_rotate_kid) follows similar steps to generation, but with one vital cryptographic difference: the chain of trust.
When updating the KidDocument with a new controller key, the updated document MUST be signed by the OLD private key. The daemon reads the old key file from disk, assuming it contains a 32-byte seed. It reconstitutes the old signing key from this seed. It generates a new ML-DSA-65 keypair and replaces the controller key in the document. It then signs this rotation update with the OLD key. This proves to the entire network that the entity rotating the key is the legitimate owner of the previous key. It maintains a continuous chain of cryptographically verifiable authority. After signing, the new key replaces the old key on disk, the document is re-wrapped in an AuthorizedKid, re-signed by the root identity, and published to the DHT.
-> See: kinetic-daemon/src/api/kid.rs — Lines 210 to 225
4. Axum Web Framework Integration
This module is built on top of axum, a modern web framework for Rust. It relies on Axum’s extractor pattern to parse requests and manage state safely.
Path<String>: Used inhandle_get_kidandhandle_rotate_kidto safely extract the target domain name directly from the URL path.Json<T>: Used to automatically deserialize incoming HTTP bodies into Rust structs (likeGenerateKidRequest), and to serialize outgoing responses directly from raw JSON values.State<ApiState>: Provides safe, concurrent access to the daemon’s global state, primarily the network handle needed for DHT publication.Extension<Role>: Injects the authenticated user’s role into the handler, enabling the RBAC checks that protect generation and rotation.
Key Pieces
GenerateKidRequest
-> See: kinetic-daemon/src/api/kid.rs — Lines 15 to 21
This is the incoming JSON payload structure used when a client wants to create an identity. It derives serde::Deserialize to automatically parse HTTP request bodies. It splits the desired identity into a mandatory base_name (e.g., saif.kin) and an optional sub_name (e.g., admin). The API handler normalizes and combines these strings into a single canonical FQDN.
get_kids_dir
-> See: kinetic-daemon/src/api/kid.rs — Lines 23 to 25
A small but crucial helper function that resolves the exact filesystem path where KIDs are stored. It queries the global configuration crate for the active base directory. The base directory dynamically changes depending on whether the daemon is running in production, testing, or a custom specified path. It then appends the kids subdirectory to this base path. This guarantees that all KID-related filesystem operations remain isolated from other daemon data like logs or databases.
handle_list_kids
-> See: kinetic-daemon/src/api/kid.rs — Lines 28 to 51
This function handles GET requests to list all locally managed KIDs. It iterates over the kids directory on the filesystem using std::fs::read_dir. It reads every .json file and attempts to parse it as a KidDocument using serde_json::from_str. It collects all valid, parseable documents into a vector. Instead of returning strongly typed Rust structs, it returns a generic serde_json::Value. This provides flexibility but means the compiler cannot guarantee the final JSON schema structure matches the API specification perfectly. It returns them to the caller wrapped in an axum::Json response. It safely ignores invalid files, directories, or the raw .key files.
handle_get_kid
-> See: kinetic-daemon/src/api/kid.rs — Lines 54 to 73
This handler retrieves a specific KID document by its requested domain name. It extracts the name using the axum::extract::Path extractor. It normalizes the requested name to prevent directory traversal attacks or mismatch issues. It constructs the expected filesystem path. It reads the file and serves the parsed JSON if it exists and is valid. It returns a standard HTTP 404 Not Found error tuple if the local daemon does not possess that KID.
handle_generate_kid
-> See: kinetic-daemon/src/api/kid.rs — Lines 76 to 168
This is the most complex and orchestrated handler in the module. It manages the entire creation lifecycle of new identities on the node. It uses several Axum extractors to gather context, including Extension<Role>, State<ApiState>, and Json<GenerateKidRequest>. The function performs the following strict sequence of operations:
- Validates that the caller holds at least a
Publishrole. - Normalizes the incoming
base_nameandsub_nameinto a single FQDN. - Generates a new
ML-DSA-65signing keypair. - Encodes the public key using
base64 URL-safe, no-paddingencoding. - Hashes the raw public key with SHA-256 to derive the unique
KineticDid. - Constructs the in-memory
KidDocumentsetting the DID and controller keys. - Signs the document using the newly generated key.
- Writes both the JSON document and the raw key seed to the local filesystem.
- Wraps the signed document into an
AuthorizedKidstruct. - Loads the daemon’s root
identity.keyfrom disk. - Signs the
AuthorizedKidwrapper with the daemon’s root identity to prove ownership. - Publishes the double-signed payload asynchronously to the DHT.
- Returns the final document as a JSON response to the client.
handle_rotate_kid
-> See: kinetic-daemon/src/api/kid.rs — Lines 171 to 256
This handler manages the delicate and sensitive process of key rotation. Key rotation is critical in case of suspected compromise or routine security policy enforcement. It performs the following sequence of operations:
- Validates the caller’s RBAC permissions (
PublishorAdmin). - Normalizes the target FQDN from the URL path.
- Verifies that both the local
.jsondocument and the.keyfile exist on disk. - Reads and parses the existing
KidDocumentfrom disk. - Generates an new
ML-DSA-65keypair. - Replaces the primary controller key in the document with the new public key.
- Reads the OLD key seed from disk.
- Validates that the old key seed is exactly 32 bytes long.
- Reconstitutes the OLD signing key from the 32-byte seed.
- Signs the updated document using the OLD key to prove cryptographic continuity.
- Overwrites the local
.jsonand.keyfiles with the new data. - Wraps the updated document in an
AuthorizedKid. - Re-signs the authorization wrapper with the daemon’s root identity key.
- Publishes the newly rotated identity to the DHT to inform the rest of the network.
How This Connects to the Rest of Kinetic
This API module acts as the integration glue tying together several lower-level Kinetic crates into a unified HTTP interface:
- CROSS-CRATE:
kinetic-kid— Provides the foundational cryptographic data structures. Specifically, this module usesKidDocument,KineticDid, andControllerKey. This API instantiates, populates, signs, and manipulates these core structures. - CROSS-CRATE:
kinetic-core— Supplies the criticalAuthorizedKidwrapper necessary for proving domain authority. It also provides the name normalization utility (kinetic_core::types::normalize_name) and global configuration path resolution (kinetic_core::config::get_base_dir). - CROSS-CRATE:
kinetic-network— The API utilizes the network state viastate.network.publish_redundant_payloadto push the finalized, double-signed identity documents out to the broader peer-to-peer network via the DHT, making them globally discoverable.
Quick Reference
- Storage Location:
<base_dir>/kids/{fqdn}.json(public JSON document) and<base_dir>/kids/{fqdn}.key(private ML-DSA seed bytes). - GET
/kids: Lists all locally stored KIDs. Handled exclusively byhandle_list_kids. - GET
/kids/:name: Retrieves a specific local KID document by name. Handled byhandle_get_kid. - POST
/kids: Generates a new KID, double-signs it, saves it locally to disk, and publishes it to the DHT network. RequiresPublishpermissions. Handled byhandle_generate_kid. - POST
/kids/:name/rotate: Generates a new key for an existing KID, signs the update with the old key, saves the new files, and republishes. RequiresPublishpermissions. Handled byhandle_rotate_kid. - Cryptography Standard: Exclusively uses
ML-DSA-65for all KID controller keys. - Inner Signature: The
KidDocumentis signed by its own newly generated KID key to prevent tampering. - Outer Signature: The
AuthorizedKidwrapper is signed by the daemon’s rootidentity.keyto cryptographically prove domain ownership and authorization.
Open Questions / Things to Revisit
- Plaintext Secret Storage: Currently, the ML-DSA private key seeds (
{fqdn}.key) are written directly to disk in raw plaintext format. If the host machine’s filesystem is compromised, all hosted identities are instantly compromised. Should these keys be encrypted at rest using a daemon-level master password or integrated into OS-level secure keyrings (like Secret Service or Keychain)? - Hardcoded Key Length Assumptions: In
handle_rotate_kid, the code checksif old_key_data.len() != 32. This operates on the hard assumption that ML-DSA seeds will always be exactly 32 bytes long. If the cryptographic backend changes or supports multiple algorithm variants with different seed sizes in the future, this hardcoded check will fail and block key rotation. - Network Publication Reliability: The
publish_redundant_payloadcall happens asynchronously, and its return Result is ignored usinglet _ = .... If the daemon is temporarily disconnected from the DHT during a generation or rotation event, the identity is updated locally but the network never learns about it. The API still returns a success response to the client, leading to a split-brain state. There should likely be a background sync job, a retry queue, or better error reporting if DHT publication fails. - Error Types in API: The error handling directly passes formatted strings inside untyped JSON structs (
Json(serde_json::json!({"error": format!(...)}))). A structured error enum (e.g., implementingaxum::response::IntoResponse) might be safer, less repetitive, and significantly easier for typed API clients to consume reliably than parsing arbitrary strings.