Summary
The pp-cli reference verifier accepts signatures made with non-Ed25519 keys while reporting signatureAlg: "ed25519" as verified.
verifyReceipt() checks the receipt's signed algorithm label, but the key resolver accepts any SPKI public key and passes it to Node's verify(null, ...). Node verifies RSA, ECDSA, and Ed448 signatures through that same call. A receipt signed with any of those key types can therefore pass the local verifier as an Ed25519 receipt if the resolved key record matches signatureKeyId.
This is distinct from #49, #50, and #51: the issue is algorithm/key-type confusion, not receipt lifecycle state, key lifecycle state, or canonicalization.
Affected code
Repository: permission-protocol/pp-cli
Current main: 0d282b947a4a3d8080182ae954b3c7d8c84059bd
src/verify.ts enforces only the label:
if (receipt.signatureAlg !== 'ed25519') {
return { verified: false, ... };
}
const ok = verifySignature(null, payloadBytes, keyResult.key, signatureBytes);
src/keyResolver.ts parses PEM or DER public keys without checking KeyObject.asymmetricKeyType.
Reproduction
I opened a focused patch with a regression test:
permission-protocol/pp-cli#5
The test:
- Loads a known-valid receipt fixture.
- Generates an RSA key pair.
- Keeps the signed receipt field
signatureAlg: "ed25519" unchanged.
- Changes
signatureKeyId to a matching RSA key record and signs the canonical bytes with the RSA private key.
- Resolves the RSA public key from a local
data: key URL and calls verifyReceipt().
Observed before the fix:
expected true to be false
Received: true
I also reproduced successful verification with generated ECDSA and Ed448 key pairs. The patch fails closed for every parsed public key whose asymmetricKeyType is not ed25519.
Scope
This demonstrates a flaw in the local Ed25519 signing/verification flow. I have not demonstrated a forged receipt against the hosted /api/v1/receipts/verify endpoint, which requires a tenant API key and has a separate implementation.
A malicious or misconfigured remote key-discovery response, or an offline verifier given a non-Ed25519 key file, can cause the local reference verifier to accept and report a receipt as Ed25519-verified when it was signed with another algorithm.
Suggested fix
After parsing the public key, require:
key.asymmetricKeyType === 'ed25519'
The linked PR applies this check in the shared key-loading path so both remote discovery and offline key files fail closed.
Validation
npm test -- --run
npm run build
git diff --check
Bounty note
Submitted for assessment under #36 as a distinct Ed25519 verification-flow flaw. Payout details can be provided privately after validation.
Summary
The
pp-clireference verifier accepts signatures made with non-Ed25519 keys while reportingsignatureAlg: "ed25519"as verified.verifyReceipt()checks the receipt's signed algorithm label, but the key resolver accepts any SPKI public key and passes it to Node'sverify(null, ...). Node verifies RSA, ECDSA, and Ed448 signatures through that same call. A receipt signed with any of those key types can therefore pass the local verifier as an Ed25519 receipt if the resolved key record matchessignatureKeyId.This is distinct from #49, #50, and #51: the issue is algorithm/key-type confusion, not receipt lifecycle state, key lifecycle state, or canonicalization.
Affected code
Repository:
permission-protocol/pp-cliCurrent
main:0d282b947a4a3d8080182ae954b3c7d8c84059bdsrc/verify.tsenforces only the label:src/keyResolver.tsparses PEM or DER public keys without checkingKeyObject.asymmetricKeyType.Reproduction
I opened a focused patch with a regression test:
permission-protocol/pp-cli#5
The test:
signatureAlg: "ed25519"unchanged.signatureKeyIdto a matching RSA key record and signs the canonical bytes with the RSA private key.data:key URL and callsverifyReceipt().Observed before the fix:
I also reproduced successful verification with generated ECDSA and Ed448 key pairs. The patch fails closed for every parsed public key whose
asymmetricKeyTypeis noted25519.Scope
This demonstrates a flaw in the local Ed25519 signing/verification flow. I have not demonstrated a forged receipt against the hosted
/api/v1/receipts/verifyendpoint, which requires a tenant API key and has a separate implementation.A malicious or misconfigured remote key-discovery response, or an offline verifier given a non-Ed25519 key file, can cause the local reference verifier to accept and report a receipt as Ed25519-verified when it was signed with another algorithm.
Suggested fix
After parsing the public key, require:
The linked PR applies this check in the shared key-loading path so both remote discovery and offline key files fail closed.
Validation
npm test -- --runnpm run buildgit diff --checkBounty note
Submitted for assessment under #36 as a distinct Ed25519 verification-flow flaw. Payout details can be provided privately after validation.