# Elixir API

The `KMS` module is the public facade. Calls route through the configured client:

```elixir
config :kms, :client, KMS.Client.Local
# or
config :kms, :client, KMS.Client.Remote
```

Most return values are `{:ok, value}` or `{:error, reason}`. Signing-key operations are embedded-only and access the configured Ecto repository and RMK provider directly; they are not exposed by `KMS.Client.Remote` or `KMS.API`.

## Signing keys

Install an RMK-wrapped PKCS#8 P-256 private key with its DER public key:

```elixir
version = "2"
aad = KMS.signing_key_aad(version)
{:ok, encrypted_private_key} = KMS.RMK.encrypt(private_key_der, aad: aad)

{:ok, key} =
  KMS.install_signing_key_once(%{
    version: version,
    public_key_der: public_key_der,
    encrypted_private_key: encrypted_private_key,
    aad: aad
  })
```

Sign and verify an already-computed SHA-256 digest without hashing it again:

```elixir
digest = :crypto.hash(:sha256, payload)
{:ok, signature_der} = KMS.sign_digest(digest, version)
:ok = KMS.verify_digest(digest, signature_der, version)
```

`KMS.validate_signing_key/1` unwraps, decodes, validates, and caches the configured key. Signing then runs directly and concurrently in caller processes. No signer process or signing HTTP route exists.

## KEK aliases and encryption

Create an alias:

```elixir
{:ok, kek} = KMS.create_alias("owner:alice")
```

Encrypt/decrypt:

```elixir
aad = "document:123/body"

{:ok, encrypted} = KMS.encrypt_alias("secret", "owner:alice", aad: aad)
{:ok, "secret"} = KMS.decrypt_alias(encrypted, "owner:alice", aad: aad)
```

Bang helpers raise on error:

```elixir
encrypted = KMS.encrypt_alias!("secret", "owner:alice", aad: aad)
"secret" = KMS.decrypt_alias!(encrypted, "owner:alice", aad: aad)
```

Tag helpers:

```elixir
{:ok, tags} = KMS.add_kek_tags("owner:alice", ["tenant:acme"])
{:ok, tags} = KMS.remove_kek_tags("owner:alice", ["tenant:acme"])
tags = KMS.list_kek_tags("owner:alice")
aliases = KMS.list_kek_aliases_by_tag("tenant:acme")
```

Tags are optional. Prefer aliases and AAD for the main encryption boundary.

## RMK PDK encryption

Use when a caller supplies a high-entropy secret:

```elixir
secret = KMS.Passphrase.generate()
{:ok, encrypted} = KMS.encrypt_rmk_pdk("document body", secret, aad: "document:123/body")
{:ok, "document body"} = KMS.decrypt_rmk_pdk(encrypted, secret, aad: "document:123/body")
```

For machine-only flows, random bytes encoded for transport also work:

```elixir
secret = Base.url_encode64(:crypto.strong_rand_bytes(32), padding: false)
```

See [Shared passphrase documents](use_cases/passphrase.md).

## Principals

Create a principal:

```elixir
{:ok, principal} =
  KMS.create_principal(%{
    kind: "users",
    ref: "alice",
    username: "alice@example.com",
    password: password,
    tags: ["tenant:acme"]
  })
```

Read/update/delete:

```elixir
principal = KMS.get_principal("users", "alice")
{:ok, principal} = KMS.fetch_principal("users", "alice")
principals = KMS.list_principals()
{:ok, principal} = KMS.update_principal("users", "alice", %{username: "new@example.com"})
{:ok, principal} = KMS.delete_principal("users", "alice")
```

`get_principal/2` and the other `get_*` helpers return `nil` only when the resource
is absent. A remote transport, HTTP, or malformed-response failure returns
`{:error, reason}`. Use `fetch_*` when a uniform `{:ok, value} | {:error, reason}`
contract is easier to handle.

## Sessions

Password session:

```elixir
{:ok, session} = KMS.create_session("users", "alice", %{password: password})
```

Pass a session to authorized operations:

```elixir
{:ok, encrypted} =
  KMS.encrypt_alias("secret", "tenant/acme/docs",
    session: session.secret,
    aad: "document:123/body"
  )
```

Advance MFA factors:

```elixir
{:ok, session} = KMS.advance_session(session.secret, %{code: "123456"})
```

Refresh/nudge/delete:

```elixir
{:ok, refreshed} = KMS.refresh_session(session)
{:ok, nudged} = KMS.nudge_session(refreshed.secret)
{:ok, deleted} = KMS.delete_session(nudged.secret)
```

See [Sessions](sessions.md).

## Factors

```elixir
{:ok, factor} =
  KMS.create_principal_factor("users", "alice", %{
    provider: "totp",
    label: "authenticator",
    position: 1,
    config: config
  })

factors = KMS.list_principal_factors("users", "alice")
{:ok, factor} = KMS.update_principal_factor(factor.id, %{enabled: false})
{:ok, factor} = KMS.delete_principal_factor(factor.id)
```

See [Authentication factors](factors.md).

## Bindings

Bindings grant principal permissions:

```elixir
{:ok, binding} =
  KMS.create_binding(principal.id, %{
    permission: "kek.decrypt",
    scope: "kek.alias:tenant/acme/docs"
  })
```

Manage bindings:

```elixir
bindings = KMS.list_bindings(principal.id)
{:ok, binding} = KMS.fetch_binding(binding.id)
{:ok, binding} = KMS.update_binding(binding.id, %{permission: "kek.encrypt"})
{:ok, binding} = KMS.delete_binding(binding.id)
```

See [Authorization and bindings](authz-bindings.md).

## Fallback recovery

Fallback functions work only through `KMS.Client.Local`. `KMS.Client.Remote` returns `{:error, :not_implemented}`, and the HTTP router contains no fallback routes.

Public key registry:

```elixir
keys = KMS.list_fallback_public_keys()
{:ok, key} = KMS.create_fallback_public_key(%{name: "ops", curve: "X25519", public_key: public_key})
{:ok, key} = KMS.fetch_fallback_public_key_by_name("ops")
```

Local trusted unwrap helpers:

```elixir
fallbacks = KMS.list_kek_fallbacks(kek.id)
{:ok, master_key} = KMS.unwrap_kek(kek.id, "ops", private_key)
```

See [Fallback recovery](fallback-recovery.md).

## WebAuthn

```elixir
{:ok, challenge} = KMS.begin_webauthn_registration("users", "alice")
{:ok, credential} = KMS.finish_webauthn_registration(challenge.id, response)

{:ok, challenge} = KMS.begin_webauthn_authentication("users", "alice")
{:ok, session} = KMS.finish_webauthn_authentication(challenge.id, response)
```

See [WebAuthn](factors/webauthn.md).

## Crypto primitives

Use `KMS.Crypto` directly for crypto-only mode:

```elixir
key = KMS.Crypto.generate_key(32)
encrypted = KMS.Crypto.encrypt("secret", key, "aad")
"secret" = KMS.Crypto.decrypt(encrypted, key, "aad")
```

See [Crypto-only library mode](use_cases/crypto_only.md).
