Skip to content

Commit

Permalink
feat: stricter verification (#132)
Browse files Browse the repository at this point in the history
* feat: restrict token registry to fail on multiple issuers (#129)

* feat: restrict token registry to fail on multiple issuers

* feat: added reasons back

* feat: added server error

* feat: stricter document store verifier (#133)

* feat: stricter did signed document verifier (#134)

* feat: stricter did identity (#135)

* chore: refactored error handling

* feat: restricted didIdentityProof

* feat: stricter dns did identity (#136)

* feat: restricted dnsDid

* feat: stricter dnsTxt (#137)

* feat: aligning data.reason in individual fragments (#138)

* feat: aligning naming convention with files and modules (#139)

BREAKING CHANGE: Modules name changes and stricter verification rules
  • Loading branch information
yehjxraymond authored Nov 6, 2020
1 parent 5ba688d commit fcbe8f9
Show file tree
Hide file tree
Showing 32 changed files with 2,333 additions and 2,002 deletions.
46 changes: 46 additions & 0 deletions src/common/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Verifier, VerificationFragmentStatus, VerificationFragmentType } from "../types/core";

export interface ErrorOptions {
name: string;
type: VerificationFragmentType;
unexpectedErrorCode: number;
unexpectedErrorString: string;
}

export const withCodedErrorHandler = (verify: Verifier["verify"], errorOptions: ErrorOptions) => async (
document: Parameters<Verifier["verify"]>[0],
options: Parameters<Verifier["verify"]>[1]
) => {
try {
// Using return await to ensure async function execute in try block
return await verify(document, options);
} catch (e) {
const { message, code, codeString } = e;
const { name, type, unexpectedErrorCode, unexpectedErrorString } = errorOptions;
if (message && code && codeString) {
return {
name,
type: type as any,
data: e,
reason: {
message,
code,
codeString,
},
status: "ERROR" as VerificationFragmentStatus,
};
} else {
return {
name,
type: type as any,
data: e,
reason: {
message: e.message,
code: unexpectedErrorCode,
codeString: unexpectedErrorString,
},
status: "ERROR" as VerificationFragmentStatus,
};
}
}
};
21 changes: 0 additions & 21 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
import * as ethers from "ethers";
import { getData, utils, v2, v3, WrappedDocument } from "@govtechsg/open-attestation";
import { INFURA_API_KEY } from "../config";

export const getProvider = (options: { network: string }): ethers.providers.Provider =>
process.env.ETHEREUM_PROVIDER === "cloudflare"
? new ethers.providers.CloudflareProvider()
: new ethers.providers.InfuraProvider(options.network, INFURA_API_KEY);

export const getIssuersDocumentStore = (
document: WrappedDocument<v2.OpenAttestationDocument> | WrappedDocument<v3.OpenAttestationDocument>
): string[] => {
if (utils.isWrappedV2Document(document)) {
const data = getData(document);
return data.issuers.map((issuer) => issuer.documentStore || issuer.certificateStore || "");
}
return [getData(document).proof.value];
};

export const getIssuersTokenRegistry = (
document: WrappedDocument<v2.OpenAttestationDocument> | WrappedDocument<v3.OpenAttestationDocument>
): string[] => {
if (utils.isWrappedV2Document(document)) {
const data = getData(document);
return data.issuers.map((issuer) => issuer.tokenRegistry || "");
}
return [getData(document).proof.value];
};
28 changes: 24 additions & 4 deletions src/did/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import { getPublicKey } from "./resolver";
import { Reason, OpenAttestationSignatureCode } from "../types/error";
import { CodedError } from "../common/error";

export interface DidVerificationStatus {
verified: boolean;
export interface ValidDidVerificationStatus {
verified: true;
did: string;
reason?: Reason;
}

export interface InvalidDidVerificationStatus {
verified: false;
did: string;
reason: Reason;
}

export type DidVerificationStatus = ValidDidVerificationStatus | InvalidDidVerificationStatus;

interface VerifySignature {
did: string;
signature: string;
Expand Down Expand Up @@ -38,9 +45,22 @@ export const verifySecp256k1VerificationKey2018 = ({
};
}

const merkleRootSigned = utils.verifyMessage(messageBytes, signature).toLowerCase() === ethereumAddress.toLowerCase();
if (!merkleRootSigned) {
return {
did,
verified: false,
reason: {
code: OpenAttestationSignatureCode.WRONG_SIGNATURE,
codeString: OpenAttestationSignatureCode[OpenAttestationSignatureCode.WRONG_SIGNATURE],
message: `merkle root is not signed correctly by ${ethereumAddress}`,
},
};
}

return {
did,
verified: utils.verifyMessage(messageBytes, signature).toLowerCase() === ethereumAddress.toLowerCase(),
verified: true,
};
};

Expand Down
26 changes: 13 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { SignedWrappedDocument, v2, v3, WrappedDocument } from "@govtechsg/open-
import { verificationBuilder } from "./verifiers/verificationBuilder";
import { Verifier, Verifiers } from "./types/core";
import { openAttestationHash } from "./verifiers/documentIntegrity/hash/openAttestationHash";
import { Identity, openAttestationDnsTxt } from "./verifiers/issuerIdentity/dnsText/openAttestationDnsTxt";
import { isValid } from "./validator";
import { openAttestationEthereumTokenRegistryStatus } from "./verifiers/documentStatus/tokenRegistryStatus/openAttestationEthereumTokenRegistryStatus";
import { openAttestationEthereumDocumentStoreStatus } from "./verifiers/documentStatus/documentStoreStatus/openAttestationEthereumDocumentStoreStatus";
import { OpenAttestationDidSignedDocumentStatus } from "./verifiers/documentStatus/didSignedDocumentStatus";
import { OpenAttestationDidSignedDidIdentityProof } from "./verifiers/issuerIdentity/didIdentityProof";
import { OpenAttestationDnsDid } from "./verifiers/issuerIdentity/dnsDidProof";
import { openAttestationEthereumTokenRegistryStatus } from "./verifiers/documentStatus/tokenRegistry";
import { openAttestationEthereumDocumentStoreStatus } from "./verifiers/documentStatus/documentStore";
import { openAttestationDidSignedDocumentStatus } from "./verifiers/documentStatus/didSigned";
import { Identity, openAttestationDnsTxtIdentityProof } from "./verifiers/issuerIdentity/dnsTxt";
import { openAttestationDidIdentityProof } from "./verifiers/issuerIdentity/did";
import { openAttestationDnsDidIdentityProof } from "./verifiers/issuerIdentity/dnsDid";

const openAttestationVerifiers: Verifiers[] = [
openAttestationHash,
openAttestationEthereumTokenRegistryStatus,
openAttestationEthereumDocumentStoreStatus,
openAttestationDnsTxt,
OpenAttestationDnsDid,
OpenAttestationDidSignedDocumentStatus,
openAttestationDidSignedDocumentStatus,
openAttestationDnsTxtIdentityProof,
openAttestationDnsDidIdentityProof,
];

const verify = verificationBuilder<
Expand All @@ -36,10 +36,10 @@ export {
Verifier,
Identity,
openAttestationHash,
openAttestationDnsTxt,
openAttestationEthereumDocumentStoreStatus,
openAttestationEthereumTokenRegistryStatus,
OpenAttestationDnsDid,
OpenAttestationDidSignedDocumentStatus,
OpenAttestationDidSignedDidIdentityProof,
openAttestationDidSignedDocumentStatus,
openAttestationDnsTxtIdentityProof,
openAttestationDnsDidIdentityProof,
openAttestationDidIdentityProof,
};
14 changes: 12 additions & 2 deletions src/types/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export enum OpenAttestationEthereumDocumentStoreStatusCode {
DOCUMENT_REVOKED = 5,
INVALID_ARGUMENT = 6,
CONTRACT_NOT_FOUND = 404,
INVALID_ISSUERS = 7,
INVALID_VALIDATION_METHOD = 8,
SERVER_ERROR = 500,
}
export enum OpenAttestationDocumentSignedCode {
Expand All @@ -23,14 +25,18 @@ export enum OpenAttestationEthereumTokenRegistryStatusCode {
CONTRACT_ADDRESS_INVALID = 2,
ETHERS_UNHANDLED_ERROR = 3,
SKIPPED = 4,
INVALID_ISSUERS = 5,
INVALID_ARGUMENT = 6,
CONTRACT_NOT_FOUND = 404,
UNDEFINED_TOKEN_REGISTRY = 7,
INVALID_VALIDATION_METHOD = 8,
SERVER_ERROR = 500,
}
export enum OpenAttestationDnsTxtCode {
UNEXPECTED_ERROR = 0,
INVALID_IDENTITY = 1,
SKIPPED = 2,
INVALID_ISSUERS = 3,
MATCHING_RECORD_NOT_FOUND = 4,
}
export enum OpenAttestationHashCode {
DOCUMENT_TAMPERED = 0,
Expand All @@ -41,15 +47,18 @@ export enum OpenAttestationDidSignedDocumentStatusCode {
UNEXPECTED_ERROR = 1,
MISSING_REVOCATION = 2,
UNSIGNED = 3,
INVALID_ISSUERS = 4,
}
export enum OpenAttestationDidSignedDidIdentityProofCode {
export enum OpenAttestationDidCode {
SKIPPED = 0,
UNEXPECTED_ERROR = 1,
INVALID_ISSUERS = 2,
}
export enum OpenAttestationDnsDidCode {
SKIPPED = 0,
UNEXPECTED_ERROR = 1,
MALFORMED_IDENTITY_PROOF = 2,
INVALID_ISSUERS = 3,
}
export enum OpenAttestationSignatureCode {
UNEXPECTED_ERROR = 0,
Expand All @@ -59,6 +68,7 @@ export enum OpenAttestationSignatureCode {
KEY_NOT_IN_DID = 4,
CORRESPONDING_PROOF_MISSING = 5,
UNSUPPORTED_KEY_TYPE = 6,
WRONG_SIGNATURE = 7,
}

export interface EthersError extends Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OpenAttestationDidSignedDocumentStatus } from "./didSignedDocumentStatus";
import { openAttestationDidSignedDocumentStatus } from "./didSignedDocumentStatus";
import { documentRopstenValidWithDocumentStore } from "../../../../test/fixtures/v2/documentRopstenValidWithDocumentStore";
import { documentDidSigned } from "../../../../test/fixtures/v2/documentDidSigned";
import { documentDnsDidSigned } from "../../../../test/fixtures/v2/documentDnsDidSigned";
Expand Down Expand Up @@ -31,7 +31,7 @@ const options = {

describe("skip", () => {
it("should return skip message", async () => {
const message = await OpenAttestationDidSignedDocumentStatus.skip(undefined as any, undefined as any);
const message = await openAttestationDidSignedDocumentStatus.skip(undefined as any, undefined as any);
expect(message).toMatchInlineSnapshot(`
Object {
"name": "OpenAttestationDidSignedDocumentStatus",
Expand All @@ -50,16 +50,16 @@ describe("skip", () => {
describe("test", () => {
describe("v2", () => {
it("should return false for documents not signed by DID", () => {
expect(OpenAttestationDidSignedDocumentStatus.test(documentRopstenValidWithDocumentStore, options)).toBe(false);
expect(OpenAttestationDidSignedDocumentStatus.test(documentRopstenNotIssuedWithTokenRegistry, options)).toBe(
expect(openAttestationDidSignedDocumentStatus.test(documentRopstenValidWithDocumentStore, options)).toBe(false);
expect(openAttestationDidSignedDocumentStatus.test(documentRopstenNotIssuedWithTokenRegistry, options)).toBe(
false
);
});
it("should return true for documents where any issuer is using the `DID` identity proof", () => {
expect(OpenAttestationDidSignedDocumentStatus.test(documentDidSigned, options)).toBe(true);
expect(openAttestationDidSignedDocumentStatus.test(documentDidSigned, options)).toBe(true);
});
it("should return true for documents where any issuer is using the `DNS-DID` identity proof", () => {
expect(OpenAttestationDidSignedDocumentStatus.test(documentDnsDidSigned, options)).toBe(true);
expect(openAttestationDidSignedDocumentStatus.test(documentDnsDidSigned, options)).toBe(true);
});
});
});
Expand All @@ -71,7 +71,7 @@ describe("verify", () => {
describe("v2", () => {
it("should pass for documents using `DID` and is correctly signed", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDidSigned, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDidSigned, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": Object {
Expand All @@ -94,7 +94,7 @@ describe("verify", () => {
});
it("should pass for documents using `DID-DNS` and is correctly signed", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDnsDidSigned, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDnsDidSigned, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": Object {
Expand All @@ -117,7 +117,7 @@ describe("verify", () => {
});
it("should fail when revocation block is missing", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDidObfuscatedRevocation, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDidObfuscatedRevocation, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": [Error: revocation block not found for an issuer],
Expand All @@ -134,7 +134,7 @@ describe("verify", () => {
});
it("should fail when revocation is not set to NONE (for now)", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDidCustomRevocation, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDidCustomRevocation, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": Object {
Expand All @@ -157,7 +157,7 @@ describe("verify", () => {
});
it("should fail when proof is missing", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDidMissingProof, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDidMissingProof, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": [Error: Only signed v2 is supported now],
Expand All @@ -174,7 +174,7 @@ describe("verify", () => {
});
it("should fail when did resolver fails for some reasons", async () => {
mockGetPublicKey.mockRejectedValue(new Error("Error from DID resolver"));
const res = await OpenAttestationDidSignedDocumentStatus.verify(documentDidSigned, options);
const res = await openAttestationDidSignedDocumentStatus.verify(documentDidSigned, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": [Error: Error from DID resolver],
Expand All @@ -191,7 +191,7 @@ describe("verify", () => {
});
it("should fail when corresponding proof to key is not found in proof", async () => {
whenPublicKeyResolvesSuccessfully();
const res = await OpenAttestationDidSignedDocumentStatus.verify({ ...documentDidSigned, proof: [] }, options);
const res = await openAttestationDidSignedDocumentStatus.verify({ ...documentDidSigned, proof: [] }, options);
expect(res).toMatchInlineSnapshot(`
Object {
"data": [Error: Proof not found for did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89#controller],
Expand Down
Loading

0 comments on commit fcbe8f9

Please sign in to comment.