PrivateKeyInfo.ts (9433B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; 4 import { Attribute, AttributeJson } from "./Attribute"; 5 import { EMPTY_STRING } from "./constants"; 6 import { ECPrivateKey } from "./ECPrivateKey"; 7 import { AsnError } from "./errors"; 8 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 9 import { RSAPrivateKey } from "./RSAPrivateKey"; 10 import * as Schema from "./Schema"; 11 12 const VERSION = "version"; 13 const PRIVATE_KEY_ALGORITHM = "privateKeyAlgorithm"; 14 const PRIVATE_KEY = "privateKey"; 15 const ATTRIBUTES = "attributes"; 16 const PARSED_KEY = "parsedKey"; 17 const CLEAR_PROPS = [ 18 VERSION, 19 PRIVATE_KEY_ALGORITHM, 20 PRIVATE_KEY, 21 ATTRIBUTES 22 ]; 23 24 export interface IPrivateKeyInfo { 25 version: number; 26 privateKeyAlgorithm: AlgorithmIdentifier; 27 privateKey: asn1js.OctetString; 28 attributes?: Attribute[]; 29 parsedKey?: RSAPrivateKey | ECPrivateKey; 30 } 31 32 export type PrivateKeyInfoParameters = PkiObjectParameters & Partial<IPrivateKeyInfo> & { json?: JsonWebKey; }; 33 34 export interface PrivateKeyInfoJson { 35 version: number; 36 privateKeyAlgorithm: AlgorithmIdentifierJson; 37 privateKey: asn1js.OctetStringJson; 38 attributes?: AttributeJson[]; 39 } 40 41 /** 42 * Represents the PrivateKeyInfo structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5208) 43 */ 44 export class PrivateKeyInfo extends PkiObject implements IPrivateKeyInfo { 45 46 public static override CLASS_NAME = "PrivateKeyInfo"; 47 48 public version!: number; 49 public privateKeyAlgorithm!: AlgorithmIdentifier; 50 public privateKey!: asn1js.OctetString; 51 public attributes?: Attribute[]; 52 public parsedKey?: RSAPrivateKey | ECPrivateKey; 53 54 /** 55 * Initializes a new instance of the {@link PrivateKeyInfo} class 56 * @param parameters Initialization parameters 57 */ 58 constructor(parameters: PrivateKeyInfoParameters = {}) { 59 super(); 60 61 this.version = pvutils.getParametersValue(parameters, VERSION, PrivateKeyInfo.defaultValues(VERSION)); 62 this.privateKeyAlgorithm = pvutils.getParametersValue(parameters, PRIVATE_KEY_ALGORITHM, PrivateKeyInfo.defaultValues(PRIVATE_KEY_ALGORITHM)); 63 this.privateKey = pvutils.getParametersValue(parameters, PRIVATE_KEY, PrivateKeyInfo.defaultValues(PRIVATE_KEY)); 64 if (ATTRIBUTES in parameters) { 65 this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, PrivateKeyInfo.defaultValues(ATTRIBUTES)); 66 } 67 if (PARSED_KEY in parameters) { 68 this.parsedKey = pvutils.getParametersValue(parameters, PARSED_KEY, PrivateKeyInfo.defaultValues(PARSED_KEY)); 69 } 70 71 if (parameters.json) { 72 this.fromJSON(parameters.json); 73 } 74 75 if (parameters.schema) { 76 this.fromSchema(parameters.schema); 77 } 78 } 79 80 /** 81 * Returns default values for all class members 82 * @param memberName String name for a class member 83 * @returns Default value 84 */ 85 public static override defaultValues(memberName: string): any { 86 switch (memberName) { 87 case VERSION: 88 return 0; 89 case PRIVATE_KEY_ALGORITHM: 90 return new AlgorithmIdentifier(); 91 case PRIVATE_KEY: 92 return new asn1js.OctetString(); 93 case ATTRIBUTES: 94 return []; 95 case PARSED_KEY: 96 return {}; 97 default: 98 return super.defaultValues(memberName); 99 } 100 } 101 102 /** 103 * @inheritdoc 104 * @asn ASN.1 schema 105 * ```asn 106 * PrivateKeyInfo ::= SEQUENCE { 107 * version Version, 108 * privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}}, 109 * privateKey PrivateKey, 110 * attributes [0] Attributes OPTIONAL } 111 * 112 * Version ::= INTEGER {v1(0)} (v1,...) 113 * 114 * PrivateKey ::= OCTET STRING 115 * 116 * Attributes ::= SET OF Attribute 117 *``` 118 */ 119 public static override schema(parameters: Schema.SchemaParameters<{ 120 version?: string; 121 privateKeyAlgorithm?: AlgorithmIdentifierSchema; 122 privateKey?: string; 123 attributes?: string; 124 }> = {}): Schema.SchemaType { 125 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 126 127 return (new asn1js.Sequence({ 128 name: (names.blockName || EMPTY_STRING), 129 value: [ 130 new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), 131 AlgorithmIdentifier.schema(names.privateKeyAlgorithm || {}), 132 new asn1js.OctetString({ name: (names.privateKey || EMPTY_STRING) }), 133 new asn1js.Constructed({ 134 optional: true, 135 idBlock: { 136 tagClass: 3, // CONTEXT-SPECIFIC 137 tagNumber: 0 // [0] 138 }, 139 value: [ 140 new asn1js.Repeated({ 141 name: (names.attributes || EMPTY_STRING), 142 value: Attribute.schema() 143 }) 144 ] 145 }) 146 ] 147 })); 148 } 149 150 public fromSchema(schema: Schema.SchemaType): void { 151 // Clear input data first 152 pvutils.clearProps(schema, CLEAR_PROPS); 153 154 // Check the schema is valid 155 const asn1 = asn1js.compareSchema(schema, 156 schema, 157 PrivateKeyInfo.schema({ 158 names: { 159 version: VERSION, 160 privateKeyAlgorithm: { 161 names: { 162 blockName: PRIVATE_KEY_ALGORITHM 163 } 164 }, 165 privateKey: PRIVATE_KEY, 166 attributes: ATTRIBUTES 167 } 168 }) 169 ); 170 AsnError.assertSchema(asn1, this.className); 171 172 //#region Get internal properties from parsed schema 173 this.version = asn1.result.version.valueBlock.valueDec; 174 this.privateKeyAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.privateKeyAlgorithm }); 175 this.privateKey = asn1.result.privateKey; 176 177 if (ATTRIBUTES in asn1.result) 178 this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element })); 179 180 // TODO Use factory 181 switch (this.privateKeyAlgorithm.algorithmId) { 182 case "1.2.840.113549.1.1.1": // RSA 183 { 184 const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHexView); 185 if (privateKeyASN1.offset !== -1) 186 this.parsedKey = new RSAPrivateKey({ schema: privateKeyASN1.result }); 187 } 188 break; 189 case "1.2.840.10045.2.1": // ECDSA 190 if ("algorithmParams" in this.privateKeyAlgorithm) { 191 if (this.privateKeyAlgorithm.algorithmParams instanceof asn1js.ObjectIdentifier) { 192 const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHexView); 193 if (privateKeyASN1.offset !== -1) { 194 this.parsedKey = new ECPrivateKey({ 195 namedCurve: this.privateKeyAlgorithm.algorithmParams.valueBlock.toString(), 196 schema: privateKeyASN1.result 197 }); 198 } 199 } 200 } 201 break; 202 default: 203 } 204 //#endregion 205 } 206 207 public toSchema(): asn1js.Sequence { 208 //#region Create array for output sequence 209 const outputArray: any = [ 210 new asn1js.Integer({ value: this.version }), 211 this.privateKeyAlgorithm.toSchema(), 212 this.privateKey 213 ]; 214 215 if (this.attributes) { 216 outputArray.push(new asn1js.Constructed({ 217 optional: true, 218 idBlock: { 219 tagClass: 3, // CONTEXT-SPECIFIC 220 tagNumber: 0 // [0] 221 }, 222 value: Array.from(this.attributes, o => o.toSchema()) 223 })); 224 } 225 //#endregion 226 227 //#region Construct and return new ASN.1 schema for this object 228 return (new asn1js.Sequence({ 229 value: outputArray 230 })); 231 //#endregion 232 } 233 234 public toJSON(): PrivateKeyInfoJson | JsonWebKey { 235 //#region Return common value in case we do not have enough info fo making JWK 236 if (!this.parsedKey) { 237 const object: PrivateKeyInfoJson = { 238 version: this.version, 239 privateKeyAlgorithm: this.privateKeyAlgorithm.toJSON(), 240 privateKey: this.privateKey.toJSON(), 241 }; 242 243 if (this.attributes) { 244 object.attributes = Array.from(this.attributes, o => o.toJSON()); 245 } 246 247 return object; 248 } 249 //#endregion 250 251 //#region Making JWK 252 const jwk: JsonWebKey = {}; 253 254 switch (this.privateKeyAlgorithm.algorithmId) { 255 case "1.2.840.10045.2.1": // ECDSA 256 jwk.kty = "EC"; 257 break; 258 case "1.2.840.113549.1.1.1": // RSA 259 jwk.kty = "RSA"; 260 break; 261 default: 262 } 263 264 // TODO Unclear behavior 265 const publicKeyJWK = this.parsedKey.toJSON(); 266 Object.assign(jwk, publicKeyJWK); 267 268 return jwk; 269 //#endregion 270 } 271 272 /** 273 * Converts JSON value into current object 274 * @param json JSON object 275 */ 276 public fromJSON(json: any): void { 277 if ("kty" in json) { 278 switch (json.kty.toUpperCase()) { 279 case "EC": 280 this.parsedKey = new ECPrivateKey({ json }); 281 282 this.privateKeyAlgorithm = new AlgorithmIdentifier({ 283 algorithmId: "1.2.840.10045.2.1", 284 algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve }) 285 }); 286 break; 287 case "RSA": 288 this.parsedKey = new RSAPrivateKey({ json }); 289 290 this.privateKeyAlgorithm = new AlgorithmIdentifier({ 291 algorithmId: "1.2.840.113549.1.1.1", 292 algorithmParams: new asn1js.Null() 293 }); 294 break; 295 default: 296 throw new Error(`Invalid value for "kty" parameter: ${json.kty}`); 297 } 298 299 this.privateKey = new asn1js.OctetString({ valueHex: this.parsedKey.toSchema().toBER(false) }); 300 } 301 } 302 303 }