Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  FxAccountsPairingChannel.sys.mjs   Sprache: unbekannt

 
/*!
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * The following bundle is from an external repository at github.com/mozilla/fxa-pairing-channel,
 * it implements a shared library for two javascript environments to create an encrypted and authenticated
 * communication channel by sharing a secret key and by relaying messages through a websocket server.
 * 
 * It is used by the Firefox Accounts pairing flow, with one side of the channel being web
 * content from https://accounts.firefox.com and the other side of the channel being chrome native code.
 * 
 * This uses the event-target-shim node library published under the MIT license:
 * https://github.com/mysticatea/event-target-shim/blob/master/LICENSE
 * 
 * Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:c8ec3119920b4ffa833b, Chunkhash:378a5f51445e7aa7630e.
 * 
 */

// This header provides a little bit of plumbing to use `FxAccountsPairingChannel`
// from Firefox browser code, hence the presence of these privileged browser APIs.
// If you're trying to use this from ordinary web content you're in for a bad time.

import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

// We cannot use WebSocket from chrome code without a window,
// see https://bugzilla.mozilla.org/show_bug.cgi?id=784686
const browser = Services.appShell.createWindowlessBrowser(true);
const {WebSocket} = browser.document.ownerGlobal;

export var FxAccountsPairingChannel =
/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
/******/
/******/  // The require function
/******/  function __webpack_require__(moduleId) {
/******/
/******/   // Check if module is in cache
/******/   if(installedModules[moduleId]) {
/******/    return installedModules[moduleId].exports;
/******/   }
/******/   // Create a new module (and put it into the cache)
/******/   var module = installedModules[moduleId] = {
/******/    i: moduleId,
/******/    l: false,
/******/    exports: {}
/******/   };
/******/
/******/   // Execute the module function
/******/   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/   // Flag the module as loaded
/******/   module.l = true;
/******/
/******/   // Return the exports of the module
/******/   return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__)
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/   if(!__webpack_require__.o(exports, name)) {
/******/    Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/   }
/******/  };
/******/
/******/  // define __esModule on exports
/******/  __webpack_require__.r = function(exports) {
/******/   if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/   }
/******/   Object.defineProperty(exports, '__esModule', { value: true });
/******/  };
/******/
/******/  // create a fake namespace object
/******/  // mode & 1: value is a module id, require it
/******/  // mode & 2: merge all properties of value into the ns
/******/  // mode & 4: return value when already ns object
/******/  // mode & 8|1: behave like require
/******/  __webpack_require__.t = function(value, mode) {
/******/   if(mode & 1) value = __webpack_require__(value);
/******/   if(mode & 8) return value;
/******/   if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/   var ns = Object.create(null);
/******/   __webpack_require__.r(ns);
/******/   Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/   if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/   return ns;
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules
/******/  __webpack_require__.n = function(module) {
/******/   var getter = module && module.__esModule ?
/******/    function getDefault() { return module['default']; } :
/******/    function getModuleExports() { return module; };
/******/   __webpack_require__.d(getter, 'a', getter);
/******/   return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__
/******/  __webpack_require__.p = "";
/******/
/******/
/******/  // Load entry module and return exports
/******/  return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, "PairingChannel", function() { return /* binding */ src_PairingChannel; });
__webpack_require__.d(__webpack_exports__, "base64urlToBytes", function() { return /* reexport */ base64urlToBytes; });
__webpack_require__.d(__webpack_exports__, "bytesToBase64url", function() { return /* reexport */ bytesToBase64url; });
__webpack_require__.d(__webpack_exports__, "bytesToHex", function() { return /* reexport */ bytesToHex; });
__webpack_require__.d(__webpack_exports__, "bytesToUtf8", function() { return /* reexport */ bytesToUtf8; });
__webpack_require__.d(__webpack_exports__, "hexToBytes", function() { return /* reexport */ hexToBytes; });
__webpack_require__.d(__webpack_exports__, "TLSCloseNotify", function() { return /* reexport */ TLSCloseNotify; });
__webpack_require__.d(__webpack_exports__, "TLSError", function() { return /* reexport */ TLSError; });
__webpack_require__.d(__webpack_exports__, "utf8ToBytes", function() { return /* reexport */ utf8ToBytes; });
__webpack_require__.d(__webpack_exports__, "_internals", function() { return /* binding */ _internals; });

// CONCATENATED MODULE: ./src/alerts.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable sorting/sort-object-props */
const ALERT_LEVEL = {
  WARNING: 1,
  FATAL: 2
};

const ALERT_DESCRIPTION = {
  CLOSE_NOTIFY: 0,
  UNEXPECTED_MESSAGE: 10,
  BAD_RECORD_MAC: 20,
  RECORD_OVERFLOW: 22,
  HANDSHAKE_FAILURE: 40,
  ILLEGAL_PARAMETER: 47,
  DECODE_ERROR: 50,
  DECRYPT_ERROR: 51,
  PROTOCOL_VERSION: 70,
  INTERNAL_ERROR: 80,
  MISSING_EXTENSION: 109,
  UNSUPPORTED_EXTENSION: 110,
  UNKNOWN_PSK_IDENTITY: 115,
  NO_APPLICATION_PROTOCOL: 120,
};
/* eslint-enable sorting/sort-object-props */

function alertTypeToName(type) {
  for (const name in ALERT_DESCRIPTION) {
    if (ALERT_DESCRIPTION[name] === type) {
      return `${name} (${type})`;
    }
  }
  return `UNKNOWN (${type})`;
}

class TLSAlert extends Error {
  constructor(description, level) {
    super(`TLS Alert: ${alertTypeToName(description)}`);
    this.description = description;
    this.level = level;
  }

  static fromBytes(bytes) {
    if (bytes.byteLength !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    switch (bytes[1]) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        if (bytes[0] !== ALERT_LEVEL.WARNING) {
          // Close notifications should be fatal.
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        return new TLSCloseNotify();
      default:
        return new TLSError(bytes[1]);
    }
  }

  toBytes() {
    return new Uint8Array([this.level, this.description]);
  }
}

class TLSCloseNotify extends TLSAlert {
  constructor() {
    super(ALERT_DESCRIPTION.CLOSE_NOTIFY, ALERT_LEVEL.WARNING);
  }
}

class TLSError extends TLSAlert {
  constructor(description = ALERT_DESCRIPTION.INTERNAL_ERROR) {
    super(description, ALERT_LEVEL.FATAL);
  }
}

// CONCATENATED MODULE: ./src/utils.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */



//
// Various low-level utility functions.
//
// These are mostly conveniences for working with Uint8Arrays as
// the primitive "bytes" type.
//

const UTF8_ENCODER = new TextEncoder();
const UTF8_DECODER = new TextDecoder();

function noop() {}

function assert(cond, msg) {
  if (! cond) {
    throw new Error('assert failed: ' + msg);
  }
}

function assertIsBytes(value, msg = 'value must be a Uint8Array') {
  // Using `value instanceof Uint8Array` seems to fail in Firefox chrome code
  // for inscrutable reasons, so we do a less direct check.
  assert(ArrayBuffer.isView(value), msg);
  assert(value.BYTES_PER_ELEMENT === 1, msg);
  return value;
}

const EMPTY = new Uint8Array(0);

function zeros(n) {
  return new Uint8Array(n);
}

function arrayToBytes(value) {
  return new Uint8Array(value);
}

function bytesToHex(bytes) {
  return Array.prototype.map.call(bytes, byte => {
    let s = byte.toString(16);
    if (s.length === 1) {
      s = '0' + s;
    }
    return s;
  }).join('');
}

function hexToBytes(hexstr) {
  assert(hexstr.length % 2 === 0, 'hexstr.length must be even');
  return new Uint8Array(Array.prototype.map.call(hexstr, (c, n) => {
    if (n % 2 === 1) {
      return hexstr[n - 1] + c;
    } else {
      return '';
    }
  }).filter(s => {
    return !! s;
  }).map(s => {
    return parseInt(s, 16);
  }));
}

function bytesToUtf8(bytes) {
  return UTF8_DECODER.decode(bytes);
}

function utf8ToBytes(str) {
  return UTF8_ENCODER.encode(str);
}

function bytesToBase64url(bytes) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to encode secrets?
  const charCodes = String.fromCharCode.apply(String, bytes);
  return btoa(charCodes).replace(/\+/g, '-').replace(/\//g, '_');
}

function base64urlToBytes(str) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to decode secrets?
  str = atob(str.replace(/-/g, '+').replace(/_/g, '/'));
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    bytes[i] = str.charCodeAt(i);
  }
  return bytes;
}

function bytesAreEqual(v1, v2) {
  assertIsBytes(v1);
  assertIsBytes(v2);
  if (v1.length !== v2.length) {
    return false;
  }
  for (let i = 0; i < v1.length; i++) {
    if (v1[i] !== v2[i]) {
      return false;
    }
  }
  return true;
}

// The `BufferReader` and `BufferWriter` classes are helpers for dealing with the
// binary struct format that's used for various TLS message.  Think of them as a
// buffer with a pointer to the "current position" and a bunch of helper methods
// to read/write structured data and advance said pointer.

class utils_BufferWithPointer {
  constructor(buf) {
    this._buffer = buf;
    this._dataview = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
    this._pos = 0;
  }

  length() {
    return this._buffer.byteLength;
  }

  tell() {
    return this._pos;
  }

  seek(pos) {
    if (pos < 0) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    if (pos > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    this._pos = pos;
  }

  incr(offset) {
    this.seek(this._pos + offset);
  }
}

// The `BufferReader` class helps you read structured data from a byte array.
// It offers methods for reading both primitive values, and the variable-length
// vector structures defined in https://tools.ietf.org/html/rfc8446#section-3.4.
//
// Such vectors are represented as a length followed by the concatenated
// bytes of each item, and the size of the length field is determined by
// the maximum allowed number of bytes in the vector.  For example
// to read a vector that may contain up to 65535 bytes, use `readVector16`.
//
// To read a variable-length vector of between 1 and 100 uint16 values,
// defined in the RFC like this:
//
//    uint16 items<2..200>;
//
// You would do something like this:
//
//    const items = []
//    buf.readVector8(buf => {
//      items.push(buf.readUint16())
//    })
//
// The various `read` will throw `DECODE_ERROR` if you attempt to read path
// the end of the buffer, or past the end of a variable-length list.
//
class utils_BufferReader extends utils_BufferWithPointer {

  hasMoreBytes() {
    return this.tell() < this.length();
  }

  readBytes(length) {
    // This avoids copies by returning a view onto the existing buffer.
    const start = this._buffer.byteOffset + this.tell();
    this.incr(length);
    return new Uint8Array(this._buffer.buffer, start, length);
  }

  _rangeErrorToAlert(cb) {
    try {
      return cb(this);
    } catch (err) {
      if (err instanceof RangeError) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      throw err;
    }
  }

  readUint8() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint8(this._pos);
      this.incr(1);
      return n;
    });
  }

  readUint16() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint16(this._pos);
      this.incr(2);
      return n;
    });
  }

  readUint24() {
    return this._rangeErrorToAlert(() => {
      let n = this._dataview.getUint16(this._pos);
      n = (n << 8) | this._dataview.getUint8(this._pos + 2);
      this.incr(3);
      return n;
    });
  }

  readUint32() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint32(this._pos);
      this.incr(4);
      return n;
    });
  }

  _readVector(length, cb) {
    const contentsBuf = new utils_BufferReader(this.readBytes(length));
    const expectedEnd = this.tell();
    // Keep calling the callback until we've consumed the expected number of bytes.
    let n = 0;
    while (contentsBuf.hasMoreBytes()) {
      const prevPos = contentsBuf.tell();
      cb(contentsBuf, n);
      // Check that the callback made forward progress, otherwise we'll infinite loop.
      if (contentsBuf.tell() <= prevPos) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      n += 1;
    }
    // Check that the callback correctly consumed the vector's entire contents.
    if (this.tell() !== expectedEnd) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
  }

  readVector8(cb) {
    const length = this.readUint8();
    return this._readVector(length, cb);
  }

  readVector16(cb) {
    const length = this.readUint16();
    return this._readVector(length, cb);
  }

  readVector24(cb) {
    const length = this.readUint24();
    return this._readVector(length, cb);
  }

  readVectorBytes8() {
    return this.readBytes(this.readUint8());
  }

  readVectorBytes16() {
    return this.readBytes(this.readUint16());
  }

  readVectorBytes24() {
    return this.readBytes(this.readUint24());
  }
}


class utils_BufferWriter extends utils_BufferWithPointer {
  constructor(size = 1024) {
    super(new Uint8Array(size));
  }

  _maybeGrow(n) {
    const curSize = this._buffer.byteLength;
    const newPos = this._pos + n;
    const shortfall = newPos - curSize;
    if (shortfall > 0) {
      // Classic grow-by-doubling, up to 4kB max increment.
      // This formula was not arrived at by any particular science.
      const incr = Math.min(curSize, 4 * 1024);
      const newbuf = new Uint8Array(curSize + Math.ceil(shortfall / incr) * incr);
      newbuf.set(this._buffer, 0);
      this._buffer = newbuf;
      this._dataview = new DataView(newbuf.buffer, newbuf.byteOffset, newbuf.byteLength);
    }
  }

  slice(start = 0, end = this.tell()) {
    if (end < 0) {
      end = this.tell() + end;
    }
    if (start < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    return this._buffer.slice(start, end);
  }

  flush() {
    const slice = this.slice();
    this.seek(0);
    return slice;
  }

  writeBytes(data) {
    this._maybeGrow(data.byteLength);
    this._buffer.set(data, this.tell());
    this.incr(data.byteLength);
  }

  writeUint8(n) {
    this._maybeGrow(1);
    this._dataview.setUint8(this._pos, n);
    this.incr(1);
  }

  writeUint16(n) {
    this._maybeGrow(2);
    this._dataview.setUint16(this._pos, n);
    this.incr(2);
  }

  writeUint24(n) {
    this._maybeGrow(3);
    this._dataview.setUint16(this._pos, n >> 8);
    this._dataview.setUint8(this._pos + 2, n & 0xFF);
    this.incr(3);
  }

  writeUint32(n) {
    this._maybeGrow(4);
    this._dataview.setUint32(this._pos, n);
    this.incr(4);
  }

  // These are helpers for writing the variable-length vector structure
  // defined in https://tools.ietf.org/html/rfc8446#section-3.4.
  //
  // Such vectors are represented as a length followed by the concatenated
  // bytes of each item, and the size of the length field is determined by
  // the maximum allowed size of the vector.  For example to write a vector
  // that may contain up to 65535 bytes, use `writeVector16`.
  //
  // To write a variable-length vector of between 1 and 100 uint16 values,
  // defined in the RFC like this:
  //
  //    uint16 items<2..200>;
  //
  // You would do something like this:
  //
  //    buf.writeVector8(buf => {
  //      for (let item of items) {
  //          buf.writeUint16(item)
  //      }
  //    })
  //
  // The helper will automatically take care of writing the appropriate
  // length field once the callback completes.

  _writeVector(maxLength, writeLength, cb) {
    // Initially, write the length field as zero.
    const lengthPos = this.tell();
    writeLength(0);
    // Call the callback to write the vector items.
    const bodyPos = this.tell();
    cb(this);
    const length = this.tell() - bodyPos;
    if (length >= maxLength) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Backfill the actual length field.
    this.seek(lengthPos);
    writeLength(length);
    this.incr(length);
    return length;
  }

  writeVector8(cb) {
    return this._writeVector(Math.pow(2, 8), len => this.writeUint8(len), cb);
  }

  writeVector16(cb) {
    return this._writeVector(Math.pow(2, 16), len => this.writeUint16(len), cb);
  }

  writeVector24(cb) {
    return this._writeVector(Math.pow(2, 24), len => this.writeUint24(len), cb);
  }

  writeVectorBytes8(bytes) {
    return this.writeVector8(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes16(bytes) {
    return this.writeVector16(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes24(bytes) {
    return this.writeVector24(buf => {
      buf.writeBytes(bytes);
    });
  }
}

// CONCATENATED MODULE: ./src/crypto.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Low-level crypto primitives.
//
// This file implements the AEAD encrypt/decrypt and hashing routines
// for the TLS_AES_128_GCM_SHA256 ciphersuite. They are (thankfully)
// fairly light-weight wrappers around what's available via the WebCrypto
// API.
//




const AEAD_SIZE_INFLATION = 16;
const KEY_LENGTH = 16;
const IV_LENGTH = 12;
const HASH_LENGTH = 32;

async function prepareKey(key, mode) {
  return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]);
}

async function encrypt(key, iv, plaintext, additionalData) {
  const ciphertext = await crypto.subtle.encrypt({
    additionalData,
    iv,
    name: 'AES-GCM',
    tagLength: AEAD_SIZE_INFLATION * 8
  }, key, plaintext);
  return new Uint8Array(ciphertext);
}

async function decrypt(key, iv, ciphertext, additionalData) {
  try {
    const plaintext = await crypto.subtle.decrypt({
      additionalData,
      iv,
      name: 'AES-GCM',
      tagLength: AEAD_SIZE_INFLATION * 8
    }, key, ciphertext);
    return new Uint8Array(plaintext);
  } catch (err) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.BAD_RECORD_MAC);
  }
}

async function hash(message) {
  return new Uint8Array(await crypto.subtle.digest({ name: 'SHA-256' }, message));
}

async function hmac(keyBytes, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['sign']);
  const sig = await crypto.subtle.sign({ name: 'HMAC' }, key, message);
  return new Uint8Array(sig);
}

async function verifyHmac(keyBytes, signature, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['verify']);
  if (! (await crypto.subtle.verify({ name: 'HMAC' }, key, signature, message))) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.DECRYPT_ERROR);
  }
}

async function hkdfExtract(salt, ikm) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.2
  return await hmac(salt, ikm);
}

async function hkdfExpand(prk, info, length) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.3
  const N = Math.ceil(length / HASH_LENGTH);
  if (N <= 0) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  if (N >= 255) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  const input = new utils_BufferWriter();
  const output = new utils_BufferWriter();
  let T = new Uint8Array(0);
  for (let i = 1; i <= N; i++) {
    input.writeBytes(T);
    input.writeBytes(info);
    input.writeUint8(i);
    T = await hmac(prk, input.flush());
    output.writeBytes(T);
  }
  return output.slice(0, length);
}

async function hkdfExpandLabel(secret, label, context, length) {
  //  struct {
  //    uint16 length = Length;
  //    opaque label < 7..255 > = "tls13 " + Label;
  //    opaque context < 0..255 > = Context;
  //  } HkdfLabel;
  const hkdfLabel = new utils_BufferWriter();
  hkdfLabel.writeUint16(length);
  hkdfLabel.writeVectorBytes8(utf8ToBytes('tls13 ' + label));
  hkdfLabel.writeVectorBytes8(context);
  return hkdfExpand(secret, hkdfLabel.flush(), length);
}

async function getRandomBytes(size) {
  const bytes = new Uint8Array(size);
  crypto.getRandomValues(bytes);
  return bytes;
}

// CONCATENATED MODULE: ./src/extensions.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Extension parsing.
//
// This file contains some helpers for reading/writing the various kinds
// of Extension that might appear in a HandshakeMessage.
//
// "Extensions" are how TLS signals the presence of particular bits of optional
// functionality in the protocol. Lots of parts of TLS1.3 that don't seem like
// they're optional are implemented in terms of an extension, IIUC because that's
// what was needed for a clean deployment in amongst earlier versions of the protocol.
//





/* eslint-disable sorting/sort-object-props */
const EXTENSION_TYPE = {
  PRE_SHARED_KEY: 41,
  SUPPORTED_VERSIONS: 43,
  PSK_KEY_EXCHANGE_MODES: 45,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of extensions,
// which are all uniformly formatted as:
//
//   struct {
//     ExtensionType extension_type;
//     opaque extension_data<0..2^16-1>;
//   } Extension;
//
// Extensions always appear inside of a handshake message,
// and their internal structure may differ based on the
// type of that message.

class extensions_Extension {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static read(messageType, buf) {
    const type = buf.readUint16();
    let ext = {
      TYPE_TAG: type,
    };
    buf.readVector16(buf => {
      switch (type) {
        case EXTENSION_TYPE.PRE_SHARED_KEY:
          ext = extensions_PreSharedKeyExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.SUPPORTED_VERSIONS:
          ext = extensions_SupportedVersionsExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES:
          ext = extensions_PskKeyExchangeModesExtension._read(messageType, buf);
          break;
        default:
          // Skip over unrecognised extensions.
          buf.incr(buf.length());
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    return ext;
  }

  write(messageType, buf) {
    buf.writeUint16(this.TYPE_TAG);
    buf.writeVector16(buf => {
      this._write(messageType, buf);
    });
  }

  static _read(messageType, buf) {
    throw new Error('not implemented');
  }

  static _write(messageType, buf) {
    throw new Error('not implemented');
  }
}

// The PreSharedKey extension:
//
//  struct {
//    opaque identity<1..2^16-1>;
//    uint32 obfuscated_ticket_age;
//  } PskIdentity;
//  opaque PskBinderEntry<32..255>;
//  struct {
//    PskIdentity identities<7..2^16-1>;
//    PskBinderEntry binders<33..2^16-1>;
//  } OfferedPsks;
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello: OfferedPsks;
//      case server_hello: uint16 selected_identity;
//    };
//  } PreSharedKeyExtension;

class extensions_PreSharedKeyExtension extends extensions_Extension {
  constructor(identities, binders, selectedIdentity) {
    super();
    this.identities = identities;
    this.binders = binders;
    this.selectedIdentity = selectedIdentity;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PRE_SHARED_KEY;
  }

  static _read(messageType, buf) {
    let identities = null, binders = null, selectedIdentity = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        identities = []; binders = [];
        buf.readVector16(buf => {
          const identity = buf.readVectorBytes16();
          buf.readBytes(4); // Skip over the ticket age.
          identities.push(identity);
        });
        buf.readVector16(buf => {
          const binder = buf.readVectorBytes8();
          if (binder.byteLength < HASH_LENGTH) {
            throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
          }
          binders.push(binder);
        });
        if (identities.length !== binders.length) {
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedIdentity = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(identities, binders, selectedIdentity);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector16(buf => {
          this.identities.forEach(pskId => {
            buf.writeVectorBytes16(pskId);
            buf.writeUint32(0); // Zero for "tag age" field.
          });
        });
        buf.writeVector16(buf => {
          this.binders.forEach(pskBinder => {
            buf.writeVectorBytes8(pskBinder);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedIdentity);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


// The SupportedVersions extension:
//
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello:
//        ProtocolVersion versions < 2..254 >;
//      case server_hello:
//        ProtocolVersion selected_version;
//    };
//  } SupportedVersions;

class extensions_SupportedVersionsExtension extends extensions_Extension {
  constructor(versions, selectedVersion) {
    super();
    this.versions = versions;
    this.selectedVersion = selectedVersion;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.SUPPORTED_VERSIONS;
  }

  static _read(messageType, buf) {
    let versions = null, selectedVersion = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        versions = [];
        buf.readVector8(buf => {
          versions.push(buf.readUint16());
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedVersion = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(versions, selectedVersion);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.versions.forEach(version => {
            buf.writeUint16(version);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedVersion);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


class extensions_PskKeyExchangeModesExtension extends extensions_Extension {
  constructor(modes) {
    super();
    this.modes = modes;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES;
  }

  static _read(messageType, buf) {
    const modes = [];
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.readVector8(buf => {
          modes.push(buf.readUint8());
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(modes);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.modes.forEach(mode => {
            buf.writeUint8(mode);
          });
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}

// CONCATENATED MODULE: ./src/constants.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const VERSION_TLS_1_0 = 0x0301;
const VERSION_TLS_1_2 = 0x0303;
const VERSION_TLS_1_3 = 0x0304;
const TLS_AES_128_GCM_SHA256 = 0x1301;
const PSK_MODE_KE = 0;

// CONCATENATED MODULE: ./src/messages.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Message parsing.
//
// Herein we have code for reading and writing the various Handshake
// messages involved in the TLS protocol.
//







/* eslint-disable sorting/sort-object-props */
const HANDSHAKE_TYPE = {
  CLIENT_HELLO: 1,
  SERVER_HELLO: 2,
  NEW_SESSION_TICKET: 4,
  ENCRYPTED_EXTENSIONS: 8,
  FINISHED: 20,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of handshake messages,
// which are all uniformly formatted as:
//
//  struct {
//    HandshakeType msg_type;    /* handshake type */
//    uint24 length;             /* bytes in message */
//    select(Handshake.msg_type) {
//        ... type specific cases here ...
//    };
//  } Handshake;

class messages_HandshakeMessage {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static fromBytes(bytes) {
    // Each handshake message has a type and length prefix, per
    // https://tools.ietf.org/html/rfc8446#appendix-B.3
    const buf = new utils_BufferReader(bytes);
    const msg = this.read(buf);
    if (buf.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return msg;
  }

  toBytes() {
    const buf = new utils_BufferWriter();
    this.write(buf);
    return buf.flush();
  }

  static read(buf) {
    const type = buf.readUint8();
    let msg = null;
    buf.readVector24(buf => {
      switch (type) {
        case HANDSHAKE_TYPE.CLIENT_HELLO:
          msg = messages_ClientHello._read(buf);
          break;
        case HANDSHAKE_TYPE.SERVER_HELLO:
          msg = messages_ServerHello._read(buf);
          break;
        case HANDSHAKE_TYPE.NEW_SESSION_TICKET:
          msg = messages_NewSessionTicket._read(buf);
          break;
        case HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS:
          msg = EncryptedExtensions._read(buf);
          break;
        case HANDSHAKE_TYPE.FINISHED:
          msg = messages_Finished._read(buf);
          break;
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    if (msg === null) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    return msg;
  }

  write(buf) {
    buf.writeUint8(this.TYPE_TAG);
    buf.writeVector24(buf => {
      this._write(buf);
    });
  }

  static _read(buf) {
    throw new Error('not implemented');
  }

  _write(buf) {
    throw new Error('not implemented');
  }

  // Some little helpers for reading a list of extensions,
  // which is uniformly represented as:
  //
  //   Extension extensions<8..2^16-1>;
  //
  // Recognized extensions are returned as a Map from extension type
  // to extension data object, with a special `lastSeenExtension`
  // property to make it easy to check which one came last.

  static _readExtensions(messageType, buf) {
    const extensions = new Map();
    buf.readVector16(buf => {
      const ext = extensions_Extension.read(messageType, buf);
      if (extensions.has(ext.TYPE_TAG)) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      extensions.set(ext.TYPE_TAG, ext);
      extensions.lastSeenExtension = ext.TYPE_TAG;
    });
    return extensions;
  }

  _writeExtensions(buf, extensions) {
    buf.writeVector16(buf => {
      extensions.forEach(ext => {
        ext.write(this.TYPE_TAG, buf);
      });
    });
  }
}


// The ClientHello message:
//
// struct {
//   ProtocolVersion legacy_version = 0x0303;
//   Random random;
//   opaque legacy_session_id<0..32>;
//   CipherSuite cipher_suites<2..2^16-2>;
//   opaque legacy_compression_methods<1..2^8-1>;
//   Extension extensions<8..2^16-1>;
// } ClientHello;

class messages_ClientHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.CLIENT_HELLO;
  }

  static _read(buf) {
    // The legacy_version field may indicate an earlier version of TLS
    // for backwards compatibility, but must not predate TLS 1.0!
    if (buf.readUint16() < VERSION_TLS_1_0) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // The random bytes provided by the peer.
    const random = buf.readBytes(32);
    // Read legacy_session_id, so the server can echo it.
    const sessionId = buf.readVectorBytes8();
    // We only support a single ciphersuite, but the peer may offer several.
    // Scan the list to confirm that the one we want is present.
    let found = false;
    buf.readVector16(buf => {
      const cipherSuite = buf.readUint16();
      if (cipherSuite === TLS_AES_128_GCM_SHA256) {
        found = true;
      }
    });
    if (! found) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    // legacy_compression_methods must be a single zero byte for TLS1.3 ClientHellos.
    // It can be non-zero in previous versions of TLS, but we're not going to
    // make a successful handshake with such versions, so better to just bail out now.
    const legacyCompressionMethods = buf.readVectorBytes8();
    if (legacyCompressionMethods.byteLength !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    if (legacyCompressionMethods[0] !== 0x00) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Read and check the extensions.
    const extensions = this._readExtensions(HANDSHAKE_TYPE.CLIENT_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).versions.indexOf(VERSION_TLS_1_3) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // Was the PreSharedKey extension the last one?
    if (extensions.has(EXTENSION_TYPE.PRE_SHARED_KEY)) {
      if (extensions.lastSeenExtension !== EXTENSION_TYPE.PRE_SHARED_KEY) {
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
      }
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeVector16(buf => {
      buf.writeUint16(TLS_AES_128_GCM_SHA256);
    });
    // A single zero byte for legacy_compression_methods
    buf.writeVectorBytes8(new Uint8Array(1));
    this._writeExtensions(buf, this.extensions);
  }
}


// The ServerHello message:
//
//  struct {
//      ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
//      Random random;
//      opaque legacy_session_id_echo<0..32>;
//      CipherSuite cipher_suite;
//      uint8 legacy_compression_method = 0;
//      Extension extensions < 6..2 ^ 16 - 1 >;
//  } ServerHello;

class messages_ServerHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.SERVER_HELLO;
  }

  static _read(buf) {
    // Fixed value for legacy_version.
    if (buf.readUint16() !== VERSION_TLS_1_2) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Random bytes from the server.
    const random = buf.readBytes(32);
    // It should have echoed our vector for legacy_session_id.
    const sessionId = buf.readVectorBytes8();
    // It should have selected our single offered ciphersuite.
    if (buf.readUint16() !== TLS_AES_128_GCM_SHA256) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // legacy_compression_method must be zero.
    if (buf.readUint8() !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.SERVER_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).selectedVersion !== VERSION_TLS_1_3) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeUint16(TLS_AES_128_GCM_SHA256);
    // A single zero byte for legacy_compression_method
    buf.writeUint8(0);
    this._writeExtensions(buf, this.extensions);
  }
}


// The EncryptedExtensions message:
//
//  struct {
//    Extension extensions < 0..2 ^ 16 - 1 >;
//  } EncryptedExtensions;
//
// We don't actually send any EncryptedExtensions,
// but still have to send an empty message.

class EncryptedExtensions extends messages_HandshakeMessage {
  constructor(extensions) {
    super();
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS;
  }

  static _read(buf) {
    const extensions = this._readExtensions(HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS, buf);
    return new this(extensions);
  }

  _write(buf) {
    this._writeExtensions(buf, this.extensions);
  }
}


// The Finished message:
//
// struct {
//   opaque verify_data[Hash.length];
// } Finished;

class messages_Finished extends messages_HandshakeMessage {

  constructor(verifyData) {
    super();
    this.verifyData = verifyData;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.FINISHED;
  }

  static _read(buf) {
    const verifyData = buf.readBytes(HASH_LENGTH);
    return new this(verifyData);
  }

  _write(buf) {
    buf.writeBytes(this.verifyData);
  }
}


// The NewSessionTicket message:
//
//   struct {
//    uint32 ticket_lifetime;
//    uint32 ticket_age_add;
//    opaque ticket_nonce < 0..255 >;
//    opaque ticket < 1..2 ^ 16 - 1 >;
//    Extension extensions < 0..2 ^ 16 - 2 >;
//  } NewSessionTicket;
//
// We don't actually make use of these, but we need to be able
// to accept them and do basic validation.

class messages_NewSessionTicket extends messages_HandshakeMessage {
  constructor(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions) {
    super();
    this.ticketLifetime = ticketLifetime;
    this.ticketAgeAdd = ticketAgeAdd;
    this.ticketNonce = ticketNonce;
    this.ticket = ticket;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.NEW_SESSION_TICKET;
  }

  static _read(buf) {
    const ticketLifetime = buf.readUint32();
    const ticketAgeAdd = buf.readUint32();
    const ticketNonce = buf.readVectorBytes8();
    const ticket = buf.readVectorBytes16();
    if (ticket.byteLength < 1) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.NEW_SESSION_TICKET, buf);
    return new this(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions);
  }

  _write(buf) {
    buf.writeUint32(this.ticketLifetime);
    buf.writeUint32(this.ticketAgeAdd);
    buf.writeVectorBytes8(this.ticketNonce);
    buf.writeVectorBytes16(this.ticket);
    this._writeExtensions(buf, this.extensions);
  }
}

// CONCATENATED MODULE: ./src/states.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */








//
// State-machine for TLS Handshake Management.
//
// Internally, we manage the TLS connection by explicitly modelling the
// client and server state-machines from RFC8446.  You can think of
// these `State` objects as little plugins for the `Connection` class
// that provide different behaviours of `send` and `receive` depending
// on the state of the connection.
//

class states_State {

  constructor(conn) {
    this.conn = conn;
  }

  async initialize() {
    // By default, nothing to do when entering the state.
  }

  async sendApplicationData(bytes) {
    // By default, assume we're not ready to send yet and the caller
    // should be blocking on the connection promise before reaching here.
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }

  async recvApplicationData(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvHandshakeMessage(msg) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvAlertMessage(alert) {
    switch (alert.description) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        this.conn._closeForRecv(alert);
        throw alert;
      default:
        return await this.handleErrorAndRethrow(alert);
    }
  }

  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async handleErrorAndRethrow(err) {
    let alert = err;
    if (! (alert instanceof TLSAlert)) {
      alert = new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Try to send error alert to the peer, but we may not
    // be able to if the outgoing connection was already closed.
    try {
      await this.conn._sendAlertMessage(alert);
    } catch (_) { }
    await this.conn._transition(ERROR, err);
    throw err;
  }

  async close() {
    const alert = new TLSCloseNotify();
    await this.conn._sendAlertMessage(alert);
    this.conn._closeForSend(alert);
  }

}

// A special "guard" state to prevent us from using
// an improperly-initialized Connection.

class UNINITIALIZED extends states_State {
  async initialize() {
    throw new Error('uninitialized state');
  }
  async sendApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvHandshakeMessage(msg) {
    throw new Error('uninitialized state');
  }
  async recvChangeCipherSpec(bytes) {
    throw new Error('uninitialized state');
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw new Error('uninitialized state');
  }
}

// A special "error" state for when something goes wrong.
// This state never transitions to another state, effectively
// terminating the connection.

class ERROR extends states_State {
  async initialize(err) {
    this.error = err;
    this.conn._setConnectionFailure(err);
    // Unceremoniously shut down the record layer on error.
    this.conn._recordlayer.setSendError(err);
    this.conn._recordlayer.setRecvError(err);
  }
  async sendApplicationData(bytes) {
    throw this.error;
  }
  async recvApplicationData(bytes) {
    throw this.error;
  }
  async recvHandshakeMessage(msg) {
    throw this.error;
  }
  async recvAlertMessage(err) {
    throw this.error;
  }
  async recvChangeCipherSpec(bytes) {
    throw this.error;
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw this.error;
  }
}

// The "connected" state, for when the handshake is complete
// and we're ready to send application-level data.
// The logic for this is largely symmetric between client and server.

class states_CONNECTED extends states_State {
  async initialize() {
    this.conn._setConnectionSuccess();
  }
  async sendApplicationData(bytes) {
    await this.conn._sendApplicationData(bytes);
  }
  async recvApplicationData(bytes) {
    return bytes;
  }
  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }
}

// A base class for states that occur in the middle of the handshake
// (that is, between ClientHello and Finished).  These states may receive
// CHANGE_CIPHER_SPEC records for b/w compat reasons, which must contain
// exactly a single 0x01 byte and must otherwise be ignored.

class states_MidHandshakeState extends states_State {
  async recvChangeCipherSpec(bytes) {
    if (this.conn._hasSeenChangeCipherSpec) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (bytes.byteLength !== 1 || bytes[0] !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    this.conn._hasSeenChangeCipherSpec = true;
  }
}

// These states implement (part of) the client state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.1
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * send ClientHello
//   * receive ServerHello
//   * receive EncryptedExtensions
//   * receive server Finished
//   * send client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_CLIENT_START extends states_State {
  async initialize() {
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addPSK(this.conn.psk);
    // Construct a ClientHello message with our single PSK.
    // We can't know the PSK binder value yet, so we initially write zeros.
    const clientHello = new messages_ClientHello(
      // Client random salt.
      await getRandomBytes(32),
      // Random legacy_session_id; we *could* send an empty string here,
      // but sending a random one makes it easier to be compatible with
      // the data emitted by tlslite-ng for test-case generation.
      await getRandomBytes(32),
      [
        new extensions_SupportedVersionsExtension([VERSION_TLS_1_3]),
        new extensions_PskKeyExchangeModesExtension([PSK_MODE_KE]),
        new extensions_PreSharedKeyExtension([this.conn.pskId], [zeros(HASH_LENGTH)]),
      ],
    );
    const buf = new utils_BufferWriter();
    clientHello.write(buf);
    // Now that we know what the ClientHello looks like,
    // go back and calculate the appropriate PSK binder value.
    // We only support a single PSK, so the length of the binders field is the
    // length of the hash plus one for rendering it as a variable-length byte array,
    // plus two for rendering the variable-length list of PSK binders.
    const PSK_BINDERS_SIZE = HASH_LENGTH + 1 + 2;
    const truncatedTranscript = buf.slice(0, buf.tell() - PSK_BINDERS_SIZE);
    const pskBinder = await keyschedule.calculateFinishedMAC(keyschedule.extBinderKey, truncatedTranscript);
    buf.incr(-HASH_LENGTH);
    buf.writeBytes(pskBinder);
    await this.conn._sendHandshakeMessageBytes(buf.flush());
    await this.conn._transition(states_CLIENT_WAIT_SH, clientHello.sessionId);
  }
}

class states_CLIENT_WAIT_SH extends states_State {
  async initialize(sessionId) {
    this._sessionId = sessionId;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ServerHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (! bytesAreEqual(msg.sessionId, this._sessionId)) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    if (! pskExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    // We expect only the SUPPORTED_VERSIONS and PRE_SHARED_KEY extensions.
    if (msg.extensions.size !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    if (pskExt.selectedIdentity !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    await this.conn._keyschedule.addECDHE(null);
    await this.conn._setSendKey(this.conn._keyschedule.clientHandshakeTrafficSecret);
    await this.conn._setRecvKey(this.conn._keyschedule.serverHandshakeTrafficSecret);
    await this.conn._transition(states_CLIENT_WAIT_EE);
  }
}

class states_CLIENT_WAIT_EE extends states_MidHandshakeState {
  async recvHandshakeMessage(msg) {
    // We don't make use of any encrypted extensions, but we still
    // have to wait for the server to send the (empty) list of them.
    if (! (msg instanceof EncryptedExtensions)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // We do not support any EncryptedExtensions.
    if (msg.extensions.size !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    const keyschedule = this.conn._keyschedule;
    const serverFinishedTranscript = keyschedule.getTranscript();
    await this.conn._transition(states_CLIENT_WAIT_FINISHED, serverFinishedTranscript);
  }
}

class states_CLIENT_WAIT_FINISHED extends states_State {
  async initialize(serverFinishedTranscript) {
    this._serverFinishedTranscript = serverFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // Verify server Finished MAC.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(keyschedule.serverHandshakeTrafficSecret, msg.verifyData, this._serverFinishedTranscript);
    // Send our own Finished message in return.
    // This must be encrypted with the handshake traffic key,
    // but must not appear in the transcript used to calculate the application keys.
    const clientFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.clientHandshakeTrafficSecret);
    await keyschedule.finalize();
    await this.conn._sendHandshakeMessage(new messages_Finished(clientFinishedMAC));
    await this.conn._setSendKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._setRecvKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_CLIENT_CONNECTED);
  }
}

class states_CLIENT_CONNECTED extends states_CONNECTED {
  async recvHandshakeMessage(msg) {
    // A connected client must be prepared to accept NewSessionTicket
    // messages.  We never use them, but other server implementations
    // might send them.
    if (! (msg instanceof messages_NewSessionTicket)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
  }
}

// These states implement (part of) the server state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.2
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * receive ClientHello
//   * send ServerHello
//   * send empty EncryptedExtensions
//   * send server Finished
//   * receive client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_SERVER_START extends states_State {
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ClientHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // In the spec, this is where we select connection parameters, and maybe
    // tell the client to try again if we can't find a compatible set.
    // Since we only support a fixed cipherset, the only thing to "negotiate"
    // is whether they provided an acceptable PSK.
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    const pskModesExt = msg.extensions.get(EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES);
    if (! pskExt || ! pskModesExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (pskModesExt.modes.indexOf(PSK_MODE_KE) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    const pskIndex = pskExt.identities.findIndex(pskId => bytesAreEqual(pskId, this.conn.pskId));
    if (pskIndex === -1) {
      throw new TLSError(ALERT_DESCRIPTION.UNKNOWN_PSK_IDENTITY);
    }
    await this.conn._keyschedule.addPSK(this.conn.psk);
    // Validate the PSK binder.
    const keyschedule = this.conn._keyschedule;
    const transcript = keyschedule.getTranscript();
    // Calculate size occupied by the PSK binders.
    let pskBindersSize = 2; // Vector16 representation overhead.
    for (const binder of pskExt.binders) {
      pskBindersSize += binder.byteLength + 1; // Vector8 representation overhead.
    }
    await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, pskExt.binders[pskIndex], transcript.slice(0, -pskBindersSize));
    await this.conn._transition(states_SERVER_NEGOTIATED, msg.sessionId, pskIndex);
  }
}

class states_SERVER_NEGOTIATED extends states_MidHandshakeState {
  async initialize(sessionId, pskIndex) {
    await this.conn._sendHandshakeMessage(new messages_ServerHello(
      // Server random
      await getRandomBytes(32),
      sessionId,
      [
        new extensions_SupportedVersionsExtension(null, VERSION_TLS_1_3),
        new extensions_PreSharedKeyExtension(null, null, pskIndex),
      ]
    ));
    // If the client sent a non-empty sessionId, the server *must* send a change-cipher-spec for b/w compat.
    if (sessionId.byteLength > 0) {
      await this.conn._sendChangeCipherSpec();
    }
    // We can now transition to the encrypted part of the handshake.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addECDHE(null);
    await this.conn._setSendKey(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._setRecvKey(keyschedule.clientHandshakeTrafficSecret);
    // Send an empty EncryptedExtensions message.
    await this.conn._sendHandshakeMessage(new EncryptedExtensions([]));
    // Send the Finished message.
    const serverFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._sendHandshakeMessage(new messages_Finished(serverFinishedMAC));
    // We can now *send* using the application traffic key,
    // but have to wait to receive the client Finished before receiving under that key.
    // We need to remember the handshake state from before the client Finished
    // in order to successfully verify the client Finished.
    const clientFinishedTranscript = await keyschedule.getTranscript();
    const clientHandshakeTrafficSecret = keyschedule.clientHandshakeTrafficSecret;
    await keyschedule.finalize();
    await this.conn._setSendKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_SERVER_WAIT_FINISHED, clientHandshakeTrafficSecret, clientFinishedTranscript);
  }
}

class states_SERVER_WAIT_FINISHED extends states_MidHandshakeState {
  async initialize(clientHandshakeTrafficSecret, clientFinishedTranscript) {
    this._clientHandshakeTrafficSecret = clientHandshakeTrafficSecret;
    this._clientFinishedTranscript = clientFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(this._clientHandshakeTrafficSecret, msg.verifyData, this._clientFinishedTranscript);
    this._clientHandshakeTrafficSecret = this._clientFinishedTranscript = null;
    await this.conn._setRecvKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._transition(states_CONNECTED);
  }
}

// CONCATENATED MODULE: ./src/keyschedule.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// TLS1.3 Key Schedule.
//
// In this file we implement the "key schedule" from
// https://tools.ietf.org/html/rfc8446#section-7.1, which
// defines how to calculate various keys as the handshake
// state progresses.







// The `KeySchedule` class progresses through three stages corresponding
// to the three phases of the TLS1.3 key schedule:
//
//   UNINITIALIZED
//       |
//       | addPSK()
//       v
//   EARLY_SECRET
//       |
//       | addECDHE()
//       v
//   HANDSHAKE_SECRET
//       |
//       | finalize()
//       v
//   MASTER_SECRET
//
// It will error out if the calling code attempts to add key material
// in the wrong order.

const STAGE_UNINITIALIZED = 0;
const STAGE_EARLY_SECRET = 1;
const STAGE_HANDSHAKE_SECRET = 2;
const STAGE_MASTER_SECRET = 3;

class keyschedule_KeySchedule {
  constructor() {
    this.stage = STAGE_UNINITIALIZED;
    // WebCrypto doesn't support a rolling hash construct, so we have to
    // keep the entire message transcript in memory.
    this.transcript = new utils_BufferWriter();
    // This tracks the main secret from with other keys are derived at each stage.
    this.secret = null;
    // And these are all the various keys we'll derive as the handshake progresses.
    this.extBinderKey = null;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.clientApplicationTrafficSecret = null;
    this.serverApplicationTrafficSecret = null;
  }

  async addPSK(psk) {
    // Use the selected PSK (if any) to calculate the "early secret".
    if (psk === null) {
      psk = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_UNINITIALIZED) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_EARLY_SECRET;
    this.secret = await hkdfExtract(zeros(HASH_LENGTH), psk);
    this.extBinderKey = await this.deriveSecret('ext binder', EMPTY);
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async addECDHE(ecdhe) {
    // Mix in the ECDHE output (if any) to calculate the "handshake secret".
    if (ecdhe === null) {
      ecdhe = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_EARLY_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_HANDSHAKE_SECRET;
    this.extBinderKey = null;
    this.secret = await hkdfExtract(this.secret, ecdhe);
    this.clientHandshakeTrafficSecret = await this.deriveSecret('c hs traffic');
    this.serverHandshakeTrafficSecret = await this.deriveSecret('s hs traffic');
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async finalize() {
    if (this.stage !== STAGE_HANDSHAKE_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_MASTER_SECRET;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.secret = await hkdfExtract(this.secret, zeros(HASH_LENGTH));
    this.clientApplicationTrafficSecret = await this.deriveSecret('c ap traffic');
    this.serverApplicationTrafficSecret = await this.deriveSecret('s ap traffic');
    this.secret = null;
  }

  addToTranscript(bytes) {
    this.transcript.writeBytes(bytes);
  }

  getTranscript() {
    return this.transcript.slice();
  }

  async deriveSecret(label, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    return await hkdfExpandLabel(this.secret, label, await hash(transcript), HASH_LENGTH);
  }

  async calculateFinishedMAC(baseKey, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    return await hmac(finishedKey, await hash(transcript));
  }

  async verifyFinishedMAC(baseKey, mac, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    await verifyHmac(finishedKey, mac, await hash(transcript));
  }
}

// CONCATENATED MODULE: ./src/recordlayer.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// This file implements the "record layer" for TLS1.3, as defined in
// https://tools.ietf.org/html/rfc8446#section-5.
//
// The record layer is responsible for encrypting/decrypting bytes to be
// sent over the wire, including stateful management of sequence numbers
// for the incoming and outgoing stream.
//
// The main interface is the RecordLayer class, which takes a callback function
// sending data and can be used like so:
//
//    rl = new RecordLayer(async function send_encrypted_data(data) {
//      // application-specific sending logic here.
//    });
//
//    // Records are sent and received in plaintext by default,
//    // until you specify the key to use.
//    await rl.setSendKey(key)
//
//    // Send some data by specifying the record type and the bytes.
//    // Where allowed by the record type, it will be buffered until
//    // explicitly flushed, and then sent by calling the callback.
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for a handshake message>)
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for another handshake message>)
//    await rl.flush()
//
//    // Separate keys are used for sending and receiving.
//    rl.setRecvKey(key);
//
//    // When data is received, push it into the RecordLayer
//    // and pass a callback that will be called with a [type, bytes]
//    // pair for each message parsed from the data.
//    rl.recv(dataReceivedFromPeer, async (type, bytes) => {
//      switch (type) {
//        case RECORD_TYPE.APPLICATION_DATA:
//          // do something with application data
//        case RECORD_TYPE.HANDSHAKE:
//          // do something with a handshake message
//        default:
//          // etc...
//      }
//    });
//







/* eslint-disable sorting/sort-object-props */
const RECORD_TYPE = {
  CHANGE_CIPHER_SPEC: 20,
  ALERT: 21,
  HANDSHAKE: 22,
  APPLICATION_DATA: 23,
};
/* eslint-enable sorting/sort-object-props */

// Encrypting at most 2^24 records will force us to stay
// below data limits on AES-GCM encryption key use, and also
// means we can accurately represent the sequence number as
// a javascript double.
const MAX_SEQUENCE_NUMBER = Math.pow(2, 24);
const MAX_RECORD_SIZE = Math.pow(2, 14);
const MAX_ENCRYPTED_RECORD_SIZE = MAX_RECORD_SIZE + 256;
const RECORD_HEADER_SIZE = 5;

// These are some helper classes to manage the encryption/decryption state
// for a particular key.

class recordlayer_CipherState {
  constructor(key, iv) {
    this.key = key;
    this.iv = iv;
    this.seqnum = 0;
  }

  static async create(baseKey, mode) {
    // Derive key and iv per https://tools.ietf.org/html/rfc8446#section-7.3
    const key = await prepareKey(await hkdfExpandLabel(baseKey, 'key', EMPTY, KEY_LENGTH), mode);
    const iv = await hkdfExpandLabel(baseKey, 'iv', EMPTY, IV_LENGTH);
    return new this(key, iv);
  }

  nonce() {
    // Ref https://tools.ietf.org/html/rfc8446#section-5.3:
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.49Quellennavigators  Analyse erneut starten  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge