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 all 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
162 changes: 142 additions & 20 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,12 +452,11 @@ describe("verify", () => {
}
`);
});

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

const handlers = [
rest.get("https://www.ica.gov.sg/ocsp/SGCNM21566327", (_, res, ctx) => {
rest.get("https://ocsp.example.com/SGCNM21566327", (_, res, ctx) => {
return res(
ctx.json({
certificateId: "SGCNM21566327",
Expand All @@ -476,13 +476,13 @@ describe("verify", () => {
"details": Object {
"issuance": Array [
Object {
"did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89",
"did": "did:ethr:0xB26B4941941C51a4885E5B7D3A1B861E54405f90",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://www.ica.gov.sg/ocsp",
"address": "https://ocsp.example.com",
"revoked": false,
},
],
Expand All @@ -498,12 +498,11 @@ describe("verify", () => {

server.close();
});

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

const handlers = [
rest.get("https://www.ica.gov.sg/ocsp/SGCNM21566327", (_, res, ctx) => {
rest.get("https://ocsp.example.com/SGCNM21566327", (_, res, ctx) => {
return res(
ctx.json({
certificateId: "SGCNM21566327",
Expand All @@ -526,13 +525,13 @@ describe("verify", () => {
"details": Object {
"issuance": Array [
Object {
"did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89",
"did": "did:ethr:0xB26B4941941C51a4885E5B7D3A1B861E54405f90",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://www.ica.gov.sg/ocsp",
"address": "https://ocsp.example.com",
"reason": Object {
"code": 4,
"codeString": "SUPERSEDED",
Expand All @@ -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();

const handlers = [
rest.get(
"https://ocsp.example.com/0x28b221f6287d8e4f8da09a835bcb750537cc8385e2535ff63591fdf0162be824",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x28b221f6287d8e4f8da09a835bcb750537cc8385e2535ff63591fdf0162be824",
})
);
}
),
rest.get(
"https://ocsp.example.com/0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c",
})
);
}
),
];

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:0xB26B4941941C51a4885E5B7D3A1B861E54405f90",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://ocsp.example.com",
"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();

const handlers = [
rest.get(
"https://ocsp.example.com/0x28b221f6287d8e4f8da09a835bcb750537cc8385e2535ff63591fdf0162be824",
(_, res, ctx) => {
return res(
ctx.json({
revoked: true,
documentHash: "0x28b221f6287d8e4f8da09a835bcb750537cc8385e2535ff63591fdf0162be824",
reasonCode: 4,
})
);
}
),
rest.get(
"https://ocsp.example.com/0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c",
(_, res, ctx) => {
return res(
ctx.json({
revoked: false,
documentHash: "0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c",
})
);
}
),
];

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:0xB26B4941941C51a4885E5B7D3A1B861E54405f90",
"issued": true,
},
],
"revocation": Array [
Object {
"address": "https://ocsp.example.com",
"reason": Object {
"code": 4,
"codeString": "SUPERSEDED",
"message": "Document 0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c has been revoked under OCSP Responder: https://ocsp.example.com",
},
"revoked": true,
},
],
},
"issuedOnAll": true,
"revokedOnAny": true,
},
"name": "OpenAttestationDidSignedDocumentStatus",
"reason": Object {
"code": 4,
"codeString": "SUPERSEDED",
"message": "Document 0x56961854a82feafe9a56eb57acfe3b97f17eda5d497b622c9acc9f03c412618c has been revoked under OCSP Responder: https://ocsp.example.com",
},
"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
18 changes: 12 additions & 6 deletions src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.ts
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 { isRevokedByOcspResponder, isRevokedByOcspResponder2, isRevokedOnDocumentStore } from "../utils";
import { InvalidRevocationStatus, RevocationStatus, ValidRevocationStatus } from "../revocation.types";
import {
DidSignedIssuanceStatus,
Expand Down Expand Up @@ -98,11 +98,17 @@ const verifyV2 = async (
"REVOCATION_LOCATION_MISSING"
);
case v2.RevocationType.OcspResponder:
if (typeof revocationItem.location === "string") {
return isRevokedByOcspResponder({
certificateId: documentData.id as string,
location: revocationItem.location,
});
const { location } = revocationItem;
if (typeof location === "string") {
return isRevokedByOcspResponder2({
merkleRoot,
targetHash,
proofs,
location,
}).catch(() =>
// FIXME: Omit this catch fallback after removing support for old OCSP responders
isRevokedByOcspResponder({ certificateId: documentData.id as string, location })
);
}
throw new CodedError(
"missing revocation location for an issuer",
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