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..8b701564 --- /dev/null +++ b/src/main/javascript/crypto/parser-test.html @@ -0,0 +1,99 @@ + +
+ + + + + + + + \ 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..86eead8c 100644 --- a/src/main/javascript/crypto/src/bundle.ts +++ b/src/main/javascript/crypto/src/bundle.ts @@ -2,8 +2,10 @@ import {Authenticator} from "./Authenticator"; import {Eip712AttestationRequest} from "./libs/Eip712AttestationRequest"; import {AttestationCrypto} from "./libs/AttestationCrypto"; import {IntegrationExample} from "./IntegrationExample"; +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; \ No newline at end of file +(window as any).IntegrationExample = IntegrationExample; +(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 84eff475..2790e206 100644 --- a/src/main/javascript/crypto/src/main.spec.ts +++ b/src/main/javascript/crypto/src/main.spec.ts @@ -31,6 +31,8 @@ import {Issuer} from "./libs/Issuer"; import { AttestedObject } from './libs/AttestedObject'; import { AttestableObject } from './libs/AttestableObject'; import { UseToken } from './asn1/shemas/UseToken'; +import {SchemaGenerator} from "./util/SchemaGenerator"; +import {DevconTicket, SignedDevconTicket} from "./asn1/shemas/SignedDevconTicket"; const url = require('url'); let EC = require("elliptic"); @@ -676,4 +678,99 @@ describe("read attested object", () => { }) }) +describe("Schema Generator", function(){ + + test("Serialize & parse ASN based on a dynamic schema", async function(){ + + 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); + + // 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); + + expect(asnObject.ticket.linkedTicket.devconId).toBe("6"); + expect(asnObject.ticket.linkedTicket.ticketIdNumber).toBe(10); + expect(asnObject.ticket.linkedTicket.ticketClass).toBe(2); + + 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 new file mode 100644 index 00000000..90a29bc4 --- /dev/null +++ b/src/main/javascript/crypto/src/util/SchemaGenerator.ts @@ -0,0 +1,133 @@ +import * as asn1_schema_1 from "@peculiar/asn1-schema"; +import {AsnParser, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema"; +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"; + +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 declare type EncodingType = "hex" | "base64"; + +export class SchemaGenerator { + + jsonSchema: 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; + // @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) { + this.jsonSchema = jsonSchema; + + this.generatedSchema = 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.getSchemaType(); + 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; + } + + getSchemaType(){ + return this.generatedSchema; + } + + getSchemaObject(){ + return new this.generatedSchema(); + } + + serialize(object: Object): Uint8Array { + return new Uint8Array(AsnSerializer.serialize(object)); + } + + serializeAndFormat(object: Object, encoding: EncodingType = "hex"){ + + let uint = this.serialize(object); + + if (encoding === "hex"){ + return uint8tohex(uint); + } else { + return uint8arrayToBase64(uint); + } + } + + parse(data: Uint8Array|string, encoding: EncodingType = "hex"): any { + + if (!(data instanceof Uint8Array)){ + if (encoding === "hex"){ + data = hexStringToUint8(data); + } else { + data = base64ToUint8array(data); + } + } + + return AsnParser.parse(data, this.generatedSchema); + } +} \ 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: