From 6468eff0fb74a610821c34171152c3daf5dd24a3 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Tue, 26 Jul 2022 12:42:54 +1000 Subject: [PATCH 1/2] feature: Dynamic schema object generator WIP --- src/main/javascript/crypto/package.json | 1 + src/main/javascript/crypto/parser-test.html | 14 ++ src/main/javascript/crypto/src/bundle.ts | 5 +- src/main/javascript/crypto/src/main.spec.ts | 43 ++++ .../crypto/src/util/SchemaGenerator.ts | 189 ++++++++++++++++++ src/main/javascript/crypto/webpack.config.js | 8 + 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/main/javascript/crypto/parser-test.html create mode 100644 src/main/javascript/crypto/src/util/SchemaGenerator.ts diff --git a/src/main/javascript/crypto/package.json b/src/main/javascript/crypto/package.json index 1fdd1ccd..3293e66e 100644 --- a/src/main/javascript/crypto/package.json +++ b/src/main/javascript/crypto/package.json @@ -11,6 +11,7 @@ "pack": "webpack", "clean": "rm -R dist", "watch": "webpack --watch --progress", + "serve": "webpack serve", "testjest": "jest", "prepublishOnly": "npm run clean && npm run build" }, diff --git a/src/main/javascript/crypto/parser-test.html b/src/main/javascript/crypto/parser-test.html new file mode 100644 index 00000000..204efe91 --- /dev/null +++ b/src/main/javascript/crypto/parser-test.html @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/javascript/crypto/src/bundle.ts b/src/main/javascript/crypto/src/bundle.ts index fb86ffc0..26a52a9b 100644 --- a/src/main/javascript/crypto/src/bundle.ts +++ b/src/main/javascript/crypto/src/bundle.ts @@ -2,8 +2,11 @@ import {Authenticator} from "./Authenticator"; import {Eip712AttestationRequest} from "./libs/Eip712AttestationRequest"; import {AttestationCrypto} from "./libs/AttestationCrypto"; import {IntegrationExample} from "./IntegrationExample"; +import {Meh, SchemaGenerator} from "./util/SchemaGenerator"; (window as any).Authenticator = Authenticator; (window as any).Attest = Eip712AttestationRequest; (window as any).AttestationCrypto = AttestationCrypto; -(window as any).IntegrationExample = IntegrationExample; \ No newline at end of file +(window as any).IntegrationExample = IntegrationExample; +(window as any).SchemaGenerator = SchemaGenerator; +(window as any).Meh = Meh; \ No newline at end of file diff --git a/src/main/javascript/crypto/src/main.spec.ts b/src/main/javascript/crypto/src/main.spec.ts index 84eff475..b3b3ab7d 100644 --- a/src/main/javascript/crypto/src/main.spec.ts +++ b/src/main/javascript/crypto/src/main.spec.ts @@ -31,6 +31,15 @@ import {Issuer} from "./libs/Issuer"; import { AttestedObject } from './libs/AttestedObject'; import { AttestableObject } from './libs/AttestableObject'; import { UseToken } from './asn1/shemas/UseToken'; +import subtle from "./safe-connect/SubtleCryptoShim"; +import {EthereumAddressAttestation} from "./safe-connect/EthereumAddressAttestation"; +import {EthereumKeyLinkingAttestation} from "./safe-connect/EthereumKeyLinkingAttestation"; +import {SchemaGenerator} from "./util/SchemaGenerator"; +import {DevconTicket, SignedDevconTicket} from "./asn1/shemas/SignedDevconTicket"; +import * as util from "util"; +import * as asn1_schema_1 from "@peculiar/asn1-schema"; +import {AsnParser, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema"; +import {utils} from "ethers"; const url = require('url'); let EC = require("elliptic"); @@ -676,4 +685,38 @@ describe("read attested object", () => { }) }) +describe("Schema Generator", function(){ + + test("Serialize & parse ASN based on a dynamic schema", async function(){ + + let schemaGenerator = new SchemaGenerator(); + + let GeneratedSchema = schemaGenerator.getSchemaObject(); + + console.log(util.inspect(GeneratedSchema)); + + let currentSchema = new GeneratedSchema(); + + currentSchema.ticket.devconId = "6"; + currentSchema.ticket.ticketIdNumber = 10; + currentSchema.ticket.ticketClass = 1; + + currentSchema.ticket.linkedTicket.devconId = "6"; + currentSchema.ticket.linkedTicket.ticketIdNumber = 10; + currentSchema.ticket.linkedTicket.ticketClass = 1; + + currentSchema.signatureValue = new Uint8Array(hexStringToUint8("0xb135ded73c021184158fa6ea91eff0a97753f27163f3b35a57d3fac57146bf0a45795224fefb95edde7dd55a1554829b5be20f3e39b1fb27a52bd63972d1e89c1c")); + + console.log(currentSchema); + + let encoded = AsnSerializer.serialize(currentSchema); + + console.log(uint8tohex(new Uint8Array(encoded))); + + let decoded = AsnParser.parse(encoded, GeneratedSchema); + + console.log(decoded); + }); +}); + diff --git a/src/main/javascript/crypto/src/util/SchemaGenerator.ts b/src/main/javascript/crypto/src/util/SchemaGenerator.ts new file mode 100644 index 00000000..659fb437 --- /dev/null +++ b/src/main/javascript/crypto/src/util/SchemaGenerator.ts @@ -0,0 +1,189 @@ +import * as asn1_schema_1 from "@peculiar/asn1-schema"; +import {AsnParser, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema"; +import {hexStringToUint8, uint8tohex} from "../libs/utils"; +import {AsnItemType, AsnRepeatType, IAsn1PropOptions} from "@peculiar/asn1-schema/build/types/decorators"; +import {IAsnConverter} from "@peculiar/asn1-schema/build/types/types"; + +interface SchemaDefinitionInterface { + [key: string]: SchemaItemInterface +} + +interface SchemaItemInterface { + name?: string; + items?: SchemaDefinitionInterface, + type?: AsnItemType | string; + optional?: boolean; + defaultValue?: any; + context?: number; + implicit?: boolean; + converter?: IAsnConverter; + repeated?: AsnRepeatType; +} + +export class SchemaGenerator { + + jsonSchema: any; + + schemaObject: any; + + private static __decorate = function (decorators: any, target: any, key: any, desc: any) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + // @ts-ignore + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; + }; + + constructor(jsonSchema: SchemaDefinitionInterface = { + ticket: { + name: "Ticket", + items: { + devconId: { + type: "Utf8String", + optional: false + }, + ticketIdNumber: { + type: "Integer", + optional: true + }, + ticketIdString: { + type: "Utf8String", + optional: true + }, + ticketClass: { + type: "Integer", + optional: false + }, + linkedTicket: { + name: "Linked Ticket", + items: { + devconId: { + type: "Utf8String", + optional: false + }, + ticketIdNumber: { + type: "Integer", + optional: true + }, + ticketIdString: { + type: "Utf8String", + optional: true + }, + ticketClass: { + type: "Integer", + optional: false + } + } + } + } + }, + commitment: { + type: AsnPropTypes.OctetString, + optional: true + }, + signatureValue: { + type: AsnPropTypes.BitString, + optional: false + } + }) { + this.jsonSchema = jsonSchema; + + this.schemaObject = this.generateSchema(); + } + + private generateSchema(): any { + + let Schema: any = class {}; + + for (let i in this.jsonSchema){ + + if (this.jsonSchema[i].items){ + + let childSchemaGenerator: any = new SchemaGenerator(this.jsonSchema[i].items); + let childSchema = childSchemaGenerator.getSchemaObject(); + + Schema.prototype[i] = new childSchema(); + + SchemaGenerator.__decorate([ + (asn1_schema_1.AsnProp)(this.getDecoratorOptions({ type: childSchema })) + ], Schema.prototype, i, void 0); + + } else { + SchemaGenerator.__decorate([ + (asn1_schema_1.AsnProp)(this.getDecoratorOptions(this.jsonSchema[i])) + ], Schema.prototype, i, void 0); + } + + } + + return Schema; + } + + private getDecoratorOptions(item: SchemaItemInterface){ + + let type: AsnPropTypes; + + if (typeof item.type === "string"){ + if (!(item.type in AsnPropTypes)) + throw new Error("Non-existent AsnPropType " + item.type); + + type = AsnPropTypes[item.type as any] as unknown as AsnPropTypes; + } else { + type = item.type as AsnPropTypes; + } + + let decoratorOptions: IAsn1PropOptions = { + type: type, + optional: item.optional, + defaultValue: item.defaultValue, + context: item.context, + implicit: item.implicit, + converter: item.converter, + repeated: item.repeated + } + + return decoratorOptions; + } + + getSchemaObject(){ + return this.schemaObject; + } + + +} + +export class Meh { + + meh() { + + let schemaGenerator = new SchemaGenerator(); + + let GeneratedSchema = schemaGenerator.getSchemaObject(); + + console.log("The full schema object"); + console.log(GeneratedSchema); + + let currentSchema = new GeneratedSchema(); + + currentSchema.ticket.devconId = "6"; + currentSchema.ticket.ticketIdNumber = 10; + currentSchema.ticket.ticketClass = 1; + + currentSchema.ticket.linkedTicket.devconId = "6"; + currentSchema.ticket.linkedTicket.ticketIdNumber = 10; + currentSchema.ticket.linkedTicket.ticketClass = 1; + + currentSchema.signatureValue = new Uint8Array(hexStringToUint8("0xb135ded73c021184158fa6ea91eff0a97753f27163f3b35a57d3fac57146bf0a45795224fefb95edde7dd55a1554829b5be20f3e39b1fb27a52bd63972d1e89c1c")); + + console.log("Populated schema object"); + console.log(currentSchema); + + let encoded = AsnSerializer.serialize(currentSchema); + + console.log(uint8tohex(new Uint8Array(encoded))); + + let decoded = AsnParser.parse(encoded, GeneratedSchema); + + console.log(decoded); + } +} \ No newline at end of file diff --git a/src/main/javascript/crypto/webpack.config.js b/src/main/javascript/crypto/webpack.config.js index 7986c952..7b2ff150 100644 --- a/src/main/javascript/crypto/webpack.config.js +++ b/src/main/javascript/crypto/webpack.config.js @@ -2,6 +2,7 @@ const path = require('path'); module.exports = { entry: './src/bundle.ts', + mode: "development", module: { rules: [ { @@ -13,6 +14,13 @@ module.exports = { } ] }, + devServer: { + static: { + directory: path.join(__dirname, '/'), + }, + compress: true, + port: 3015, + }, resolve: { extensions: [ '.tsx', '.ts', '.js' ], fallback: From e4e43ec78bc62354ddf1dad6038bb274c4a992da Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Mon, 1 Aug 2022 15:40:03 +1000 Subject: [PATCH 2/2] fix: test cleanup and improve verbosity & API - Cleanup HTML test, move out of main class file. - Improve verbosity of API & private members. - Add hex & base64 format support. --- src/main/javascript/crypto/parser-test.html | 87 ++++++++++++- src/main/javascript/crypto/src/bundle.ts | 5 +- src/main/javascript/crypto/src/main.spec.ts | 116 ++++++++++++----- .../crypto/src/util/SchemaGenerator.ts | 120 +++++------------- 4 files changed, 205 insertions(+), 123 deletions(-) diff --git a/src/main/javascript/crypto/parser-test.html b/src/main/javascript/crypto/parser-test.html index 204efe91..8b701564 100644 --- a/src/main/javascript/crypto/parser-test.html +++ b/src/main/javascript/crypto/parser-test.html @@ -7,8 +7,93 @@ - + \ No newline at end of file diff --git a/src/main/javascript/crypto/src/bundle.ts b/src/main/javascript/crypto/src/bundle.ts index 26a52a9b..86eead8c 100644 --- a/src/main/javascript/crypto/src/bundle.ts +++ b/src/main/javascript/crypto/src/bundle.ts @@ -2,11 +2,10 @@ import {Authenticator} from "./Authenticator"; import {Eip712AttestationRequest} from "./libs/Eip712AttestationRequest"; import {AttestationCrypto} from "./libs/AttestationCrypto"; import {IntegrationExample} from "./IntegrationExample"; -import {Meh, SchemaGenerator} from "./util/SchemaGenerator"; +import {SchemaGenerator} from "./util/SchemaGenerator"; (window as any).Authenticator = Authenticator; (window as any).Attest = Eip712AttestationRequest; (window as any).AttestationCrypto = AttestationCrypto; (window as any).IntegrationExample = IntegrationExample; -(window as any).SchemaGenerator = SchemaGenerator; -(window as any).Meh = Meh; \ No newline at end of file +(window as any).SchemaGenerator = SchemaGenerator; \ No newline at end of file diff --git a/src/main/javascript/crypto/src/main.spec.ts b/src/main/javascript/crypto/src/main.spec.ts index b3b3ab7d..2790e206 100644 --- a/src/main/javascript/crypto/src/main.spec.ts +++ b/src/main/javascript/crypto/src/main.spec.ts @@ -31,15 +31,8 @@ import {Issuer} from "./libs/Issuer"; import { AttestedObject } from './libs/AttestedObject'; import { AttestableObject } from './libs/AttestableObject'; import { UseToken } from './asn1/shemas/UseToken'; -import subtle from "./safe-connect/SubtleCryptoShim"; -import {EthereumAddressAttestation} from "./safe-connect/EthereumAddressAttestation"; -import {EthereumKeyLinkingAttestation} from "./safe-connect/EthereumKeyLinkingAttestation"; import {SchemaGenerator} from "./util/SchemaGenerator"; import {DevconTicket, SignedDevconTicket} from "./asn1/shemas/SignedDevconTicket"; -import * as util from "util"; -import * as asn1_schema_1 from "@peculiar/asn1-schema"; -import {AsnParser, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema"; -import {utils} from "ethers"; const url = require('url'); let EC = require("elliptic"); @@ -689,33 +682,94 @@ describe("Schema Generator", function(){ test("Serialize & parse ASN based on a dynamic schema", async function(){ - let schemaGenerator = new SchemaGenerator(); - - let GeneratedSchema = schemaGenerator.getSchemaObject(); - - console.log(util.inspect(GeneratedSchema)); - - let currentSchema = new GeneratedSchema(); - - currentSchema.ticket.devconId = "6"; - currentSchema.ticket.ticketIdNumber = 10; - currentSchema.ticket.ticketClass = 1; - - currentSchema.ticket.linkedTicket.devconId = "6"; - currentSchema.ticket.linkedTicket.ticketIdNumber = 10; - currentSchema.ticket.linkedTicket.ticketClass = 1; - - currentSchema.signatureValue = new Uint8Array(hexStringToUint8("0xb135ded73c021184158fa6ea91eff0a97753f27163f3b35a57d3fac57146bf0a45795224fefb95edde7dd55a1554829b5be20f3e39b1fb27a52bd63972d1e89c1c")); - - console.log(currentSchema); - - let encoded = AsnSerializer.serialize(currentSchema); + let generatedSchema = new SchemaGenerator({ + ticket: { + name: "Ticket", + items: { + devconId: { + type: "Utf8String", + optional: false + }, + ticketIdNumber: { + type: "Integer", + optional: true + }, + ticketIdString: { + type: "Utf8String", + optional: true + }, + ticketClass: { + type: "Integer", + optional: false + }, + linkedTicket: { + name: "Linked Ticket", + items: { + devconId: { + type: "Utf8String", + optional: false + }, + ticketIdNumber: { + type: "Integer", + optional: true + }, + ticketIdString: { + type: "Utf8String", + optional: true + }, + ticketClass: { + type: "Integer", + optional: false + } + } + } + } + }, + commitment: { + type: "OctetString", + optional: true + }, + signatureValue: { + type: "BitString", + optional: false + } + }); + + let asnObject = generatedSchema.getSchemaObject(); + + asnObject.ticket.devconId = "6"; + asnObject.ticket.ticketIdNumber = 5; + asnObject.ticket.ticketClass = 1; + + asnObject.ticket.linkedTicket.devconId = "6"; + asnObject.ticket.linkedTicket.ticketIdNumber = 10; + asnObject.ticket.linkedTicket.ticketClass = 2; + + asnObject.signatureValue = new Uint8Array(hexStringToUint8("0xb135ded73c021184158fa6ea91eff0a97753f27163f3b35a57d3fac57146bf0a45795224fefb95edde7dd55a1554829b5be20f3e39b1fb27a52bd63972d1e89c1c")); + + console.log(asnObject); + + let encoded = generatedSchema.serializeAndFormat(asnObject); + + console.log("Encoded: "); + console.log(encoded); + + let decoded = generatedSchema.parse(encoded); + + console.log("Decoded: "); + console.log(decoded); - console.log(uint8tohex(new Uint8Array(encoded))); + // Can't do a single match here because nested properties are set in the prototype + expect(asnObject.ticket.devconId).toBe("6"); + expect(asnObject.ticket.ticketIdNumber).toBe(5); + expect(asnObject.ticket.ticketClass).toBe(1); - let decoded = AsnParser.parse(encoded, GeneratedSchema); + expect(asnObject.ticket.linkedTicket.devconId).toBe("6"); + expect(asnObject.ticket.linkedTicket.ticketIdNumber).toBe(10); + expect(asnObject.ticket.linkedTicket.ticketClass).toBe(2); - console.log(decoded); + decoded.signatureValue = new Uint8Array(decoded.signatureValue); + expect(asnObject.signatureValue).toEqual(decoded.signatureValue); }); }); diff --git a/src/main/javascript/crypto/src/util/SchemaGenerator.ts b/src/main/javascript/crypto/src/util/SchemaGenerator.ts index 659fb437..90a29bc4 100644 --- a/src/main/javascript/crypto/src/util/SchemaGenerator.ts +++ b/src/main/javascript/crypto/src/util/SchemaGenerator.ts @@ -1,6 +1,6 @@ import * as asn1_schema_1 from "@peculiar/asn1-schema"; import {AsnParser, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema"; -import {hexStringToUint8, uint8tohex} from "../libs/utils"; +import {base64ToUint8array, hexStringToUint8, uint8arrayToBase64, uint8tohex} from "../libs/utils"; import {AsnItemType, AsnRepeatType, IAsn1PropOptions} from "@peculiar/asn1-schema/build/types/decorators"; import {IAsnConverter} from "@peculiar/asn1-schema/build/types/types"; @@ -20,11 +20,13 @@ interface SchemaItemInterface { repeated?: AsnRepeatType; } +export declare type EncodingType = "hex" | "base64"; + export class SchemaGenerator { jsonSchema: any; - schemaObject: any; + generatedSchema: any; private static __decorate = function (decorators: any, target: any, key: any, desc: any) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; @@ -34,61 +36,10 @@ export class SchemaGenerator { return c > 3 && r && Object.defineProperty(target, key, r), r; }; - constructor(jsonSchema: SchemaDefinitionInterface = { - ticket: { - name: "Ticket", - items: { - devconId: { - type: "Utf8String", - optional: false - }, - ticketIdNumber: { - type: "Integer", - optional: true - }, - ticketIdString: { - type: "Utf8String", - optional: true - }, - ticketClass: { - type: "Integer", - optional: false - }, - linkedTicket: { - name: "Linked Ticket", - items: { - devconId: { - type: "Utf8String", - optional: false - }, - ticketIdNumber: { - type: "Integer", - optional: true - }, - ticketIdString: { - type: "Utf8String", - optional: true - }, - ticketClass: { - type: "Integer", - optional: false - } - } - } - } - }, - commitment: { - type: AsnPropTypes.OctetString, - optional: true - }, - signatureValue: { - type: AsnPropTypes.BitString, - optional: false - } - }) { + constructor(jsonSchema: SchemaDefinitionInterface) { this.jsonSchema = jsonSchema; - this.schemaObject = this.generateSchema(); + this.generatedSchema = this.generateSchema(); } private generateSchema(): any { @@ -100,8 +51,7 @@ export class SchemaGenerator { if (this.jsonSchema[i].items){ let childSchemaGenerator: any = new SchemaGenerator(this.jsonSchema[i].items); - let childSchema = childSchemaGenerator.getSchemaObject(); - + let childSchema = childSchemaGenerator.getSchemaType(); Schema.prototype[i] = new childSchema(); SchemaGenerator.__decorate([ @@ -145,45 +95,39 @@ export class SchemaGenerator { return decoratorOptions; } - getSchemaObject(){ - return this.schemaObject; + getSchemaType(){ + return this.generatedSchema; } + getSchemaObject(){ + return new this.generatedSchema(); + } -} - -export class Meh { - - meh() { - - let schemaGenerator = new SchemaGenerator(); - - let GeneratedSchema = schemaGenerator.getSchemaObject(); - - console.log("The full schema object"); - console.log(GeneratedSchema); - - let currentSchema = new GeneratedSchema(); - - currentSchema.ticket.devconId = "6"; - currentSchema.ticket.ticketIdNumber = 10; - currentSchema.ticket.ticketClass = 1; - - currentSchema.ticket.linkedTicket.devconId = "6"; - currentSchema.ticket.linkedTicket.ticketIdNumber = 10; - currentSchema.ticket.linkedTicket.ticketClass = 1; + serialize(object: Object): Uint8Array { + return new Uint8Array(AsnSerializer.serialize(object)); + } - currentSchema.signatureValue = new Uint8Array(hexStringToUint8("0xb135ded73c021184158fa6ea91eff0a97753f27163f3b35a57d3fac57146bf0a45795224fefb95edde7dd55a1554829b5be20f3e39b1fb27a52bd63972d1e89c1c")); + serializeAndFormat(object: Object, encoding: EncodingType = "hex"){ - console.log("Populated schema object"); - console.log(currentSchema); + let uint = this.serialize(object); - let encoded = AsnSerializer.serialize(currentSchema); + if (encoding === "hex"){ + return uint8tohex(uint); + } else { + return uint8arrayToBase64(uint); + } + } - console.log(uint8tohex(new Uint8Array(encoded))); + parse(data: Uint8Array|string, encoding: EncodingType = "hex"): any { - let decoded = AsnParser.parse(encoded, GeneratedSchema); + if (!(data instanceof Uint8Array)){ + if (encoding === "hex"){ + data = hexStringToUint8(data); + } else { + data = base64ToUint8array(data); + } + } - console.log(decoded); + return AsnParser.parse(data, this.generatedSchema); } } \ No newline at end of file