Skip to content

Commit a25cae2

Browse files
leanthebeanclaude
andauthored
feat: emit msg.sender in cert revocation events (I-02) (#47)
Address BLOCKSEC-5249 finding I-02. Generated with Claude Code Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1955adc commit a25cae2

4 files changed

Lines changed: 81 additions & 26 deletions

File tree

src/CertManager.sol

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ contract CertManager is ICertManager {
2121
error InvalidBasicConstraints();
2222
error InvalidSubjectPublicKey();
2323
error UnsupportedCriticalExtension();
24+
error NotOwner();
25+
error NotRevoker();
26+
error IncompleteCertChain();
27+
error DeprecatedEntrypoint();
28+
error InvalidOwner();
29+
error InvalidRevoker();
2430

2531
event CertVerified(bytes32 indexed certHash);
26-
event CertRevoked(bytes32 indexed certHash);
27-
event CertUnrevoked(bytes32 indexed certHash);
32+
event CertRevoked(bytes32 indexed certHash, address indexed account);
33+
event CertUnrevoked(bytes32 indexed certHash, address indexed account);
2834
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
2935
event RevokerUpdated(address indexed previousRevoker, address indexed newRevoker);
3036

@@ -83,11 +89,11 @@ contract CertManager is ICertManager {
8389
}
8490

8591
function _onlyOwner() internal view {
86-
require(msg.sender == owner, "not owner");
92+
if (msg.sender != owner) revert NotOwner();
8793
}
8894

8995
function _onlyRevoker() internal view {
90-
require(msg.sender == revoker, "not revoker");
96+
if (msg.sender != revoker) revert NotRevoker();
9197
}
9298

9399
constructor(IP384Verifier p384Verifier_) {
@@ -112,12 +118,12 @@ contract CertManager is ICertManager {
112118
/// @notice DEPRECATED — always reverts. The fully on-chain (non-hinted) path is too expensive
113119
/// post-Fusaka and has been removed. Use {verifyCACertWithHints}.
114120
function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) {
115-
revert("use hinted cert verification");
121+
revert DeprecatedEntrypoint();
116122
}
117123

118124
/// @notice DEPRECATED — always reverts. Use {verifyClientCertWithHints}.
119125
function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) {
120-
revert("use hinted cert verification");
126+
revert DeprecatedEntrypoint();
121127
}
122128

123129
/// @notice Verify a CA certificate against its (already-cached) parent and cache the result.
@@ -173,13 +179,13 @@ contract CertManager is ICertManager {
173179
}
174180

175181
function transferOwnership(address newOwner) external onlyOwner {
176-
require(newOwner != address(0), "invalid owner");
182+
if (newOwner == address(0)) revert InvalidOwner();
177183
emit OwnershipTransferred(owner, newOwner);
178184
owner = newOwner;
179185
}
180186

181187
function setRevoker(address newRevoker) external onlyOwner {
182-
require(newRevoker != address(0), "invalid revoker");
188+
if (newRevoker == address(0)) revert InvalidRevoker();
183189
emit RevokerUpdated(revoker, newRevoker);
184190
revoker = newRevoker;
185191
}
@@ -200,12 +206,12 @@ contract CertManager is ICertManager {
200206

201207
function unrevokeCert(bytes32 certId) external onlyOwner {
202208
revoked[certId] = false;
203-
emit CertUnrevoked(certId);
209+
emit CertUnrevoked(certId, msg.sender);
204210
}
205211

206212
function _revokeCert(bytes32 certId) internal {
207213
revoked[certId] = true;
208-
emit CertRevoked(certId);
214+
emit CertRevoked(certId, msg.sender);
209215
}
210216

211217
function _requireCanRevoke(bytes32 certId) internal view {
@@ -240,7 +246,7 @@ contract CertManager is ICertManager {
240246
// Fail closed: a chain that terminates at bytes32(0) without reaching the pinned root is
241247
// broken and must not be treated as a verified, non-revoked chain. Reverting here instead
242248
// of returning silently means revocation safety never depends on upstream guards.
243-
revert("incomplete cert chain");
249+
revert IncompleteCertChain();
244250
}
245251

246252
function _verifyCert(

src/ICertManager.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ interface ICertManager {
4545

4646
// --- DEPRECATED: these always revert; use the *WithHints variants above. ---
4747

48-
/// @dev DEPRECATED — always reverts ("use hinted cert verification").
48+
/// @dev DEPRECATED — always reverts.
4949
function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32);
5050

51-
/// @dev DEPRECATED — always reverts ("use hinted cert verification").
51+
/// @dev DEPRECATED — always reverts.
5252
function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory);
5353
}

test/CertManager.t.sol

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,12 +500,61 @@ contract RequireCachedChainNotRevokedTest is Test {
500500
// verifiedParent[PARENT] is unset (bytes32(0)), so the chain is broken: it can never
501501
// reach ROOT_CA_CERT_HASH. The fixed function must fail closed instead of returning.
502502
cm.setParent(CHILD, PARENT);
503-
vm.expectRevert("incomplete cert chain");
503+
vm.expectRevert(CertManager.IncompleteCertChain.selector);
504504
cm.requireCachedChainNotRevoked(CHILD);
505505
}
506506

507507
function test_RevertsOnZeroCertHash() public {
508-
vm.expectRevert("incomplete cert chain");
508+
vm.expectRevert(CertManager.IncompleteCertChain.selector);
509509
cm.requireCachedChainNotRevoked(bytes32(0));
510510
}
511511
}
512+
513+
/// @dev Regression coverage for BLOCKSEC-5249 finding I-02: `CertRevoked` / `CertUnrevoked` now
514+
/// include the acting `msg.sender` as an indexed topic so revocation activity is monitorable.
515+
contract CertRevocationEventTest is Test {
516+
CertManager internal cm;
517+
518+
event CertRevoked(bytes32 indexed certHash, address indexed account);
519+
event CertUnrevoked(bytes32 indexed certHash, address indexed account);
520+
521+
bytes32 internal constant CERT_ID = bytes32(uint256(0xabc));
522+
523+
function setUp() public {
524+
// Deployer is both owner and revoker, so this contract can revoke/unrevoke directly.
525+
cm = new CertManager(new P384Verifier());
526+
}
527+
528+
function test_RevokeCertEmitsSender() public {
529+
vm.expectEmit(true, true, false, true, address(cm));
530+
emit CertRevoked(CERT_ID, address(this));
531+
cm.revokeCert(CERT_ID);
532+
}
533+
534+
function test_RevokeCertsEmitsSender() public {
535+
bytes32[] memory ids = new bytes32[](1);
536+
ids[0] = CERT_ID;
537+
vm.expectEmit(true, true, false, true, address(cm));
538+
emit CertRevoked(CERT_ID, address(this));
539+
cm.revokeCerts(ids);
540+
}
541+
542+
function test_UnrevokeCertEmitsSender() public {
543+
cm.revokeCert(CERT_ID);
544+
vm.expectEmit(true, true, false, true, address(cm));
545+
emit CertUnrevoked(CERT_ID, address(this));
546+
cm.unrevokeCert(CERT_ID);
547+
}
548+
549+
/// @dev The recorded account is the actual caller, not the contract: a delegated revoker
550+
/// address shows up in the event topic.
551+
function test_RevokeCertRecordsActualCaller() public {
552+
address delegatedRevoker = address(0xBEEF);
553+
cm.setRevoker(delegatedRevoker);
554+
555+
vm.expectEmit(true, true, false, true, address(cm));
556+
emit CertRevoked(CERT_ID, delegatedRevoker);
557+
vm.prank(delegatedRevoker);
558+
cm.revokeCert(CERT_ID);
559+
}
560+
}

test/hinted/HintedNitroAttestation.t.sol

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -390,43 +390,43 @@ contract HintedNitroAttestationTest is Test {
390390

391391
address newRevoker = address(0xBEEF);
392392
vm.prank(address(0xCAFE));
393-
vm.expectRevert("not owner");
393+
vm.expectRevert(CertManager.NotOwner.selector);
394394
certManager.setRevoker(newRevoker);
395395

396-
vm.expectRevert("invalid revoker");
396+
vm.expectRevert(CertManager.InvalidRevoker.selector);
397397
certManager.setRevoker(address(0));
398398

399399
certManager.setRevoker(newRevoker);
400400
assertEq(certManager.revoker(), newRevoker);
401401

402402
bytes32 otherCertHash = keccak256("other cert");
403403
vm.prank(address(0xCAFE));
404-
vm.expectRevert("not revoker");
404+
vm.expectRevert(CertManager.NotRevoker.selector);
405405
certManager.revokeCert(otherCertHash);
406406

407407
vm.prank(newRevoker);
408408
certManager.revokeCert(otherCertHash);
409409
assertTrue(certManager.revoked(otherCertHash));
410410

411411
vm.prank(newRevoker);
412-
vm.expectRevert("not owner");
412+
vm.expectRevert(CertManager.NotOwner.selector);
413413
certManager.unrevokeCert(otherCertHash);
414414

415415
certManager.unrevokeCert(otherCertHash);
416416
assertFalse(certManager.revoked(otherCertHash));
417417

418-
vm.expectRevert("invalid owner");
418+
vm.expectRevert(CertManager.InvalidOwner.selector);
419419
certManager.transferOwnership(address(0));
420420

421421
address newOwner = address(0xA11CE);
422422
vm.prank(address(0xCAFE));
423-
vm.expectRevert("not owner");
423+
vm.expectRevert(CertManager.NotOwner.selector);
424424
certManager.transferOwnership(newOwner);
425425

426426
certManager.transferOwnership(newOwner);
427427
assertEq(certManager.owner(), newOwner);
428428

429-
vm.expectRevert("not owner");
429+
vm.expectRevert(CertManager.NotOwner.selector);
430430
certManager.setRevoker(address(0x1234));
431431

432432
vm.prank(newOwner);
@@ -455,7 +455,7 @@ contract HintedNitroAttestationTest is Test {
455455
bytes32 rootHash = certManager.ROOT_CA_CERT_HASH();
456456

457457
vm.prank(newRevoker);
458-
vm.expectRevert("not owner");
458+
vm.expectRevert(CertManager.NotOwner.selector);
459459
certManager.revokeCert(rootHash);
460460
assertFalse(certManager.revoked(rootHash));
461461

@@ -464,7 +464,7 @@ contract HintedNitroAttestationTest is Test {
464464
certHashes[1] = rootHash;
465465

466466
vm.prank(newRevoker);
467-
vm.expectRevert("not owner");
467+
vm.expectRevert(CertManager.NotOwner.selector);
468468
certManager.revokeCerts(certHashes);
469469
assertFalse(certManager.revoked(certHashes[0]));
470470
assertFalse(certManager.revoked(rootHash));
@@ -738,10 +738,10 @@ contract HintedNitroAttestationTest is Test {
738738
}
739739

740740
function test_DeployableCertManagerDisablesUnhintedEntrypoints() public {
741-
vm.expectRevert("use hinted cert verification");
741+
vm.expectRevert(CertManager.DeprecatedEntrypoint.selector);
742742
certManager.verifyCACert("", bytes32(0));
743743

744-
vm.expectRevert("use hinted cert verification");
744+
vm.expectRevert(CertManager.DeprecatedEntrypoint.selector);
745745
certManager.verifyClientCert("", bytes32(0));
746746

747747
vm.expectRevert("use hinted attestation verification");

0 commit comments

Comments
 (0)