diff options
Diffstat (limited to 'third_party/js/PKI.js/src/ECPrivateKey.ts')
-rw-r--r-- | third_party/js/PKI.js/src/ECPrivateKey.ts | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/ECPrivateKey.ts b/third_party/js/PKI.js/src/ECPrivateKey.ts new file mode 100644 index 0000000000..bc7b06df26 --- /dev/null +++ b/third_party/js/PKI.js/src/ECPrivateKey.ts @@ -0,0 +1,297 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { ECNamedCurves } from "./ECNamedCurves"; +import { ECPublicKey, ECPublicKeyParameters } from "./ECPublicKey"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VERSION = "version"; +const PRIVATE_KEY = "privateKey"; +const NAMED_CURVE = "namedCurve"; +const PUBLIC_KEY = "publicKey"; +const CLEAR_PROPS = [ + VERSION, + PRIVATE_KEY, + NAMED_CURVE, + PUBLIC_KEY +]; + +export interface IECPrivateKey { + version: number; + privateKey: asn1js.OctetString; + namedCurve?: string; + publicKey?: ECPublicKey; +} + +export type ECPrivateKeyParameters = PkiObjectParameters & Partial<IECPrivateKey> & { json?: ECPrivateKeyJson; }; + +export interface ECPrivateKeyJson { + crv: string; + y?: string; + x?: string; + d: string; +} + +/** + * Represents the PrivateKeyInfo structure described in [RFC5915](https://datatracker.ietf.org/doc/html/rfc5915) + */ +export class ECPrivateKey extends PkiObject implements IECPrivateKey { + + public static override CLASS_NAME = "ECPrivateKey"; + + public version!: number; + public privateKey!: asn1js.OctetString; + public namedCurve?: string; + public publicKey?: ECPublicKey; + + /** + * Initializes a new instance of the {@link ECPrivateKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: ECPrivateKeyParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, ECPrivateKey.defaultValues(VERSION)); + this.privateKey = pvutils.getParametersValue(parameters, PRIVATE_KEY, ECPrivateKey.defaultValues(PRIVATE_KEY)); + if (NAMED_CURVE in parameters) { + this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPrivateKey.defaultValues(NAMED_CURVE)); + } + if (PUBLIC_KEY in parameters) { + this.publicKey = pvutils.getParametersValue(parameters, PUBLIC_KEY, ECPrivateKey.defaultValues(PUBLIC_KEY)); + } + + 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 VERSION): 1; + public static override defaultValues(memberName: typeof PRIVATE_KEY): asn1js.OctetString; + public static override defaultValues(memberName: typeof NAMED_CURVE): string; + public static override defaultValues(memberName: typeof PUBLIC_KEY): ECPublicKey; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 1; + case PRIVATE_KEY: + return new asn1js.OctetString(); + case NAMED_CURVE: + return EMPTY_STRING; + case PUBLIC_KEY: + return new ECPublicKey(); + 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 + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === ECPrivateKey.defaultValues(memberName)); + case PRIVATE_KEY: + return (memberValue.isEqual(ECPrivateKey.defaultValues(memberName))); + case NAMED_CURVE: + return (memberValue === EMPTY_STRING); + case PUBLIC_KEY: + return ((ECPublicKey.compareWithDefault(NAMED_CURVE, memberValue.namedCurve)) && + (ECPublicKey.compareWithDefault("x", memberValue.x)) && + (ECPublicKey.compareWithDefault("y", memberValue.y))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + * privateKey OCTET STRING, + * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + privateKey?: string; + namedCurve?: string; + publicKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.OctetString({ name: (names.privateKey || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.ObjectIdentifier({ name: (names.namedCurve || EMPTY_STRING) }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.BitString({ name: (names.publicKey || EMPTY_STRING) }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ECPrivateKey.schema({ + names: { + version: VERSION, + privateKey: PRIVATE_KEY, + namedCurve: NAMED_CURVE, + publicKey: PUBLIC_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.privateKey = asn1.result.privateKey; + + if (NAMED_CURVE in asn1.result) { + this.namedCurve = asn1.result.namedCurve.valueBlock.toString(); + } + + if (PUBLIC_KEY in asn1.result) { + const publicKeyData: ECPublicKeyParameters = { schema: asn1.result.publicKey.valueBlock.valueHex }; + if (NAMED_CURVE in this) { + publicKeyData.namedCurve = this.namedCurve; + } + + this.publicKey = new ECPublicKey(publicKeyData); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + const outputArray: any = [ + new asn1js.Integer({ value: this.version }), + this.privateKey + ]; + + if (this.namedCurve) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.ObjectIdentifier({ value: this.namedCurve }) + ] + })); + } + + if (this.publicKey) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.BitString({ valueHex: this.publicKey.toSchema().toBER(false) }) + ] + })); + } + + return new asn1js.Sequence({ + value: outputArray + }); + } + + public toJSON(): ECPrivateKeyJson { + if (!this.namedCurve || ECPrivateKey.compareWithDefault(NAMED_CURVE, this.namedCurve)) { + throw new Error("Not enough information for making JSON: absent \"namedCurve\" value"); + } + + const curve = ECNamedCurves.find(this.namedCurve); + + const privateKeyJSON: ECPrivateKeyJson = { + crv: curve ? curve.name : this.namedCurve, + d: pvtsutils.Convert.ToBase64Url(this.privateKey.valueBlock.valueHexView), + }; + + if (this.publicKey) { + const publicKeyJSON = this.publicKey.toJSON(); + + privateKeyJSON.x = publicKeyJSON.x; + privateKeyJSON.y = publicKeyJSON.y; + } + + return privateKeyJSON; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + ParameterError.assert("json", json, "crv", "d"); + + let coordinateLength = 0; + + const curve = ECNamedCurves.find(json.crv); + if (curve) { + this.namedCurve = curve.id; + coordinateLength = curve.size; + } + + const convertBuffer = pvtsutils.Convert.FromBase64Url(json.d); + + if (convertBuffer.byteLength < coordinateLength) { + const buffer = new ArrayBuffer(coordinateLength); + const view = new Uint8Array(buffer); + const convertBufferView = new Uint8Array(convertBuffer); + view.set(convertBufferView, 1); + + this.privateKey = new asn1js.OctetString({ valueHex: buffer }); + } else { + this.privateKey = new asn1js.OctetString({ valueHex: convertBuffer.slice(0, coordinateLength) }); + } + + if (json.x && json.y) { + this.publicKey = new ECPublicKey({ json }); + } + } + +} |