Skip to content

feat(core): managed (non-plaintext) IdentityStore for production keys #19

Description

@andyjsbell

Branch: feat/managed-identity-store
Depends on: current main
Priority: P2 — plaintext signing keys block the regulated ICP.

Prompt:

Agent signing identities are persisted by FileIdentityStore, which writes raw ed25519 secret keys hex-encoded in plaintext JSON under data/identities/<agent_id>.json. The module rustdoc is explicit that this is "appropriate for development and demo use only" and that production should use an OS keychain, HSM, or KMS — but no such implementation exists, and the Python attest() entry point silently auto-creates plaintext keys with no opt-out. The regulated mid-market buyers named as the primary ICP cannot accept plaintext signing keys on disk. This task ships at least one non-plaintext IdentityStore implementation behind the existing trait seam.

Context

  • crates/awp-core/src/identity.rs:148 — the IdentityStore trait (the intended production seam). identity.rs:176FileIdentityStore (the only impl), impl IdentityStore for FileIdentityStore at identity.rs:200. Storage format and dev-only warnings are documented at identity.rs:18-69; a commented-out KmsIdentityStore sketch sits at identity.rs:60.
  • AgentIdentity::load_or_create (identity.rs:104) takes a store and an agent id.
  • crates/awp-python/src/lib.rs:100-106 — the Python binding's load_or_create delegates to FileIdentityStore. python/awp-langgraph/awp/langgraph/wrapper.py calls this with no way to select a managed store.
  • The IdentityStore trait is the abstraction to implement against; do not change its shape.

Your Task

  1. Choose and implement one managed backend. Preferred first target is an OS-keychain store (portable, no cloud dependency) or an encrypted-at-rest file store using a KEK from env/KMS. Add crates/awp-core/src/identity/<backend>.rs (or a submodule) implementing IdentityStore:
    • Option A (recommended first): EncryptedFileIdentityStore — same on-disk layout as FileIdentityStore but the secret key is sealed with an AEAD (e.g. chacha20poly1305) under a key-encryption-key read from AWP_IDENTITY_KEK (or fetched from a KMS). The secret is never written in cleartext.
    • Option B: KeyringIdentityStore — stores the secret in the OS keychain via the keyring crate.
      Pick one; ask before adding the crypto/keyring dependency if it isn't already vendored, per the repo's dep policy.
  2. Expose it through the bindingcrates/awp-python/src/lib.rs: add a way for Python callers to select the managed store (e.g. an enum/string parameter on the identity constructor, or a distinct AgentIdentity.load_or_create_encrypted(...)).
  3. Give the SDK an opt-inpython/awp-langgraph/awp/langgraph/wrapper.py: let attest(...) accept an identity-store selector so a caller can require the managed store instead of silently getting plaintext-on-disk. Keep FileIdentityStore as the default for local dev, but make the managed path a first-class option and document it.
  4. Document the security posture — update the rustdoc in identity.rs so the recommended production path points to the new impl rather than only warning against FileIdentityStore.

Do Not Touch

  • The IdentityStore trait signature (identity.rs:148) — implement against it.
  • signing.rs — key generation and signing are correct; this task is about at-rest storage only.
  • FileIdentityStore — keep it as the documented dev/demo default; do not remove it.

Verification

make check
# → lint and tests pass

# Rust round-trip test (add to crates/awp-core/src/identity.rs tests):
cargo test -p awp-core identity::encrypted
# → generate → seal → reload yields the same agent_id/public_key; the
#   on-disk bytes do NOT contain the raw secret key hex

# Prove the secret is not cleartext on disk:
cargo test -p awp-core identity::encrypted_at_rest_is_sealed
# → asserts the persisted file does not contain the known secret bytes

# Python selection path:
make check-python
# → a pytest asserts attest(..., identity_store="encrypted") signs and
#   verifies without ever writing a plaintext key file

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions