PublicKeyInfo.ts (8533B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import * as common from "./common"; 4 import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; 5 import { ECPublicKey } from "./ECPublicKey"; 6 import { RSAPublicKey } from "./RSAPublicKey"; 7 import * as Schema from "./Schema"; 8 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 9 import { AsnError } from "./errors"; 10 import { EMPTY_STRING } from "./constants"; 11 12 const ALGORITHM = "algorithm"; 13 const SUBJECT_PUBLIC_KEY = "subjectPublicKey"; 14 const CLEAR_PROPS = [ALGORITHM, SUBJECT_PUBLIC_KEY]; 15 16 export interface IPublicKeyInfo { 17 /** 18 * Algorithm identifier 19 */ 20 algorithm: AlgorithmIdentifier; 21 /** 22 * Subject public key value 23 */ 24 subjectPublicKey: asn1js.BitString; 25 /** 26 * Parsed public key value 27 */ 28 parsedKey?: ECPublicKey | RSAPublicKey | undefined; 29 } 30 export type PublicKeyInfoParameters = PkiObjectParameters & Partial<IPublicKeyInfo> & { json?: JsonWebKey; }; 31 32 export interface PublicKeyInfoJson { 33 algorithm: AlgorithmIdentifierJson; 34 subjectPublicKey: asn1js.BitStringJson; 35 } 36 37 export type PublicKeyInfoSchema = Schema.SchemaParameters<{ 38 algorithm?: AlgorithmIdentifierSchema; 39 subjectPublicKey?: string; 40 }>; 41 42 /** 43 * Represents the PublicKeyInfo structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) 44 */ 45 export class PublicKeyInfo extends PkiObject implements IPublicKeyInfo { 46 47 public static override CLASS_NAME = "PublicKeyInfo"; 48 49 public algorithm!: AlgorithmIdentifier; 50 public subjectPublicKey!: asn1js.BitString; 51 private _parsedKey?: ECPublicKey | RSAPublicKey | null; 52 public get parsedKey(): ECPublicKey | RSAPublicKey | undefined { 53 if (this._parsedKey === undefined) { 54 switch (this.algorithm.algorithmId) { 55 // TODO Use fabric 56 case "1.2.840.10045.2.1": // ECDSA 57 if ("algorithmParams" in this.algorithm) { 58 if (this.algorithm.algorithmParams.constructor.blockName() === asn1js.ObjectIdentifier.blockName()) { 59 try { 60 this._parsedKey = new ECPublicKey({ 61 namedCurve: this.algorithm.algorithmParams.valueBlock.toString(), 62 schema: this.subjectPublicKey.valueBlock.valueHexView 63 }); 64 } 65 catch { 66 // nothing 67 } // Could be a problems during recognition of internal public key data here. Let's ignore them. 68 } 69 } 70 break; 71 case "1.2.840.113549.1.1.1": // RSA 72 { 73 const publicKeyASN1 = asn1js.fromBER(this.subjectPublicKey.valueBlock.valueHexView); 74 if (publicKeyASN1.offset !== -1) { 75 try { 76 this._parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result }); 77 } 78 catch { 79 // nothing 80 } // Could be a problems during recognition of internal public key data here. Let's ignore them. 81 } 82 } 83 break; 84 default: 85 } 86 87 this._parsedKey ||= null; 88 } 89 90 return this._parsedKey || undefined; 91 } 92 public set parsedKey(value: ECPublicKey | RSAPublicKey | undefined) { 93 this._parsedKey = value; 94 } 95 96 /** 97 * Initializes a new instance of the {@link PublicKeyInfo} class 98 * @param parameters Initialization parameters 99 */ 100 constructor(parameters: PublicKeyInfoParameters = {}) { 101 super(); 102 103 this.algorithm = pvutils.getParametersValue(parameters, ALGORITHM, PublicKeyInfo.defaultValues(ALGORITHM)); 104 this.subjectPublicKey = pvutils.getParametersValue(parameters, SUBJECT_PUBLIC_KEY, PublicKeyInfo.defaultValues(SUBJECT_PUBLIC_KEY)); 105 const parsedKey = pvutils.getParametersValue(parameters, "parsedKey", null); 106 if (parsedKey) { 107 this.parsedKey = parsedKey; 108 } 109 110 if (parameters.json) { 111 this.fromJSON(parameters.json); 112 } 113 114 if (parameters.schema) { 115 this.fromSchema(parameters.schema); 116 } 117 } 118 119 /** 120 * Returns default values for all class members 121 * @param memberName String name for a class member 122 * @returns Default value 123 */ 124 public static override defaultValues(memberName: typeof ALGORITHM): AlgorithmIdentifier; 125 public static override defaultValues(memberName: typeof SUBJECT_PUBLIC_KEY): asn1js.BitString; 126 public static override defaultValues(memberName: string): any { 127 switch (memberName) { 128 case ALGORITHM: 129 return new AlgorithmIdentifier(); 130 case SUBJECT_PUBLIC_KEY: 131 return new asn1js.BitString(); 132 default: 133 return super.defaultValues(memberName); 134 } 135 } 136 137 /** 138 * @inheritdoc 139 * @asn ASN.1 schema 140 * ```asn 141 * SubjectPublicKeyInfo ::= Sequence { 142 * algorithm AlgorithmIdentifier, 143 * subjectPublicKey BIT STRING } 144 *``` 145 */ 146 public static override schema(parameters: PublicKeyInfoSchema = {}): Schema.SchemaType { 147 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 148 149 return (new asn1js.Sequence({ 150 name: (names.blockName || EMPTY_STRING), 151 value: [ 152 AlgorithmIdentifier.schema(names.algorithm || {}), 153 new asn1js.BitString({ name: (names.subjectPublicKey || EMPTY_STRING) }) 154 ] 155 })); 156 } 157 158 public fromSchema(schema: Schema.SchemaType): void { 159 // Clear input data first 160 pvutils.clearProps(schema, CLEAR_PROPS); 161 162 // Check the schema is valid 163 const asn1 = asn1js.compareSchema(schema, 164 schema, 165 PublicKeyInfo.schema({ 166 names: { 167 algorithm: { 168 names: { 169 blockName: ALGORITHM 170 } 171 }, 172 subjectPublicKey: SUBJECT_PUBLIC_KEY 173 } 174 }) 175 ); 176 AsnError.assertSchema(asn1, this.className); 177 178 //#region Get internal properties from parsed schema 179 this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm }); 180 this.subjectPublicKey = asn1.result.subjectPublicKey; 181 //#endregion 182 } 183 184 public toSchema(): asn1js.Sequence { 185 return (new asn1js.Sequence({ 186 value: [ 187 this.algorithm.toSchema(), 188 this.subjectPublicKey 189 ] 190 })); 191 } 192 193 public toJSON(): PublicKeyInfoJson | JsonWebKey { 194 //#region Return common value in case we do not have enough info fo making JWK 195 if (!this.parsedKey) { 196 return { 197 algorithm: this.algorithm.toJSON(), 198 subjectPublicKey: this.subjectPublicKey.toJSON(), 199 }; 200 } 201 //#endregion 202 203 //#region Making JWK 204 const jwk: JsonWebKey = {}; 205 206 switch (this.algorithm.algorithmId) { 207 case "1.2.840.10045.2.1": // ECDSA 208 jwk.kty = "EC"; 209 break; 210 case "1.2.840.113549.1.1.1": // RSA 211 jwk.kty = "RSA"; 212 break; 213 default: 214 } 215 216 const publicKeyJWK = this.parsedKey.toJSON(); 217 Object.assign(jwk, publicKeyJWK); 218 219 return jwk; 220 //#endregion 221 } 222 223 /** 224 * Converts JSON value into current object 225 * @param json JSON object 226 */ 227 public fromJSON(json: any): void { 228 if ("kty" in json) { 229 switch (json.kty.toUpperCase()) { 230 case "EC": 231 this.parsedKey = new ECPublicKey({ json }); 232 233 this.algorithm = new AlgorithmIdentifier({ 234 algorithmId: "1.2.840.10045.2.1", 235 algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve }) 236 }); 237 break; 238 case "RSA": 239 this.parsedKey = new RSAPublicKey({ json }); 240 241 this.algorithm = new AlgorithmIdentifier({ 242 algorithmId: "1.2.840.113549.1.1.1", 243 algorithmParams: new asn1js.Null() 244 }); 245 break; 246 default: 247 throw new Error(`Invalid value for "kty" parameter: ${json.kty}`); 248 } 249 250 this.subjectPublicKey = new asn1js.BitString({ valueHex: this.parsedKey.toSchema().toBER(false) }); 251 } 252 } 253 254 public async importKey(publicKey: CryptoKey, crypto = common.getCrypto(true)): Promise<void> { 255 try { 256 if (!publicKey) { 257 throw new Error("Need to provide publicKey input parameter"); 258 } 259 260 const exportedKey = await crypto.exportKey("spki", publicKey); 261 const asn1 = asn1js.fromBER(exportedKey); 262 try { 263 this.fromSchema(asn1.result); 264 } 265 catch { 266 throw new Error("Error during initializing object from schema"); 267 } 268 } catch (e) { 269 const message = e instanceof Error ? e.message : `${e}`; 270 throw new Error(`Error during exporting public key: ${message}`); 271 } 272 } 273 274 }