tor-browser

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

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 }