tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }