Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ocsp revocation against merkle root & intermediate hashes #228

Merged
merged 6 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 134 additions & 12 deletions src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ describe("verify", () => {
beforeEach(() => {
mockGetPublicKey.mockReset();
});

describe("v2", () => {
it("should pass for documents using `DID` and is correctly signed", async () => {
whenPublicKeyResolvesSuccessfully("0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89");
Expand Down Expand Up @@ -451,8 +452,7 @@ describe("verify", () => {
}
`);
});

it("should pass when DID document is signed and is not revoked by an OCSP", async () => {
it("should pass when DID document is signed and is not revoked by an OCSP v1", async () => {
whenPublicKeyResolvesSuccessfully("0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89");

const handlers = [
Expand Down Expand Up @@ -498,8 +498,7 @@ describe("verify", () => {

server.close();
});

it("should fail when DID document is signed but is found by an OCSP", async () => {
it("should fail when DID document is signed but is found by an OCSP v1", async () => {
whenPublicKeyResolvesSuccessfully("0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89");

const handlers = [
Expand Down Expand Up @@ -556,6 +555,137 @@ describe("verify", () => {
}
`);

server.close();
});
it("should pass when DID document is signed and is not revoked by an OCSP v2", async () => {
whenPublicKeyResolvesSuccessfully("0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89");

const handlers = [
rest.get(
"https://www.ica.gov.sg/ocsp/0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c",
})
);
}
),
rest.get(
"https://www.ica.gov.sg/ocsp/0x4d26a49266ba73f57276b0865d995c4c6ae8be52fe54988e85b4cbf222f49e74",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x4d26a49266ba73f57276b0865d995c4c6ae8be52fe54988e85b4cbf222f49e74",
})
);
}
),
];

const server: SetupServerApi = setupServer(...handlers);
server.listen();

const res = await openAttestationDidSignedDocumentStatus.verify(didSignedOcsp, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": Object {
"details": Object {
"issuance": Array [
Object {
"did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://www.ica.gov.sg/ocsp",
"revoked": false,
},
],
},
"issuedOnAll": true,
"revokedOnAny": false,
},
"name": "OpenAttestationDidSignedDocumentStatus",
"status": "VALID",
"type": "DOCUMENT_STATUS",
}
`);

server.close();
});
it("should fail when DID document is signed but is found by an OCSP v2", async () => {
whenPublicKeyResolvesSuccessfully("0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89");

const handlers = [
rest.get(
"https://www.ica.gov.sg/ocsp/0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c",
})
);
}
),
rest.get(
"https://www.ica.gov.sg/ocsp/0x4d26a49266ba73f57276b0865d995c4c6ae8be52fe54988e85b4cbf222f49e74",
(_, res, ctx) => {
return res(
ctx.json({
revoked: true,
documentHash: "0x4d26a49266ba73f57276b0865d995c4c6ae8be52fe54988e85b4cbf222f49e74",
reasonCode: 4,
})
);
}
),
];

const server: SetupServerApi = setupServer(...handlers);
server.listen();

const res = await openAttestationDidSignedDocumentStatus.verify(didSignedOcsp, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": Object {
"details": Object {
"issuance": Array [
Object {
"did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://www.ica.gov.sg/ocsp",
"reason": Object {
"code": 4,
"codeString": "SUPERSEDED",
"message": "Document 0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c has been revoked under OCSP Responder: https://www.ica.gov.sg/ocsp",
},
"revoked": true,
},
],
},
"issuedOnAll": true,
"revokedOnAny": true,
},
"name": "OpenAttestationDidSignedDocumentStatus",
"reason": Object {
"code": 4,
"codeString": "SUPERSEDED",
"message": "Document 0x53b4a76854688ee7857442d01f33d1805e3a237377fd0e5d53a43cda30dd742c has been revoked under OCSP Responder: https://www.ica.gov.sg/ocsp",
},
"status": "INVALID",
"type": "DOCUMENT_STATUS",
}
`);

server.close();
});
});
Expand Down Expand Up @@ -602,7 +732,6 @@ describe("verify", () => {
}
`);
});

it("should pass for documents using `DID` and is correctly signed, and is not revoked on a document store (if specified)", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await openAttestationDidSignedDocumentStatus.verify(didSignedRevocationStoreNotRevokedV3, options);
Expand All @@ -628,7 +757,6 @@ describe("verify", () => {
}
`);
});

it("should pass for documents using `DID-DNS` and is correctly signed", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await openAttestationDidSignedDocumentStatus.verify(dnsDidSignedV3, options);
Expand All @@ -653,7 +781,6 @@ describe("verify", () => {
}
`);
});

it("should pass for documents using `DID-DNS` and is correctly signed, and is not revoked on a document store (if specified)", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await openAttestationDidSignedDocumentStatus.verify(dnsDidSignedRevocationStoreNotRevokedV3, options);
Expand All @@ -679,7 +806,6 @@ describe("verify", () => {
}
`);
});

it("should fail when revocation block is missing", async () => {
whenPublicKeyResolvesSuccessfully();
const docWithoutRevocationBlock = {
Expand Down Expand Up @@ -716,7 +842,6 @@ describe("verify", () => {
}
`);
});

it("should throw an unrecognized revocation type error when revocation is not set to NONE or REVOCATION_STORE", async () => {
whenPublicKeyResolvesSuccessfully();
const docWithIncorrectRevocation = {
Expand Down Expand Up @@ -748,7 +873,6 @@ describe("verify", () => {
}
`);
});

it("should fail for documents using `DID` and is correctly signed, and is revoked on a document store (if specified)", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await openAttestationDidSignedDocumentStatus.verify(didSignedRevocationStoreButRevokedV3, options);
Expand Down Expand Up @@ -784,7 +908,6 @@ describe("verify", () => {
}
`);
});

it("should fail for documents using `DID-DNS` and is correctly signed, and is revoked on a document store (if specified)", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await openAttestationDidSignedDocumentStatus.verify(dnsDidSignedRevocationStoreButRevokedV3, options);
Expand Down Expand Up @@ -820,7 +943,6 @@ describe("verify", () => {
}
`);
});

it("should fail when signature is wrong", async () => {
whenPublicKeyResolvesSuccessfully();
const documentWithWrongSig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OpenAttestationDidSignedDocumentStatusCode, Reason } from "../../../typ
import { DidVerificationStatus, ValidDidVerificationStatus, verifySignature } from "../../../did/verifier";
import { CodedError } from "../../../common/error";
import { withCodedErrorHandler } from "../../../common/errorHandler";
import { isRevokedByOcspResponder, isRevokedOnDocumentStore } from "../utils";
import { isRevokedByOcspResponder2, isRevokedOnDocumentStore } from "../utils";
import { InvalidRevocationStatus, RevocationStatus, ValidRevocationStatus } from "../revocation.types";
import {
DidSignedIssuanceStatus,
Expand Down Expand Up @@ -99,8 +99,11 @@ const verifyV2 = async (
);
case v2.RevocationType.OcspResponder:
if (typeof revocationItem.location === "string") {
return isRevokedByOcspResponder({
return isRevokedByOcspResponder2({
certificateId: documentData.id as string,
merkleRoot,
targetHash,
proofs,
location: revocationItem.location,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,41 @@ export type DidSignedIssuanceStatusArray = Static<typeof DidSignedIssuanceStatus
*/

export const ValidOcspReasonCode = Number.withConstraint((n) => n >= 0 && n <= 10 && n != 7);
/**
* @deprecated Replaced by `ValidOcspResponse2`
*
* This type guard is retained for backwards compatibility with older OCSP responders
* that are already in the wild. Do consider removing support for the old OCSP responders
* in the next major version of oa-verify.
*
* OCSP Responder (new response format): https://github.com/Open-Attestation/ocsp-responder#checking-document-status
*/
export const ValidOcspResponse = Record({
certificateStatus: OcspResponderRevocationStatus,
});
export const ValidOcspResponse2 = Record({
revoked: Literal(false),
documentHash: String,
});

/**
* @deprecated Replaced by `ValidOcspResponseRevoked2`
*
* This type guard is retained for backwards compatibility with older OCSP responders
* that are already in the wild. Do consider removing support for the old OCSP responders
* in the next major version of oa-verify.
*
* OCSP Responder (new response format): https://github.com/Open-Attestation/ocsp-responder#checking-document-status
*/
export const ValidOcspResponseRevoked = Record({
reasonCode: ValidOcspReasonCode,
certificateStatus: OcspResponderRevocationStatus,
});
export const ValidOcspResponseRevoked2 = Record({
revoked: Literal(true),
documentHash: String,
reasonCode: ValidOcspReasonCode,
});

/**
* Data for v2 Fragments
Expand Down
Loading