ECPublicKey.ts (5974B)
1 import * as asn1js from "asn1js"; 2 import { BufferSourceConverter } from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; 5 import { ECNamedCurves } from "./ECNamedCurves"; 6 import { ParameterError } from "./errors"; 7 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 8 import * as Schema from "./Schema"; 9 10 const X = "x"; 11 const Y = "y"; 12 const NAMED_CURVE = "namedCurve"; 13 14 export interface IECPublicKey { 15 namedCurve: string; 16 x: ArrayBuffer; 17 y: ArrayBuffer; 18 } 19 20 export interface ECPublicKeyJson { 21 crv: string; 22 x: string; 23 y: string; 24 } 25 26 export type ECPublicKeyParameters = PkiObjectParameters & Partial<IECPublicKey> & { json?: ECPublicKeyJson; }; 27 28 /** 29 * Represents the PrivateKeyInfo structure described in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) 30 */ 31 export class ECPublicKey extends PkiObject implements IECPublicKey { 32 33 public static override CLASS_NAME = "ECPublicKey"; 34 35 public namedCurve!: string; 36 public x!: ArrayBuffer; 37 public y!: ArrayBuffer; 38 39 /** 40 * Initializes a new instance of the {@link ECPublicKey} class 41 * @param parameters Initialization parameters 42 */ 43 constructor(parameters: ECPublicKeyParameters = {}) { 44 super(); 45 46 this.x = pvutils.getParametersValue(parameters, X, ECPublicKey.defaultValues(X)); 47 this.y = pvutils.getParametersValue(parameters, Y, ECPublicKey.defaultValues(Y)); 48 this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPublicKey.defaultValues(NAMED_CURVE)); 49 50 if (parameters.json) { 51 this.fromJSON(parameters.json); 52 } 53 54 if (parameters.schema) { 55 this.fromSchema(parameters.schema); 56 } 57 } 58 59 /** 60 * Returns default values for all class members 61 * @param memberName String name for a class member 62 * @returns Default value 63 */ 64 public static override defaultValues(memberName: typeof NAMED_CURVE): string; 65 public static override defaultValues(memberName: typeof X | typeof Y): ArrayBuffer; 66 public static override defaultValues(memberName: string): any { 67 switch (memberName) { 68 case X: 69 case Y: 70 return EMPTY_BUFFER; 71 case NAMED_CURVE: 72 return EMPTY_STRING; 73 default: 74 return super.defaultValues(memberName); 75 } 76 } 77 78 79 /** 80 * Compare values with default values for all class members 81 * @param memberName String name for a class member 82 * @param memberValue Value to compare with default value 83 */ 84 static compareWithDefault<T>(memberName: string, memberValue: T): memberValue is T { 85 switch (memberName) { 86 case X: 87 case Y: 88 return memberValue instanceof ArrayBuffer && 89 (pvutils.isEqualBuffer(memberValue, ECPublicKey.defaultValues(memberName))); 90 case NAMED_CURVE: 91 return typeof memberValue === "string" && 92 memberValue === ECPublicKey.defaultValues(memberName); 93 default: 94 return super.defaultValues(memberName); 95 } 96 } 97 98 /** 99 * Returns value of pre-defined ASN.1 schema for current class 100 * @param parameters Input parameters for the schema 101 * @returns ASN.1 schema object 102 */ 103 public static override schema(): Schema.SchemaType { 104 return new asn1js.RawData(); 105 } 106 107 public fromSchema(schema1: BufferSource): any { 108 //#region Check the schema is valid 109 110 const view = BufferSourceConverter.toUint8Array(schema1); 111 if (view[0] !== 0x04) { 112 throw new Error("Object's schema was not verified against input data for ECPublicKey"); 113 } 114 //#endregion 115 116 //#region Get internal properties from parsed schema 117 const namedCurve = ECNamedCurves.find(this.namedCurve); 118 if (!namedCurve) { 119 throw new Error(`Incorrect curve OID: ${this.namedCurve}`); 120 } 121 const coordinateLength = namedCurve.size; 122 123 if (view.byteLength !== (coordinateLength * 2 + 1)) { 124 throw new Error("Object's schema was not verified against input data for ECPublicKey"); 125 } 126 127 this.namedCurve = namedCurve.name; 128 this.x = view.slice(1, coordinateLength + 1).buffer; 129 this.y = view.slice(1 + coordinateLength, coordinateLength * 2 + 1).buffer; 130 //#endregion 131 } 132 133 public toSchema(): asn1js.RawData { 134 return new asn1js.RawData({ 135 data: pvutils.utilConcatBuf( 136 (new Uint8Array([0x04])).buffer, 137 this.x, 138 this.y 139 ) 140 }); 141 } 142 143 public toJSON(): ECPublicKeyJson { 144 const namedCurve = ECNamedCurves.find(this.namedCurve); 145 146 return { 147 crv: namedCurve ? namedCurve.name : this.namedCurve, 148 x: pvutils.toBase64(pvutils.arrayBufferToString(this.x), true, true, false), 149 y: pvutils.toBase64(pvutils.arrayBufferToString(this.y), true, true, false) 150 }; 151 } 152 153 /** 154 * Converts JSON value into current object 155 * @param json JSON object 156 */ 157 public fromJSON(json: any): void { 158 ParameterError.assert("json", json, "crv", "x", "y"); 159 160 let coordinateLength = 0; 161 const namedCurve = ECNamedCurves.find(json.crv); 162 if (namedCurve) { 163 this.namedCurve = namedCurve.id; 164 coordinateLength = namedCurve.size; 165 } 166 167 // TODO Simplify Base64url encoding 168 const xConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.x, true)); 169 170 if (xConvertBuffer.byteLength < coordinateLength) { 171 this.x = new ArrayBuffer(coordinateLength); 172 const view = new Uint8Array(this.x); 173 const convertBufferView = new Uint8Array(xConvertBuffer); 174 view.set(convertBufferView, 1); 175 } else { 176 this.x = xConvertBuffer.slice(0, coordinateLength); 177 } 178 179 // TODO Simplify Base64url encoding 180 const yConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.y, true)); 181 182 if (yConvertBuffer.byteLength < coordinateLength) { 183 this.y = new ArrayBuffer(coordinateLength); 184 const view = new Uint8Array(this.y); 185 const convertBufferView = new Uint8Array(yConvertBuffer); 186 view.set(convertBufferView, 1); 187 } else { 188 this.y = yConvertBuffer.slice(0, coordinateLength); 189 } 190 } 191 192 }