import * as asn1js from "asn1js"; import { BufferSourceConverter } from "pvtsutils"; import * as pvutils from "pvutils"; import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; import { ECNamedCurves } from "./ECNamedCurves"; import { ParameterError } from "./errors"; import { PkiObject, PkiObjectParameters } from "./PkiObject"; import * as Schema from "./Schema"; const X = "x"; const Y = "y"; const NAMED_CURVE = "namedCurve"; export interface IECPublicKey { namedCurve: string; x: ArrayBuffer; y: ArrayBuffer; } export interface ECPublicKeyJson { crv: string; x: string; y: string; } export type ECPublicKeyParameters = PkiObjectParameters & Partial & { json?: ECPublicKeyJson; }; /** * Represents the PrivateKeyInfo structure described in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) */ export class ECPublicKey extends PkiObject implements IECPublicKey { public static override CLASS_NAME = "ECPublicKey"; public namedCurve!: string; public x!: ArrayBuffer; public y!: ArrayBuffer; /** * Initializes a new instance of the {@link ECPublicKey} class * @param parameters Initialization parameters */ constructor(parameters: ECPublicKeyParameters = {}) { super(); this.x = pvutils.getParametersValue(parameters, X, ECPublicKey.defaultValues(X)); this.y = pvutils.getParametersValue(parameters, Y, ECPublicKey.defaultValues(Y)); this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPublicKey.defaultValues(NAMED_CURVE)); if (parameters.json) { this.fromJSON(parameters.json); } if (parameters.schema) { this.fromSchema(parameters.schema); } } /** * Returns default values for all class members * @param memberName String name for a class member * @returns Default value */ public static override defaultValues(memberName: typeof NAMED_CURVE): string; public static override defaultValues(memberName: typeof X | typeof Y): ArrayBuffer; public static override defaultValues(memberName: string): any { switch (memberName) { case X: case Y: return EMPTY_BUFFER; case NAMED_CURVE: return EMPTY_STRING; default: return super.defaultValues(memberName); } } /** * Compare values with default values for all class members * @param memberName String name for a class member * @param memberValue Value to compare with default value */ static compareWithDefault(memberName: string, memberValue: T): memberValue is T { switch (memberName) { case X: case Y: return memberValue instanceof ArrayBuffer && (pvutils.isEqualBuffer(memberValue, ECPublicKey.defaultValues(memberName))); case NAMED_CURVE: return typeof memberValue === "string" && memberValue === ECPublicKey.defaultValues(memberName); default: return super.defaultValues(memberName); } } /** * Returns value of pre-defined ASN.1 schema for current class * @param parameters Input parameters for the schema * @returns ASN.1 schema object */ public static override schema(): Schema.SchemaType { return new asn1js.RawData(); } public fromSchema(schema1: BufferSource): any { //#region Check the schema is valid const view = BufferSourceConverter.toUint8Array(schema1); if (view[0] !== 0x04) { throw new Error("Object's schema was not verified against input data for ECPublicKey"); } //#endregion //#region Get internal properties from parsed schema const namedCurve = ECNamedCurves.find(this.namedCurve); if (!namedCurve) { throw new Error(`Incorrect curve OID: ${this.namedCurve}`); } const coordinateLength = namedCurve.size; if (view.byteLength !== (coordinateLength * 2 + 1)) { throw new Error("Object's schema was not verified against input data for ECPublicKey"); } this.namedCurve = namedCurve.name; this.x = view.slice(1, coordinateLength + 1).buffer; this.y = view.slice(1 + coordinateLength, coordinateLength * 2 + 1).buffer; //#endregion } public toSchema(): asn1js.RawData { return new asn1js.RawData({ data: pvutils.utilConcatBuf( (new Uint8Array([0x04])).buffer, this.x, this.y ) }); } public toJSON(): ECPublicKeyJson { const namedCurve = ECNamedCurves.find(this.namedCurve); return { crv: namedCurve ? namedCurve.name : this.namedCurve, x: pvutils.toBase64(pvutils.arrayBufferToString(this.x), true, true, false), y: pvutils.toBase64(pvutils.arrayBufferToString(this.y), true, true, false) }; } /** * Converts JSON value into current object * @param json JSON object */ public fromJSON(json: any): void { ParameterError.assert("json", json, "crv", "x", "y"); let coordinateLength = 0; const namedCurve = ECNamedCurves.find(json.crv); if (namedCurve) { this.namedCurve = namedCurve.id; coordinateLength = namedCurve.size; } // TODO Simplify Base64url encoding const xConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.x, true)); if (xConvertBuffer.byteLength < coordinateLength) { this.x = new ArrayBuffer(coordinateLength); const view = new Uint8Array(this.x); const convertBufferView = new Uint8Array(xConvertBuffer); view.set(convertBufferView, 1); } else { this.x = xConvertBuffer.slice(0, coordinateLength); } // TODO Simplify Base64url encoding const yConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.y, true)); if (yConvertBuffer.byteLength < coordinateLength) { this.y = new ArrayBuffer(coordinateLength); const view = new Uint8Array(this.y); const convertBufferView = new Uint8Array(yConvertBuffer); view.set(convertBufferView, 1); } else { this.y = yConvertBuffer.slice(0, coordinateLength); } } }