richard pushed to branch tor-browser-115.3.0esr-13.0-1 at The Tor Project / Applications / Tor Browser

Commits:

2 changed files:

Changes:

  • toolkit/components/tor-launcher/TorControlPort.sys.mjs
    ... ... @@ -272,6 +272,11 @@ class AsyncSocket {
    272 272
      *
    
    273 273
      * @typedef {string} NodeFingerprint
    
    274 274
      */
    
    275
    +/**
    
    276
    + * @typedef {object} CircuitInfo
    
    277
    + * @property {CircuitID} id
    
    278
    + * @property {NodeFingerprint[]} nodes
    
    279
    + */
    
    275 280
     /**
    
    276 281
      * @typedef {object} Bridge
    
    277 282
      * @property {string} transport The transport of the bridge, or vanilla if not
    
    ... ... @@ -729,12 +734,14 @@ export class TorController {
    729 734
       /**
    
    730 735
        * Ask Tor a list of circuits.
    
    731 736
        *
    
    732
    -   * @returns {string[]} An array with a string for each line
    
    737
    +   * @returns {CircuitInfo[]} An array with a string for each line
    
    733 738
        */
    
    734 739
       async getCircuits() {
    
    735 740
         const circuits = await this.#getInfo("circuit-status");
    
    736
    -    // TODO: Do more parsing once we move the event parsing to this class!
    
    737
    -    return circuits.split(/\r?\n/);
    
    741
    +    return circuits
    
    742
    +      .split(/\r?\n/)
    
    743
    +      .map(this.#parseCircBuilt.bind(this))
    
    744
    +      .filter(circ => circ);
    
    738 745
       }
    
    739 746
     
    
    740 747
       // Configuration
    
    ... ... @@ -1022,25 +1029,15 @@ export class TorController {
    1022 1029
             this.#eventHandler.onBootstrapStatus(status);
    
    1023 1030
             break;
    
    1024 1031
           case "CIRC":
    
    1025
    -        const builtEvent =
    
    1026
    -          /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
    
    1027
    -            data.groups.data
    
    1028
    -          );
    
    1032
    +        const maybeCircuit = this.#parseCircBuilt(data.groups.data);
    
    1029 1033
             const closedEvent = /^(?<ID>[a-zA-Z0-9]{1,16})\sCLOSED/.exec(
    
    1030 1034
               data.groups.data
    
    1031 1035
             );
    
    1032
    -        if (builtEvent) {
    
    1033
    -          const fp = /\$([0-9a-fA-F]{40})/g;
    
    1034
    -          const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
    
    1035
    -            g[1].toUpperCase()
    
    1036
    +        if (maybeCircuit) {
    
    1037
    +          this.#eventHandler.onCircuitBuilt(
    
    1038
    +            maybeCircuit.id,
    
    1039
    +            maybeCircuit.nodes
    
    1036 1040
               );
    
    1037
    -          // In some cases, we might already receive SOCKS credentials in the
    
    1038
    -          // line. However, this might be a problem with onion services: we get
    
    1039
    -          // also a 4-hop circuit that we likely do not want to show to the
    
    1040
    -          // user, especially because it is used only temporarily, and it would
    
    1041
    -          // need a technical explaination.
    
    1042
    -          // const credentials = this.#parseCredentials(data.groups.data);
    
    1043
    -          this.#eventHandler.onCircuitBuilt(builtEvent.groups.ID, nodes);
    
    1044 1041
             } else if (closedEvent) {
    
    1045 1042
               this.#eventHandler.onCircuitClosed(closedEvent.groups.ID);
    
    1046 1043
             }
    
    ... ... @@ -1068,7 +1065,7 @@ export class TorController {
    1068 1065
         }
    
    1069 1066
       }
    
    1070 1067
     
    
    1071
    -  // Other helpers
    
    1068
    +  // Parsers
    
    1072 1069
     
    
    1073 1070
       /**
    
    1074 1071
        * Parse a bootstrap status line.
    
    ... ... @@ -1099,15 +1096,32 @@ export class TorController {
    1099 1096
       }
    
    1100 1097
     
    
    1101 1098
       /**
    
    1102
    -   * Throw an exception when value is not a string.
    
    1099
    +   * Parse a CIRC BUILT event or a GETINFO circuit-status.
    
    1103 1100
        *
    
    1104
    -   * @param {any} value The value to check
    
    1105
    -   * @param {string} name The name of the `value` argument
    
    1101
    +   * @param {string} line The line to parse
    
    1102
    +   * @returns {CircuitInfo?} The ID and nodes of the circuit, or null if the
    
    1103
    +   * parsing failed.
    
    1106 1104
        */
    
    1107
    -  #expectString(value, name) {
    
    1108
    -    if (typeof value !== "string" && !(value instanceof String)) {
    
    1109
    -      throw new Error(`The ${name} argument is expected to be a string.`);
    
    1105
    +  #parseCircBuilt(line) {
    
    1106
    +    const builtEvent =
    
    1107
    +      /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
    
    1108
    +        line
    
    1109
    +      );
    
    1110
    +    if (!builtEvent) {
    
    1111
    +      return null;
    
    1110 1112
         }
    
    1113
    +    const fp = /\$([0-9a-fA-F]{40})/g;
    
    1114
    +    const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
    
    1115
    +      g[1].toUpperCase()
    
    1116
    +    );
    
    1117
    +    // In some cases, we might already receive SOCKS credentials in the
    
    1118
    +    // line. However, this might be a problem with Onion services: we get
    
    1119
    +    // also a 4-hop circuit that we likely do not want to show to the
    
    1120
    +    // user, especially because it is used only temporarily, and it would
    
    1121
    +    // need a technical explaination.
    
    1122
    +    // So we do not try to extract them for now. Otherwise, we could do
    
    1123
    +    // const credentials = this.#parseCredentials(line);
    
    1124
    +    return { id: builtEvent.groups.ID, nodes };
    
    1111 1125
       }
    
    1112 1126
     
    
    1113 1127
       /**
    
    ... ... @@ -1146,6 +1160,20 @@ export class TorController {
    1146 1160
           )
    
    1147 1161
         );
    
    1148 1162
       }
    
    1163
    +
    
    1164
    +  // Other helpers
    
    1165
    +
    
    1166
    +  /**
    
    1167
    +   * Throw an exception when value is not a string.
    
    1168
    +   *
    
    1169
    +   * @param {any} value The value to check
    
    1170
    +   * @param {string} name The name of the `value` argument
    
    1171
    +   */
    
    1172
    +  #expectString(value, name) {
    
    1173
    +    if (typeof value !== "string" && !(value instanceof String)) {
    
    1174
    +      throw new Error(`The ${name} argument is expected to be a string.`);
    
    1175
    +    }
    
    1176
    +  }
    
    1149 1177
     }
    
    1150 1178
     
    
    1151 1179
     /**
    

  • toolkit/components/tor-launcher/TorProvider.sys.mjs
    ... ... @@ -137,7 +137,7 @@ export class TorProvider {
    137 137
        * built before the new identity but not yet used. If we cleaned the map, we
    
    138 138
        * risked of not having the data about it.
    
    139 139
        *
    
    140
    -   * @type {Map<CircuitID, NodeFingerprint[]>}
    
    140
    +   * @type {Map<CircuitID, Promise<NodeFingerprint[]>>}
    
    141 141
        */
    
    142 142
       #circuits = new Map();
    
    143 143
       /**
    
    ... ... @@ -204,6 +204,11 @@ export class TorProvider {
    204 204
     
    
    205 205
         logger.debug(`Notifying ${TorProviderTopics.ProcessIsReady}`);
    
    206 206
         Services.obs.notifyObservers(null, TorProviderTopics.ProcessIsReady);
    
    207
    +
    
    208
    +    // If we are using an external Tor daemon, we might need to fetch circuits
    
    209
    +    // already, in case streams use them. Do not await because we do not want to
    
    210
    +    // block the intialization on this (it should not fail anyway...).
    
    211
    +    this.#fetchCircuits();
    
    207 212
       }
    
    208 213
     
    
    209 214
       /**
    
    ... ... @@ -799,6 +804,16 @@ export class TorProvider {
    799 804
         return crypto.getRandomValues(new Uint8Array(kPasswordLen));
    
    800 805
       }
    
    801 806
     
    
    807
    +  /**
    
    808
    +   * Ask Tor the circuits it already knows to populate our circuit map with the
    
    809
    +   * circuits that were already open before we started listening for events.
    
    810
    +   */
    
    811
    +  async #fetchCircuits() {
    
    812
    +    for (const { id, nodes } of await this.#controller.getCircuits()) {
    
    813
    +      this.onCircuitBuilt(id, nodes);
    
    814
    +    }
    
    815
    +  }
    
    816
    +
    
    802 817
       // Notification handlers
    
    803 818
     
    
    804 819
       /**
    
    ... ... @@ -983,18 +998,35 @@ export class TorProvider {
    983 998
        * @param {CircuitID} circuitId The ID of the circuit used by the stream
    
    984 999
        * @param {string} username The SOCKS username
    
    985 1000
        * @param {string} password The SOCKS password
    
    986
    -   * @returns
    
    987 1001
        */
    
    988
    -  onStreamSucceeded(streamId, circuitId, username, password) {
    
    1002
    +  async onStreamSucceeded(streamId, circuitId, username, password) {
    
    989 1003
         if (!username || !password) {
    
    990 1004
           return;
    
    991 1005
         }
    
    992 1006
         logger.debug("Stream succeeded event", username, password, circuitId);
    
    993
    -    const circuit = this.#circuits.get(circuitId);
    
    1007
    +    let circuit = this.#circuits.get(circuitId);
    
    994 1008
         if (!circuit) {
    
    995
    -      logger.error(
    
    996
    -        "Seen a STREAM SUCCEEDED with an unknown circuit. Not notifying observers."
    
    997
    -      );
    
    1009
    +      circuit = new Promise((resolve, reject) => {
    
    1010
    +        this.#controlConnection.getCircuits().then(circuits => {
    
    1011
    +          for (const { id, nodes } of circuits) {
    
    1012
    +            if (id === circuitId) {
    
    1013
    +              resolve(nodes);
    
    1014
    +              return;
    
    1015
    +            }
    
    1016
    +            // Opportunistically collect circuits, since we are iterating them.
    
    1017
    +            this.#circuits.set(id, nodes);
    
    1018
    +          }
    
    1019
    +          logger.error(
    
    1020
    +            `Seen a STREAM SUCCEEDED with circuit ${circuitId}, but Tor did not send information about it.`
    
    1021
    +          );
    
    1022
    +          reject();
    
    1023
    +        });
    
    1024
    +      });
    
    1025
    +      this.#circuits.set(circuitId, circuit);
    
    1026
    +    }
    
    1027
    +    try {
    
    1028
    +      circuit = await circuit;
    
    1029
    +    } catch {
    
    998 1030
           return;
    
    999 1031
         }
    
    1000 1032
         Services.obs.notifyObservers(