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
- A device is removed (or its key rotated out). The producer gate is meant to stop it from signing.
- 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.
- 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)
- 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.
- 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.
- 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.
Summary
The producer-side signing path has a gate,
enforce_signer_authority(), that refuses to sign with a devicekey that has been revoked (
auths device remove) or rotated out of the device's current KEL keys. The gateis only invoked for software keys resolved by alias. Raw-seed (
SigningKeyMaterial::Direct) keys andhardware / Secure-Enclave-backed keys are not checked at all, because both resolve with
identity_did: None, and the gate is guarded onidentity_didbeingSome. As a result, a removed or rotated-out devicewhose 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.rsThe gate is only called when the resolved key carries an identity DID (line 626):
enforce_signer_authority(defined at line 494) reads the device KEL state via the registry and refuses tosign if the device is revoked or if the signing public key is not in
state.current_keys.Key resolution sets
identity_did: Nonefor the two paths that skip the gate:Directkeys (line ~449):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 theKEL 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
(
SigningKeyMaterial::Direct) or is hardware-backed,enforce_signer_authorityis skipped and a validsignature is produced.
any context where verify-side revocation resolution is unavailable — accepts the signature.
Already in place (do not regress)
rotated_out_key_cannot_signandrevoked_device_cannot_produce_accepted_signature(undercrates/auths-sdk/tests/cases/) pass.ctx.registry.get_key_state()current keys plus the root KEL revocation markers.Proposed remediation (pick / combine)
at resolution time (the keychain maps alias → DID — the software path does exactly this via
keychain.load_key(alias)). Carry that DID throughResolvedKey.identity_didfor the hardware path soenforce_signer_authorityruns for it. There is no good reason a Secure-Enclave key should skiprevocation/rotation enforcement.
Directseed carries no AID, so the gate cannot consult a KEL.Either (a) require an accompanying identity/AID for
Directsigning so it can be checked, or (b) makeDirectan explicit, acknowledged escape hatch — unreachable from the normalauths signpath without anopt-in (e.g. an
--allow-unverified-seed-style flag). Today the bypass is silent.device removeso a removed device's key cannot bere-fed as a raw seed; at minimum, surface that the local key persists.
Acceptance criteria
by the producer (
KeyRotatedOut/DeviceRevoked), with a test mirroringrotated_out_key_cannot_signbut using a hardware-backed resolution.
Directsigning either runs the same revocation/rotation check (when an AID is supplied) or isonly reachable behind an explicit opt-in; a test asserts a removed/rotated key cannot silently sign via
Direct.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.