Skip to content

Producer signing gate skips revocation/rotation for raw-seed (Direct) and hardware/Secure-Enclave keys #355

Description

@bordumb

Summary

The producer-side signing path has a gate, enforce_signer_authority(), that refuses to sign with a device
key that has been revoked (auths device remove) or rotated out of the device's current KEL keys. The gate
is only invoked for software keys resolved by alias. Raw-seed (SigningKeyMaterial::Direct) keys and
hardware / Secure-Enclave-backed keys are not checked at all
, because both resolve with identity_did: None, and the gate is guarded on identity_did being Some. As a result, a removed or rotated-out device
whose key is a raw seed or hardware-backed can still produce valid signatures from the producer side;
revocation then relies entirely on the verifier honoring the KEL state. Most surprisingly, the
hardest-to-steal keys (Secure Enclave) are the ones the producer gate does not cover.

Where (code) — crates/auths-sdk/src/domains/signing/service.rs

The gate is only called when the resolved key carries an identity DID (line 626):

if let Some(ref device_did) = device_resolved.identity_did {
    enforce_signer_authority(
        ctx,
        &managed.controller_did,
        device_did,
        &device_resolved.public_key_bytes,
    )?;
}

enforce_signer_authority (defined at line 494) reads the device KEL state via the registry and refuses to
sign if the device is revoked or if the signing public key is not in state.current_keys.

Key resolution sets identity_did: None for the two paths that skip the gate:

  • Hardware / Secure-Enclave alias keys (line ~411):
return Ok(Some(ResolvedKey { /* ... */ is_hardware: true, identity_did: None }));
  • Raw-seed Direct keys (line ~449):
Some(SigningKeyMaterial::Direct(seed)) => {
    /* ... */
    Ok(Some(ResolvedKey { /* ... */ is_hardware: false, identity_did: None }))
}

Only the software-alias path sets identity_did: Some(device_did) (line ~436), so only it is gated.

remove_device (crates/auths-sdk/src/domains/device/delegation.rs:103) writes a revocation marker to the
KEL but does not delete the local key material — so the key physically persists and can be re-supplied as
a raw seed (or used via its hardware handle), bypassing the gate.

Threat / scenario

  1. A device is removed (or its key rotated out). The producer gate is meant to stop it from signing.
  2. The key still exists locally (remove only writes a KEL marker). If it is used as a raw seed
    (SigningKeyMaterial::Direct) or is hardware-backed, enforce_signer_authority is skipped and a valid
    signature is produced.
  3. The only remaining defense is the verifier checking revocation/rotation. Any consumer that does not — or
    any context where verify-side revocation resolution is unavailable — accepts the signature.

Already in place (do not regress)

  • Software-alias signing is correctly gated. Tests rotated_out_key_cannot_sign and
    revoked_device_cannot_produce_accepted_signature (under crates/auths-sdk/tests/cases/) pass.
  • The gate reads ctx.registry.get_key_state() current keys plus the root KEL revocation markers.

Proposed remediation (pick / combine)

  1. Extend the gate to hardware-backed keys. A hardware alias key has an associated device AID available
    at resolution time (the keychain maps alias → DID — the software path does exactly this via
    keychain.load_key(alias)). Carry that DID through ResolvedKey.identity_did for the hardware path so
    enforce_signer_authority runs for it. There is no good reason a Secure-Enclave key should skip
    revocation/rotation enforcement.
  2. Decide explicitly about raw seeds. A Direct seed carries no AID, so the gate cannot consult a KEL.
    Either (a) require an accompanying identity/AID for Direct signing so it can be checked, or (b) make
    Direct an explicit, acknowledged escape hatch — unreachable from the normal auths sign path without an
    opt-in (e.g. an --allow-unverified-seed-style flag). Today the bypass is silent.
  3. Delete (or locally disable) key material on device remove so a removed device's key cannot be
    re-fed as a raw seed; at minimum, surface that the local key persists.

Acceptance criteria

  • A hardware / Secure-Enclave device key that has been rotated out or whose device was removed is refused
    by the producer (KeyRotatedOut / DeviceRevoked), with a test mirroring rotated_out_key_cannot_sign
    but using a hardware-backed resolution.
  • Raw-seed Direct signing either runs the same revocation/rotation check (when an AID is supplied) or is
    only reachable behind an explicit opt-in; a test asserts a removed/rotated key cannot silently sign via
    Direct.
  • Existing software-alias gating tests still pass.

Severity

High. "Revocation stops the producer from signing" is the intended guarantee; it currently has a silent
exception covering both the raw-seed and the hardware/Secure-Enclave paths.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions