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


Quelle  PeerConnectionIdp.sys.mjs   Sprache: unbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/* 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 lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  IdpSandbox: "resource://gre/modules/media/IdpSandbox.sys.mjs",
});

/**
 * Creates an IdP helper.
 *
 * @param win (object) the window we are working for
 * @param timeout (int) the timeout in milliseconds
 */
export function PeerConnectionIdp(win, timeout) {
  this._win = win;
  this._timeout = timeout || 5000;

  this.provider = null;
  this._resetAssertion();
}

(function () {
  PeerConnectionIdp._mLinePattern = new RegExp("^m=", "m");
  // attributes are funny, the 'a' is case sensitive, the name isn't
  let pattern = "^a=[iI][dD][eE][nN][tT][iI][tT][yY]:(\\S+)";
  PeerConnectionIdp._identityPattern = new RegExp(pattern, "m");
  pattern = "^a=[fF][iI][nN][gG][eE][rR][pP][rR][iI][nN][tT]:(\\S+) (\\S+)";
  PeerConnectionIdp._fingerprintPattern = new RegExp(pattern, "m");
})();

PeerConnectionIdp.prototype = {
  get enabled() {
    return !!this._idp;
  },

  _resetAssertion() {
    this.assertion = null;
    this.idpLoginUrl = null;
  },

  setIdentityProvider(provider, protocol, usernameHint, peerIdentity) {
    this._resetAssertion();
    this.provider = provider;
    this.protocol = protocol;
    this.username = usernameHint;
    this.peeridentity = peerIdentity;
    if (this._idp) {
      if (this._idp.isSame(provider, protocol)) {
        return; // noop
      }
      this._idp.stop();
    }
    this._idp = new lazy.IdpSandbox(provider, protocol, this._win);
  },

  // start the IdP and do some error fixup
  start() {
    return this._idp.start().catch(e => {
      throw new this._win.DOMException(e.message, "IdpError");
    });
  },

  close() {
    this._resetAssertion();
    this.provider = null;
    this.protocol = null;
    this.username = null;
    this.peeridentity = null;
    if (this._idp) {
      this._idp.stop();
      this._idp = null;
    }
  },

  _getFingerprintsFromSdp(sdp) {
    let fingerprints = {};
    let m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    while (m) {
      fingerprints[m[0]] = { algorithm: m[1], digest: m[2] };
      sdp = sdp.substring(m.index + m[0].length);
      m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    }

    return Object.keys(fingerprints).map(k => fingerprints[k]);
  },

  _isValidAssertion(assertion) {
    return (
      assertion &&
      assertion.idp &&
      typeof assertion.idp.domain === "string" &&
      (!assertion.idp.protocol || typeof assertion.idp.protocol === "string") &&
      typeof assertion.assertion === "string"
    );
  },

  _getSessionLevelEnd(sdp) {
    const match = sdp.match(PeerConnectionIdp._mLinePattern);
    if (!match) {
      return sdp.length;
    }
    return match.index;
  },

  _getIdentityFromSdp(sdp) {
    // a=identity is session level
    let idMatch;
    const index = this._getSessionLevelEnd(sdp);
    const sessionLevel = sdp.substring(0, index);
    idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
    if (!idMatch) {
      return undefined; // undefined === no identity
    }

    let assertion;
    try {
      assertion = JSON.parse(atob(idMatch[1]));
    } catch (e) {
      throw new this._win.DOMException(
        "invalid identity assertion: " + e,
        "InvalidSessionDescriptionError"
      );
    }
    if (!this._isValidAssertion(assertion)) {
      throw new this._win.DOMException(
        "assertion missing idp/idp.domain/assertion",
        "InvalidSessionDescriptionError"
      );
    }
    return assertion;
  },

  /**
   * Verifies the a=identity line the given SDP contains, if any.
   * If the verification succeeds callback is called with the message from the
   * IdP proxy as parameter, else (verification failed OR no a=identity line in
   * SDP at all) null is passed to callback.
   *
   * Note that this only verifies that the SDP is coherent.  We still rely on
   * the fact that the RTCPeerConnection won't connect to a peer if the
   * fingerprint of the certificate they offer doesn't appear in the SDP.
   */
  verifyIdentityFromSDP(sdp, origin) {
    let identity = this._getIdentityFromSdp(sdp);
    let fingerprints = this._getFingerprintsFromSdp(sdp);
    if (!identity || fingerprints.length <= 0) {
      return this._win.Promise.resolve(); // undefined result = no identity
    }

    this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
    return this._verifyIdentity(identity.assertion, fingerprints, origin);
  },

  /**
   * Checks that the name in the identity provided by the IdP is OK.
   *
   * @param name (string) the name to validate
   * @throws if the name isn't valid
   */
  _validateName(name) {
    let error = msg => {
      throw new this._win.DOMException(
        "assertion name error: " + msg,
        "IdpError"
      );
    };

    if (typeof name !== "string") {
      error("name not a string");
    }
    let atIdx = name.indexOf("@");
    if (atIdx <= 0) {
      error("missing authority in name from IdP");
    }

    // no third party assertions... for now
    let tail = name.substring(atIdx + 1);

    // strip the port number, if present
    let provider = this.provider;
    let providerPortIdx = provider.indexOf(":");
    if (providerPortIdx > 0) {
      provider = provider.substring(0, providerPortIdx);
    }
    let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
      Ci.nsIIDNService
    );
    if (idnService.domainToASCII(tail) !== idnService.domainToASCII(provider)) {
      error('name "' + name + '" doesn\'t match IdP: "' + this.provider + '"');
    }
  },

  /**
   * Check the validation response.  We are very defensive here when handling
   * the message from the IdP proxy.  That way, broken IdPs aren't likely to
   * cause catastrophic damage.
   */
  _checkValidation(validation, sdpFingerprints) {
    let error = msg => {
      throw new this._win.DOMException(
        "IdP validation error: " + msg,
        "IdpError"
      );
    };

    if (!this.provider) {
      error("IdP closed");
    }

    if (
      typeof validation !== "object" ||
      typeof validation.contents !== "string" ||
      typeof validation.identity !== "string"
    ) {
      error("no payload in validation response");
    }

    let fingerprints;
    try {
      fingerprints = JSON.parse(validation.contents).fingerprint;
    } catch (e) {
      error("invalid JSON");
    }

    let isFingerprint = f =>
      typeof f.digest === "string" && typeof f.algorithm === "string";
    if (!Array.isArray(fingerprints) || !fingerprints.every(isFingerprint)) {
      error(
        "fingerprints must be an array of objects" +
          " with digest and algorithm attributes"
      );
    }

    // everything in `innerSet` is found in `outerSet`
    let isSubsetOf = (outerSet, innerSet, comparator) => {
      return innerSet.every(i => {
        return outerSet.some(o => comparator(i, o));
      });
    };
    let compareFingerprints = (a, b) => {
      return a.digest === b.digest && a.algorithm === b.algorithm;
    };
    if (!isSubsetOf(fingerprints, sdpFingerprints, compareFingerprints)) {
      error("the fingerprints must be covered by the assertion");
    }
    this._validateName(validation.identity);
    return validation;
  },

  /**
   * Asks the IdP proxy to verify an identity assertion.
   */
  _verifyIdentity(assertion, fingerprints, origin) {
    let p = this.start()
      .then(idp =>
        this._wrapCrossCompartmentPromise(
          idp.validateAssertion(assertion, origin)
        )
      )
      .then(validation => this._checkValidation(validation, fingerprints));

    return this._applyTimeout(p);
  },

  /**
   * Enriches the given SDP with an `a=identity` line.  getIdentityAssertion()
   * must have already run successfully, otherwise this does nothing to the sdp.
   */
  addIdentityAttribute(sdp) {
    if (!this.assertion) {
      return sdp;
    }

    const index = this._getSessionLevelEnd(sdp);
    return (
      sdp.substring(0, index) +
      "a=identity:" +
      this.assertion +
      "\r\n" +
      sdp.substring(index)
    );
  },

  /**
   * Asks the IdP proxy for an identity assertion.  Don't call this unless you
   * have checked .enabled, or you really like exceptions.  Also, don't call
   * this when another call is still running, because it's not certain which
   * call will finish first and the final state will be similarly uncertain.
   */
  getIdentityAssertion(fingerprint, origin) {
    if (!this.enabled) {
      throw new this._win.DOMException(
        "no IdP set, call setIdentityProvider() to set one",
        "InvalidStateError"
      );
    }

    let [algorithm, digest] = fingerprint.split(" ", 2);
    let content = {
      fingerprint: [
        {
          algorithm,
          digest,
        },
      ],
    };

    this._resetAssertion();
    let p = this.start()
      .then(idp => {
        let options = {
          protocol: this.protocol,
          usernameHint: this.username,
          peerIdentity: this.peeridentity,
        };
        return this._wrapCrossCompartmentPromise(
          idp.generateAssertion(JSON.stringify(content), origin, options)
        );
      })
      .then(assertion => {
        if (!this._isValidAssertion(assertion)) {
          throw new this._win.DOMException(
            "IdP generated invalid assertion",
            "IdpError"
          );
        }
        // save the base64+JSON assertion, since that is all that is used
        this.assertion = btoa(JSON.stringify(assertion));
        return this.assertion;
      });

    return this._applyTimeout(p);
  },

  /**
   * Promises generated by the sandbox need to be very carefully treated so that
   * they can chain into promises in the `this._win` compartment.  Results need
   * to be cloned across; errors need to be converted.
   */
  _wrapCrossCompartmentPromise(sandboxPromise) {
    return new this._win.Promise((resolve, reject) => {
      sandboxPromise.then(
        result => resolve(Cu.cloneInto(result, this._win)),
        e => {
          let message = "" + (e.message || JSON.stringify(e) || "IdP error");
          if (e.name === "IdpLoginError") {
            if (typeof e.loginUrl === "string") {
              this.idpLoginUrl = e.loginUrl;
            }
            reject(new this._win.DOMException(message, "IdpLoginError"));
          } else {
            reject(new this._win.DOMException(message, "IdpError"));
          }
        }
      );
    });
  },

  /**
   * Wraps a promise, adding a timeout guard on it so that it can't take longer
   * than the specified time.  Returns a promise that rejects if the timeout
   * elapses before `p` resolves.
   */
  _applyTimeout(p) {
    let timeout = new this._win.Promise(r =>
      this._win.setTimeout(r, this._timeout)
    ).then(() => {
      throw new this._win.DOMException("IdP timed out", "IdpError");
    });
    return this._win.Promise.race([timeout, p]);
  },
};

[ Dauer der Verarbeitung: 0.42 Sekunden  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


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