ECPrivateKey.ts (8988B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import { EMPTY_STRING } from "./constants"; 5 import { ECNamedCurves } from "./ECNamedCurves"; 6 import { ECPublicKey, ECPublicKeyParameters } from "./ECPublicKey"; 7 import { AsnError, ParameterError } from "./errors"; 8 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 9 import * as Schema from "./Schema"; 10 11 const VERSION = "version"; 12 const PRIVATE_KEY = "privateKey"; 13 const NAMED_CURVE = "namedCurve"; 14 const PUBLIC_KEY = "publicKey"; 15 const CLEAR_PROPS = [ 16 VERSION, 17 PRIVATE_KEY, 18 NAMED_CURVE, 19 PUBLIC_KEY 20 ]; 21 22 export interface IECPrivateKey { 23 version: number; 24 privateKey: asn1js.OctetString; 25 namedCurve?: string; 26 publicKey?: ECPublicKey; 27 } 28 29 export type ECPrivateKeyParameters = PkiObjectParameters & Partial<IECPrivateKey> & { json?: ECPrivateKeyJson; }; 30 31 export interface ECPrivateKeyJson { 32 crv: string; 33 y?: string; 34 x?: string; 35 d: string; 36 } 37 38 /** 39 * Represents the PrivateKeyInfo structure described in [RFC5915](https://datatracker.ietf.org/doc/html/rfc5915) 40 */ 41 export class ECPrivateKey extends PkiObject implements IECPrivateKey { 42 43 public static override CLASS_NAME = "ECPrivateKey"; 44 45 public version!: number; 46 public privateKey!: asn1js.OctetString; 47 public namedCurve?: string; 48 public publicKey?: ECPublicKey; 49 50 /** 51 * Initializes a new instance of the {@link ECPrivateKey} class 52 * @param parameters Initialization parameters 53 */ 54 constructor(parameters: ECPrivateKeyParameters = {}) { 55 super(); 56 57 this.version = pvutils.getParametersValue(parameters, VERSION, ECPrivateKey.defaultValues(VERSION)); 58 this.privateKey = pvutils.getParametersValue(parameters, PRIVATE_KEY, ECPrivateKey.defaultValues(PRIVATE_KEY)); 59 if (NAMED_CURVE in parameters) { 60 this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPrivateKey.defaultValues(NAMED_CURVE)); 61 } 62 if (PUBLIC_KEY in parameters) { 63 this.publicKey = pvutils.getParametersValue(parameters, PUBLIC_KEY, ECPrivateKey.defaultValues(PUBLIC_KEY)); 64 } 65 66 if (parameters.json) { 67 this.fromJSON(parameters.json); 68 } 69 70 if (parameters.schema) { 71 this.fromSchema(parameters.schema); 72 } 73 } 74 75 /** 76 * Returns default values for all class members 77 * @param memberName String name for a class member 78 * @returns Default value 79 */ 80 public static override defaultValues(memberName: typeof VERSION): 1; 81 public static override defaultValues(memberName: typeof PRIVATE_KEY): asn1js.OctetString; 82 public static override defaultValues(memberName: typeof NAMED_CURVE): string; 83 public static override defaultValues(memberName: typeof PUBLIC_KEY): ECPublicKey; 84 public static override defaultValues(memberName: string): any { 85 switch (memberName) { 86 case VERSION: 87 return 1; 88 case PRIVATE_KEY: 89 return new asn1js.OctetString(); 90 case NAMED_CURVE: 91 return EMPTY_STRING; 92 case PUBLIC_KEY: 93 return new ECPublicKey(); 94 default: 95 return super.defaultValues(memberName); 96 } 97 } 98 99 /** 100 * Compare values with default values for all class members 101 * @param memberName String name for a class member 102 * @param memberValue Value to compare with default value 103 */ 104 public static compareWithDefault(memberName: string, memberValue: any): boolean { 105 switch (memberName) { 106 case VERSION: 107 return (memberValue === ECPrivateKey.defaultValues(memberName)); 108 case PRIVATE_KEY: 109 return (memberValue.isEqual(ECPrivateKey.defaultValues(memberName))); 110 case NAMED_CURVE: 111 return (memberValue === EMPTY_STRING); 112 case PUBLIC_KEY: 113 return ((ECPublicKey.compareWithDefault(NAMED_CURVE, memberValue.namedCurve)) && 114 (ECPublicKey.compareWithDefault("x", memberValue.x)) && 115 (ECPublicKey.compareWithDefault("y", memberValue.y))); 116 default: 117 return super.defaultValues(memberName); 118 } 119 } 120 121 /** 122 * @inheritdoc 123 * @asn ASN.1 schema 124 * ```asn 125 * ECPrivateKey ::= SEQUENCE { 126 * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), 127 * privateKey OCTET STRING, 128 * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, 129 * publicKey [1] BIT STRING OPTIONAL 130 * } 131 *``` 132 */ 133 public static override schema(parameters: Schema.SchemaParameters<{ 134 version?: string; 135 privateKey?: string; 136 namedCurve?: string; 137 publicKey?: string; 138 }> = {}): Schema.SchemaType { 139 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 140 141 return (new asn1js.Sequence({ 142 name: (names.blockName || EMPTY_STRING), 143 value: [ 144 new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), 145 new asn1js.OctetString({ name: (names.privateKey || EMPTY_STRING) }), 146 new asn1js.Constructed({ 147 optional: true, 148 idBlock: { 149 tagClass: 3, // CONTEXT-SPECIFIC 150 tagNumber: 0 // [0] 151 }, 152 value: [ 153 new asn1js.ObjectIdentifier({ name: (names.namedCurve || EMPTY_STRING) }) 154 ] 155 }), 156 new asn1js.Constructed({ 157 optional: true, 158 idBlock: { 159 tagClass: 3, // CONTEXT-SPECIFIC 160 tagNumber: 1 // [1] 161 }, 162 value: [ 163 new asn1js.BitString({ name: (names.publicKey || EMPTY_STRING) }) 164 ] 165 }) 166 ] 167 })); 168 } 169 170 public fromSchema(schema: Schema.SchemaType): void { 171 // Clear input data first 172 pvutils.clearProps(schema, CLEAR_PROPS); 173 174 // Check the schema is valid 175 const asn1 = asn1js.compareSchema(schema, 176 schema, 177 ECPrivateKey.schema({ 178 names: { 179 version: VERSION, 180 privateKey: PRIVATE_KEY, 181 namedCurve: NAMED_CURVE, 182 publicKey: PUBLIC_KEY 183 } 184 }) 185 ); 186 AsnError.assertSchema(asn1, this.className); 187 188 //#region Get internal properties from parsed schema 189 this.version = asn1.result.version.valueBlock.valueDec; 190 this.privateKey = asn1.result.privateKey; 191 192 if (NAMED_CURVE in asn1.result) { 193 this.namedCurve = asn1.result.namedCurve.valueBlock.toString(); 194 } 195 196 if (PUBLIC_KEY in asn1.result) { 197 const publicKeyData: ECPublicKeyParameters = { schema: asn1.result.publicKey.valueBlock.valueHex }; 198 if (NAMED_CURVE in this) { 199 publicKeyData.namedCurve = this.namedCurve; 200 } 201 202 this.publicKey = new ECPublicKey(publicKeyData); 203 } 204 //#endregion 205 } 206 207 public toSchema(): asn1js.Sequence { 208 const outputArray: any = [ 209 new asn1js.Integer({ value: this.version }), 210 this.privateKey 211 ]; 212 213 if (this.namedCurve) { 214 outputArray.push(new asn1js.Constructed({ 215 idBlock: { 216 tagClass: 3, // CONTEXT-SPECIFIC 217 tagNumber: 0 // [0] 218 }, 219 value: [ 220 new asn1js.ObjectIdentifier({ value: this.namedCurve }) 221 ] 222 })); 223 } 224 225 if (this.publicKey) { 226 outputArray.push(new asn1js.Constructed({ 227 idBlock: { 228 tagClass: 3, // CONTEXT-SPECIFIC 229 tagNumber: 1 // [1] 230 }, 231 value: [ 232 new asn1js.BitString({ valueHex: this.publicKey.toSchema().toBER(false) }) 233 ] 234 })); 235 } 236 237 return new asn1js.Sequence({ 238 value: outputArray 239 }); 240 } 241 242 public toJSON(): ECPrivateKeyJson { 243 if (!this.namedCurve || ECPrivateKey.compareWithDefault(NAMED_CURVE, this.namedCurve)) { 244 throw new Error("Not enough information for making JSON: absent \"namedCurve\" value"); 245 } 246 247 const curve = ECNamedCurves.find(this.namedCurve); 248 249 const privateKeyJSON: ECPrivateKeyJson = { 250 crv: curve ? curve.name : this.namedCurve, 251 d: pvtsutils.Convert.ToBase64Url(this.privateKey.valueBlock.valueHexView), 252 }; 253 254 if (this.publicKey) { 255 const publicKeyJSON = this.publicKey.toJSON(); 256 257 privateKeyJSON.x = publicKeyJSON.x; 258 privateKeyJSON.y = publicKeyJSON.y; 259 } 260 261 return privateKeyJSON; 262 } 263 264 /** 265 * Converts JSON value into current object 266 * @param json JSON object 267 */ 268 public fromJSON(json: any): void { 269 ParameterError.assert("json", json, "crv", "d"); 270 271 let coordinateLength = 0; 272 273 const curve = ECNamedCurves.find(json.crv); 274 if (curve) { 275 this.namedCurve = curve.id; 276 coordinateLength = curve.size; 277 } 278 279 const convertBuffer = pvtsutils.Convert.FromBase64Url(json.d); 280 281 if (convertBuffer.byteLength < coordinateLength) { 282 const buffer = new ArrayBuffer(coordinateLength); 283 const view = new Uint8Array(buffer); 284 const convertBufferView = new Uint8Array(convertBuffer); 285 view.set(convertBufferView, 1); 286 287 this.privateKey = new asn1js.OctetString({ valueHex: buffer }); 288 } else { 289 this.privateKey = new asn1js.OctetString({ valueHex: convertBuffer.slice(0, coordinateLength) }); 290 } 291 292 if (json.x && json.y) { 293 this.publicKey = new ECPublicKey({ json }); 294 } 295 } 296 297 }