Pier Angelo Vendrame pushed to branch tor-browser-128.4.0esr-14.5-1 at The Tor Project / Applications / Tor Browser

Commits:

3 changed files:

Changes:

  • toolkit/components/lox/Lox.sys.mjs
    ... ... @@ -37,6 +37,7 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
    37 37
       set_panic_hook: "resource://gre/modules/lox_wasm.jsm",
    
    38 38
       invitation_is_trusted: "resource://gre/modules/lox_wasm.jsm",
    
    39 39
       issue_invite: "resource://gre/modules/lox_wasm.jsm",
    
    40
    +  handle_issue_invite: "resource://gre/modules/lox_wasm.jsm",
    
    40 41
       prepare_invite: "resource://gre/modules/lox_wasm.jsm",
    
    41 42
       get_invites_remaining: "resource://gre/modules/lox_wasm.jsm",
    
    42 43
       get_trust_level: "resource://gre/modules/lox_wasm.jsm",
    
    ... ... @@ -91,6 +92,7 @@ const LoxSettingsPrefs = Object.freeze({
    91 92
     export class LoxError extends Error {
    
    92 93
       static BadInvite = "BadInvite";
    
    93 94
       static LoxServerUnreachable = "LoxServerUnreachable";
    
    95
    +  static ErrorResponse = "ErrorResponse";
    
    94 96
     
    
    95 97
       /**
    
    96 98
        * @param {string} message - The error message.
    
    ... ... @@ -408,89 +410,47 @@ class LoxImpl {
    408 410
       }
    
    409 411
     
    
    410 412
       /**
    
    411
    -   * Update Lox credential after Lox key rotation
    
    412
    -   * Do not call directly, use #getPubKeys() instead to start the update only
    
    413
    -   * once
    
    413
    +   * Update Lox credential after Lox key rotation.
    
    414 414
        *
    
    415
    -   * @param {string} prevkeys The public keys we are replacing
    
    415
    +   * Do not call directly, use #getPubKeys() instead to start the update only
    
    416
    +   * once.
    
    416 417
        */
    
    417
    -  async #updatePubkeys(prevkeys) {
    
    418
    -    let pubKeys;
    
    419
    -    try {
    
    420
    -      pubKeys = await this.#makeRequest("pubkeys", []);
    
    421
    -    } catch (error) {
    
    422
    -      lazy.logger.debug("Failed to get pubkeys", error);
    
    423
    -      // Make the next call try again.
    
    424
    -      this.#pubKeyPromise = null;
    
    425
    -      if (!this.#pubKeys) {
    
    426
    -        throw error;
    
    427
    -      }
    
    428
    -      return;
    
    429
    -    }
    
    418
    +  async #updatePubkeys() {
    
    419
    +    let pubKeys = await this.#makeRequest("pubkeys", null);
    
    430 420
         const prevKeys = this.#pubKeys;
    
    431 421
         if (prevKeys !== null) {
    
    432 422
           // check if the lox pubkeys have changed and update the lox
    
    433
    -      // credentials if so
    
    434
    -      let lox_cred_req;
    
    435
    -      try {
    
    436
    -        lox_cred_req = JSON.parse(
    
    437
    -          lazy.check_lox_pubkeys_update(
    
    438
    -            JSON.stringify(pubKeys),
    
    439
    -            prevkeys,
    
    440
    -            this.#getCredentials(this.#activeLoxId)
    
    441
    -          )
    
    442
    -        );
    
    443
    -      } catch (error) {
    
    444
    -        lazy.logger.debug("Check lox pubkey update failed", error);
    
    445
    -        // Make the next call try again.
    
    446
    -        this.#pubKeyPromise = null;
    
    447
    -        return;
    
    448
    -      }
    
    449
    -      if (lox_cred_req.updated) {
    
    423
    +      // credentials if so.
    
    424
    +      //
    
    425
    +      // The UpdateCredOption rust struct serializes to "req" rather than
    
    426
    +      // "request".
    
    427
    +      const { updated, req: request } = JSON.parse(
    
    428
    +        lazy.check_lox_pubkeys_update(
    
    429
    +          pubKeys,
    
    430
    +          prevKeys,
    
    431
    +          this.#getCredentials(this.#activeLoxId)
    
    432
    +        )
    
    433
    +      );
    
    434
    +      if (updated) {
    
    435
    +        // Try update credentials.
    
    436
    +        // NOTE: This should be re-callable if any step fails.
    
    437
    +        // TODO: Verify this.
    
    450 438
             lazy.logger.debug(
    
    451 439
               `Lox pubkey updated, update Lox credential "${this.#activeLoxId}"`
    
    452 440
             );
    
    453
    -        let response;
    
    454
    -        try {
    
    455
    -          // TODO: If this call doesn't succeed due to a networking error, the Lox
    
    456
    -          // credential may be in an unusable state (spent but not updated)
    
    457
    -          // until this request can be completed successfully (and until Lox
    
    458
    -          // is refactored to send repeat responses:
    
    459
    -          // https://gitlab.torproject.org/tpo/anti-censorship/lox/-/issues/74)
    
    460
    -          response = await this.#makeRequest("updatecred", lox_cred_req.req);
    
    461
    -        } catch (error) {
    
    462
    -          lazy.logger.debug("Lox cred update failed.", error);
    
    463
    -          // Make the next call try again.
    
    464
    -          this.#pubKeyPromise = null;
    
    465
    -          return;
    
    466
    -        }
    
    467
    -        if (response.hasOwnProperty("error")) {
    
    468
    -          lazy.logger.error(response.error);
    
    469
    -          this.#pubKeyPromise = null;
    
    470
    -          lazy.logger.debug(
    
    471
    -            `Error response to Lox pubkey update request: "${response.error}", reverting to old pubkeys`
    
    472
    -          );
    
    473
    -          return;
    
    474
    -        }
    
    475
    -        let cred;
    
    476
    -        try {
    
    477
    -          cred = lazy.handle_update_cred(
    
    478
    -            lox_cred_req.req,
    
    479
    -            JSON.stringify(response),
    
    480
    -            pubKeys
    
    481
    -          );
    
    482
    -        } catch (error) {
    
    483
    -          lazy.logger.debug("Unable to handle updated Lox cred", error);
    
    484
    -          // Make the next call try again.
    
    485
    -          this.#pubKeyPromise = null;
    
    486
    -          return;
    
    487
    -        }
    
    441
    +        // TODO: If this call doesn't succeed due to a networking error, the Lox
    
    442
    +        // credential may be in an unusable state (spent but not updated)
    
    443
    +        // until this request can be completed successfully (and until Lox
    
    444
    +        // is refactored to send repeat responses:
    
    445
    +        // https://gitlab.torproject.org/tpo/anti-censorship/lox/-/issues/74)
    
    446
    +        let response = await this.#makeRequest("updatecred", request);
    
    447
    +        let cred = lazy.handle_update_cred(request, response, pubKeys);
    
    488 448
             this.#changeCredentials(this.#activeLoxId, cred);
    
    489 449
           }
    
    490 450
         }
    
    491 451
         // If we arrive here we haven't had other errors before, we can actually
    
    492 452
         // store the new public key.
    
    493
    -    this.#pubKeys = JSON.stringify(pubKeys);
    
    453
    +    this.#pubKeys = pubKeys;
    
    494 454
         this.#store();
    
    495 455
       }
    
    496 456
     
    
    ... ... @@ -498,16 +458,24 @@ class LoxImpl {
    498 458
         // FIXME: We are always refetching #pubKeys, #encTable and #constants once
    
    499 459
         // per session, but they may change more frequently. tor-browser#42502
    
    500 460
         if (this.#pubKeyPromise === null) {
    
    501
    -      this.#pubKeyPromise = this.#updatePubkeys();
    
    461
    +      this.#pubKeyPromise = this.#updatePubkeys().catch(error => {
    
    462
    +        lazy.logger.debug("Failed to update pubKeys", error);
    
    463
    +        // Try again with the next call.
    
    464
    +        this.#pubKeyPromise = null;
    
    465
    +        if (!this.#pubKeys) {
    
    466
    +          // Re-throw if we have no pubKeys value for the caller.
    
    467
    +          throw error;
    
    468
    +        }
    
    469
    +      });
    
    502 470
         }
    
    503 471
         await this.#pubKeyPromise;
    
    504 472
       }
    
    505 473
     
    
    506 474
       async #getEncTable() {
    
    507 475
         if (this.#encTablePromise === null) {
    
    508
    -      this.#encTablePromise = this.#makeRequest("reachability", [])
    
    476
    +      this.#encTablePromise = this.#makeRequest("reachability", null)
    
    509 477
             .then(encTable => {
    
    510
    -          this.#encTable = JSON.stringify(encTable);
    
    478
    +          this.#encTable = encTable;
    
    511 479
               this.#store();
    
    512 480
             })
    
    513 481
             .catch(error => {
    
    ... ... @@ -526,10 +494,10 @@ class LoxImpl {
    526 494
       async #getConstants() {
    
    527 495
         if (this.#constantsPromise === null) {
    
    528 496
           // Try to update first, but if that doesn't work fall back to stored data
    
    529
    -      this.#constantsPromise = this.#makeRequest("constants", [])
    
    497
    +      this.#constantsPromise = this.#makeRequest("constants", null)
    
    530 498
             .then(constants => {
    
    531 499
               const prevValue = this.#constants;
    
    532
    -          this.#constants = JSON.stringify(constants);
    
    500
    +          this.#constants = constants;
    
    533 501
               this.#store();
    
    534 502
               if (prevValue !== this.#constants) {
    
    535 503
                 Services.obs.notifyObservers(null, LoxTopics.UpdateNextUnlock);
    
    ... ... @@ -579,7 +547,6 @@ class LoxImpl {
    579 547
        */
    
    580 548
       async #backgroundTasks() {
    
    581 549
         this.#assertInitialized();
    
    582
    -    let addedEvent = false;
    
    583 550
         // Only run background tasks for the active lox ID.
    
    584 551
         const loxId = this.#activeLoxId;
    
    585 552
         if (!loxId) {
    
    ... ... @@ -591,37 +558,39 @@ class LoxImpl {
    591 558
         // this should catch key rotations (ideally some days) prior to the next
    
    592 559
         // credential update
    
    593 560
         await this.#getPubKeys();
    
    561
    +    let levelup = false;
    
    594 562
         try {
    
    595
    -      const levelup = await this.#attemptUpgrade(loxId);
    
    596
    -      if (levelup) {
    
    597
    -        const level = this.#getLevel(loxId);
    
    598
    -        const newEvent = {
    
    599
    -          type: "levelup",
    
    600
    -          newlevel: level,
    
    601
    -        };
    
    602
    -        this.#events.push(newEvent);
    
    603
    -        this.#store();
    
    604
    -        addedEvent = true;
    
    605
    -      }
    
    606
    -    } catch (err) {
    
    607
    -      lazy.logger.error(err);
    
    563
    +      levelup = await this.#attemptUpgrade(loxId);
    
    564
    +    } catch (error) {
    
    565
    +      lazy.logger.error(error);
    
    608 566
         }
    
    567
    +    if (levelup) {
    
    568
    +      const level = this.#getLevel(loxId);
    
    569
    +      const newEvent = {
    
    570
    +        type: "levelup",
    
    571
    +        newlevel: level,
    
    572
    +      };
    
    573
    +      this.#events.push(newEvent);
    
    574
    +      this.#store();
    
    575
    +    }
    
    576
    +
    
    577
    +    let leveldown = false;
    
    609 578
         try {
    
    610
    -      const leveldown = await this.#blockageMigration(loxId);
    
    611
    -      if (leveldown) {
    
    612
    -        let level = this.#getLevel(loxId);
    
    613
    -        const newEvent = {
    
    614
    -          type: "blockage",
    
    615
    -          newlevel: level,
    
    616
    -        };
    
    617
    -        this.#events.push(newEvent);
    
    618
    -        this.#store();
    
    619
    -        addedEvent = true;
    
    620
    -      }
    
    621
    -    } catch (err) {
    
    622
    -      lazy.logger.error(err);
    
    579
    +      leveldown = await this.#blockageMigration(loxId);
    
    580
    +    } catch (error) {
    
    581
    +      lazy.logger.error(error);
    
    582
    +    }
    
    583
    +    if (leveldown) {
    
    584
    +      let level = this.#getLevel(loxId);
    
    585
    +      const newEvent = {
    
    586
    +        type: "blockage",
    
    587
    +        newlevel: level,
    
    588
    +      };
    
    589
    +      this.#events.push(newEvent);
    
    590
    +      this.#store();
    
    623 591
         }
    
    624
    -    if (addedEvent) {
    
    592
    +
    
    593
    +    if (levelup || leveldown) {
    
    625 594
           Services.obs.notifyObservers(null, LoxTopics.UpdateEvents);
    
    626 595
         }
    
    627 596
       }
    
    ... ... @@ -708,7 +677,7 @@ class LoxImpl {
    708 677
       // to issue open invitations for Lox bridges.
    
    709 678
       async requestOpenInvite() {
    
    710 679
         this.#assertInitialized();
    
    711
    -    let invite = await this.#makeRequest("invite", []);
    
    680
    +    let invite = JSON.parse(await this.#makeRequest("invite", null));
    
    712 681
         lazy.logger.debug(invite);
    
    713 682
         return invite;
    
    714 683
       }
    
    ... ... @@ -725,23 +694,20 @@ class LoxImpl {
    725 694
         // It's fine to get pubkey here without a delay since the user will not have a Lox
    
    726 695
         // credential yet
    
    727 696
         await this.#getPubKeys();
    
    697
    +    // NOTE: We currently only handle "open invites".
    
    698
    +    // "trusted invites" are not yet supported. tor-browser#42974.
    
    728 699
         let request = await lazy.open_invite(JSON.parse(invite).invite);
    
    729
    -    let response = await this.#makeRequest(
    
    730
    -      "openreq",
    
    731
    -      JSON.parse(request).request
    
    732
    -    );
    
    733
    -    lazy.logger.debug("openreq response: ", response);
    
    734
    -    if (response.hasOwnProperty("error")) {
    
    735
    -      throw new LoxError(
    
    736
    -        `Error response to "openreq": ${response.error}`,
    
    737
    -        LoxError.BadInvite
    
    738
    -      );
    
    700
    +    let response;
    
    701
    +    try {
    
    702
    +      response = await this.#makeRequest("openreq", request);
    
    703
    +    } catch (error) {
    
    704
    +      if (error instanceof LoxError && error.code === LoxError.ErrorResponse) {
    
    705
    +        throw new LoxError("Error response to openreq", LoxError.BadInvite);
    
    706
    +      } else {
    
    707
    +        throw error;
    
    708
    +      }
    
    739 709
         }
    
    740
    -    let cred = lazy.handle_new_lox_credential(
    
    741
    -      request,
    
    742
    -      JSON.stringify(response),
    
    743
    -      this.#pubKeys
    
    744
    -    );
    
    710
    +    let cred = lazy.handle_new_lox_credential(request, response, this.#pubKeys);
    
    745 711
         // Generate an id that is not already in the #credentials map.
    
    746 712
         let loxId;
    
    747 713
         do {
    
    ... ... @@ -795,31 +761,32 @@ class LoxImpl {
    795 761
           throw new LoxError(`Cannot generate invites at level ${level}`);
    
    796 762
         }
    
    797 763
         let request = lazy.issue_invite(
    
    798
    -      JSON.stringify(this.#getCredentials(loxId)),
    
    764
    +      this.#getCredentials(loxId),
    
    799 765
           this.#encTable,
    
    800 766
           this.#pubKeys
    
    801 767
         );
    
    802
    -    let response = await this.#makeRequest(
    
    803
    -      "issueinvite",
    
    804
    -      JSON.parse(request).request
    
    805
    -    );
    
    806
    -    if (response.hasOwnProperty("error")) {
    
    807
    -      lazy.logger.error(response.error);
    
    808
    -      throw new LoxError(`Error response to "issueinvite": ${response.error}`);
    
    809
    -    } else {
    
    810
    -      const invite = lazy.prepare_invite(response);
    
    811
    -      this.#invites.push(invite);
    
    812
    -      // cap length of stored invites
    
    813
    -      if (this.#invites.len > 50) {
    
    814
    -        this.#invites.shift();
    
    815
    -      }
    
    816
    -      this.#store();
    
    817
    -      this.#changeCredentials(loxId, response);
    
    818
    -      Services.obs.notifyObservers(null, LoxTopics.NewInvite);
    
    819
    -      // Return a copy.
    
    820
    -      // Right now invite is just a string, but that might change in the future.
    
    821
    -      return structuredClone(invite);
    
    768
    +    let response = await this.#makeRequest("issueinvite", request);
    
    769
    +    // TODO: Do we ever expect handle_issue_invite to fail (beyond
    
    770
    +    // implementation bugs)?
    
    771
    +    // TODO: What happens if #pubkeys for `issue_invite` differs from the value
    
    772
    +    // when calling `handle_issue_invite`? Should we cache the value at the
    
    773
    +    // start of this method?
    
    774
    +    let cred = lazy.handle_issue_invite(request, response, this.#pubKeys);
    
    775
    +
    
    776
    +    // Store the new credentials as a priority.
    
    777
    +    this.#changeCredentials(loxId, cred);
    
    778
    +
    
    779
    +    const invite = lazy.prepare_invite(cred);
    
    780
    +    this.#invites.push(invite);
    
    781
    +    // cap length of stored invites
    
    782
    +    if (this.#invites.len > 50) {
    
    783
    +      this.#invites.shift();
    
    822 784
         }
    
    785
    +    this.#store();
    
    786
    +    Services.obs.notifyObservers(null, LoxTopics.NewInvite);
    
    787
    +    // Return a copy.
    
    788
    +    // Right now invite is just a string, but that might change in the future.
    
    789
    +    return structuredClone(invite);
    
    823 790
       }
    
    824 791
     
    
    825 792
       /**
    
    ... ... @@ -845,15 +812,13 @@ class LoxImpl {
    845 812
           return false;
    
    846 813
         }
    
    847 814
         let response = await this.#makeRequest("checkblockage", request);
    
    848
    -    if (response.hasOwnProperty("error")) {
    
    849
    -      lazy.logger.error(response.error);
    
    850
    -      throw new LoxError(
    
    851
    -        `Error response to "checkblockage": ${response.error}`
    
    852
    -      );
    
    853
    -    }
    
    815
    +    // NOTE: If a later method fails, we should be ok to re-call "checkblockage"
    
    816
    +    // from the Lox authority. So there shouldn't be any adverse side effects to
    
    817
    +    // loosing migrationCred.
    
    818
    +    // TODO: Confirm this is safe to lose.
    
    854 819
         const migrationCred = lazy.handle_check_blockage(
    
    855 820
           this.#getCredentials(loxId),
    
    856
    -      JSON.stringify(response)
    
    821
    +      response
    
    857 822
         );
    
    858 823
         request = lazy.blockage_migration(
    
    859 824
           this.#getCredentials(loxId),
    
    ... ... @@ -861,26 +826,21 @@ class LoxImpl {
    861 826
           this.#pubKeys
    
    862 827
         );
    
    863 828
         response = await this.#makeRequest("blockagemigration", request);
    
    864
    -    if (response.hasOwnProperty("error")) {
    
    865
    -      lazy.logger.error(response.error);
    
    866
    -      throw new LoxError(
    
    867
    -        `Error response to "blockagemigration": ${response.error}`
    
    868
    -      );
    
    869
    -    }
    
    870 829
         const cred = lazy.handle_blockage_migration(
    
    871 830
           this.#getCredentials(loxId),
    
    872
    -      JSON.stringify(response),
    
    831
    +      response,
    
    873 832
           this.#pubKeys
    
    874 833
         );
    
    875 834
         this.#changeCredentials(loxId, cred);
    
    876 835
         return true;
    
    877 836
       }
    
    878 837
     
    
    879
    -  /** Attempts to upgrade the currently saved Lox credential.
    
    880
    -   *  If an upgrade is available, save an event in the event list.
    
    838
    +  /**
    
    839
    +   * Attempts to upgrade the currently saved Lox credential.
    
    840
    +   * If an upgrade is available, save an event in the event list.
    
    881 841
        *
    
    882
    -   *  @param {string} loxId Lox ID
    
    883
    -   *  @returns {boolean} Whether a levelup event occurred.
    
    842
    +   * @param {string} loxId Lox ID
    
    843
    +   * @returns {boolean} Whether the credential was successfully migrated.
    
    884 844
        */
    
    885 845
       async #attemptUpgrade(loxId) {
    
    886 846
         await this.#getEncTable();
    
    ... ... @@ -895,16 +855,18 @@ class LoxImpl {
    895 855
           this.#encTable,
    
    896 856
           this.#pubKeys
    
    897 857
         );
    
    898
    -    const response = await this.#makeRequest("levelup", request);
    
    899
    -    if (response.hasOwnProperty("error")) {
    
    900
    -      lazy.logger.error(response.error);
    
    901
    -      throw new LoxError(`Error response to "levelup": ${response.error}`);
    
    858
    +    let response;
    
    859
    +    try {
    
    860
    +      response = await this.#makeRequest("levelup", request);
    
    861
    +    } catch (error) {
    
    862
    +      if (error instanceof LoxError && error.code === LoxError.ErrorResponse) {
    
    863
    +        // Not an error.
    
    864
    +        lazy.logger.debug("Not ready for level up", error);
    
    865
    +        return false;
    
    866
    +      }
    
    867
    +      throw error;
    
    902 868
         }
    
    903
    -    const cred = lazy.handle_level_up(
    
    904
    -      request,
    
    905
    -      JSON.stringify(response),
    
    906
    -      this.#pubKeys
    
    907
    -    );
    
    869
    +    const cred = lazy.handle_level_up(request, response, this.#pubKeys);
    
    908 870
         this.#changeCredentials(loxId, cred);
    
    909 871
         return true;
    
    910 872
       }
    
    ... ... @@ -922,76 +884,40 @@ class LoxImpl {
    922 884
           this.#getPubKeys();
    
    923 885
           return false;
    
    924 886
         }
    
    925
    -    let request, response;
    
    887
    +    let request;
    
    926 888
         try {
    
    927 889
           request = lazy.trust_promotion(
    
    928 890
             this.#getCredentials(loxId),
    
    929 891
             this.#pubKeys
    
    930 892
           );
    
    931 893
         } catch (err) {
    
    894
    +      // This function is called routinely during the background tasks without
    
    895
    +      // previous checks on whether an upgrade is possible, so it is expected to
    
    896
    +      // fail with a certain frequency. Therefore, do not relay the error to the
    
    897
    +      // caller and just log the message for debugging.
    
    932 898
           lazy.logger.debug("Not ready to upgrade", err);
    
    933 899
           return false;
    
    934 900
         }
    
    935
    -    try {
    
    936
    -      response = await this.#makeRequest(
    
    937
    -        "trustpromo",
    
    938
    -        JSON.parse(request).request
    
    939
    -      );
    
    940
    -    } catch (err) {
    
    941
    -      lazy.logger.error("Failed trust promotion", err);
    
    942
    -      return false;
    
    943
    -    }
    
    944
    -    if (response.hasOwnProperty("error")) {
    
    945
    -      lazy.logger.error("Error response from trustpromo", response.error);
    
    946
    -      return false;
    
    947
    -    }
    
    948
    -    lazy.logger.debug("Got promotion cred", response, request);
    
    949
    -    let promoCred;
    
    950
    -    try {
    
    951
    -      promoCred = lazy.handle_trust_promotion(
    
    952
    -        request,
    
    953
    -        JSON.stringify(response)
    
    954
    -      );
    
    955
    -      lazy.logger.debug("Formatted promotion cred");
    
    956
    -    } catch (err) {
    
    957
    -      lazy.logger.error(
    
    958
    -        "Unable to handle trustpromo response properly",
    
    959
    -        response.error
    
    960
    -      );
    
    961
    -      return false;
    
    962
    -    }
    
    963
    -    try {
    
    964
    -      request = lazy.trust_migration(
    
    965
    -        this.#getCredentials(loxId),
    
    966
    -        promoCred,
    
    967
    -        this.#pubKeys
    
    968
    -      );
    
    969
    -      lazy.logger.debug("Formatted migration request");
    
    970
    -    } catch (err) {
    
    971
    -      lazy.logger.error("Failed to generate trust migration request", err);
    
    972
    -      return false;
    
    973
    -    }
    
    974
    -    try {
    
    975
    -      response = await this.#makeRequest(
    
    976
    -        "trustmig",
    
    977
    -        JSON.parse(request).request
    
    978
    -      );
    
    979
    -    } catch (err) {
    
    980
    -      lazy.logger.error("Failed trust migration", err);
    
    981
    -      return false;
    
    982
    -    }
    
    983
    -    if (response.hasOwnProperty("error")) {
    
    984
    -      lazy.logger.error("Error response from trustmig", response.error);
    
    985
    -      return false;
    
    986
    -    }
    
    987
    -    lazy.logger.debug("Got new credential");
    
    988
    -    let cred;
    
    989
    -    try {
    
    990
    -      cred = lazy.handle_trust_migration(request, response);
    
    991
    -    } catch (err) {
    
    992
    -      lazy.logger.error("Failed to handle response from trustmig", err);
    
    993
    -      return false;
    
    994
    -    }
    
    901
    +
    
    902
    +    let response = await this.#makeRequest("trustpromo", request);
    
    903
    +    // FIXME: Store response to "trustpromo" in case handle_trust_promotion
    
    904
    +    // or "trustmig" fails. The Lox authority will not accept a re-request
    
    905
    +    // to "trustpromo" with the same credentials.
    
    906
    +    let promoCred = lazy.handle_trust_promotion(request, response);
    
    907
    +    lazy.logger.debug("Formatted promotion cred: ", promoCred);
    
    908
    +
    
    909
    +    request = lazy.trust_migration(
    
    910
    +      this.#getCredentials(loxId),
    
    911
    +      promoCred,
    
    912
    +      this.#pubKeys
    
    913
    +    );
    
    914
    +    response = await this.#makeRequest("trustmig", request);
    
    915
    +    lazy.logger.debug("Got new credential: ", response);
    
    916
    +
    
    917
    +    // FIXME: Store response to "trustmig" in case handle_trust_migration
    
    918
    +    // fails. The Lox authority will not accept a re-request to "trustmig" with
    
    919
    +    // the same credentials.
    
    920
    +    let cred = lazy.handle_trust_migration(request, response);
    
    995 921
         this.#changeCredentials(loxId, cred);
    
    996 922
         return true;
    
    997 923
       }
    
    ... ... @@ -1079,38 +1005,47 @@ class LoxImpl {
    1079 1005
         };
    
    1080 1006
       }
    
    1081 1007
     
    
    1082
    -  async #makeRequest(procedure, args) {
    
    1008
    +  /**
    
    1009
    +   * Fetch from the Lox authority.
    
    1010
    +   *
    
    1011
    +   * @param {string} procedure - The request endpoint.
    
    1012
    +   * @param {string} body - The arguments to send in the body, if any.
    
    1013
    +   *
    
    1014
    +   * @returns {string} - The response body.
    
    1015
    +   */
    
    1016
    +  async #fetch(procedure, body) {
    
    1083 1017
         // TODO: Customize to for Lox
    
    1084
    -    const serviceUrl = "https://lox.torproject.org";
    
    1085
    -    const url = `${serviceUrl}/${procedure}`;
    
    1018
    +    const url = `https://lox.torproject.org/${procedure}`;
    
    1019
    +    const method = "POST";
    
    1020
    +    const contentType = "application/vnd.api+json";
    
    1086 1021
     
    
    1087 1022
         if (lazy.TorConnect.state === lazy.TorConnectState.Bootstrapped) {
    
    1088 1023
           let request;
    
    1089 1024
           try {
    
    1090 1025
             request = await fetch(url, {
    
    1091
    -          method: "POST",
    
    1092
    -          headers: {
    
    1093
    -            "Content-Type": "application/vnd.api+json",
    
    1094
    -          },
    
    1095
    -          body: JSON.stringify(args),
    
    1026
    +          method,
    
    1027
    +          headers: { "Content-Type": contentType },
    
    1028
    +          body,
    
    1096 1029
             });
    
    1097 1030
           } catch (error) {
    
    1098
    -        lazy.logger.debug("fetch fail", url, args, error);
    
    1031
    +        lazy.logger.debug("fetch fail", url, body, error);
    
    1099 1032
             throw new LoxError(
    
    1100 1033
               `fetch "${procedure}" from Lox authority failed: ${error?.message}`,
    
    1101 1034
               LoxError.LoxServerUnreachable
    
    1102 1035
             );
    
    1103 1036
           }
    
    1104 1037
           if (!request.ok) {
    
    1105
    -        lazy.logger.debug("fetch response", url, args, request);
    
    1038
    +        lazy.logger.debug("fetch response", url, body, request);
    
    1106 1039
             // Do not treat as a LoxServerUnreachable type.
    
    1107 1040
             throw new LoxError(
    
    1108 1041
               `Lox authority responded to "${procedure}" with ${request.status}: ${request.statusText}`
    
    1109 1042
             );
    
    1110 1043
           }
    
    1111
    -      return request.json();
    
    1044
    +      return request.text();
    
    1112 1045
         }
    
    1113 1046
     
    
    1047
    +    // TODO: Only make domain fronted requests with user permission.
    
    1048
    +    // tor-browser#42606.
    
    1114 1049
         if (this.#domainFrontedRequests === null) {
    
    1115 1050
           this.#domainFrontedRequests = new Promise((resolve, reject) => {
    
    1116 1051
             // TODO: Customize to the values for Lox
    
    ... ... @@ -1129,9 +1064,9 @@ class LoxImpl {
    1129 1064
         }
    
    1130 1065
         const builder = await this.#domainFrontedRequests;
    
    1131 1066
         try {
    
    1132
    -      return await builder.buildPostRequest(url, args);
    
    1067
    +      return await builder.buildRequest(url, { method, contentType, body });
    
    1133 1068
         } catch (error) {
    
    1134
    -      lazy.logger.debug("Domain front request fail", url, args, error);
    
    1069
    +      lazy.logger.debug("Domain front request fail", url, body, error);
    
    1135 1070
           if (error instanceof lazy.DomainFrontRequestNetworkError) {
    
    1136 1071
             throw new LoxError(
    
    1137 1072
               `Domain front fetch "${procedure}" from Lox authority failed: ${error?.message}`,
    
    ... ... @@ -1149,6 +1084,37 @@ class LoxImpl {
    1149 1084
           );
    
    1150 1085
         }
    
    1151 1086
       }
    
    1087
    +
    
    1088
    +  /**
    
    1089
    +   * Make a request to the lox authority, check for an error response, and
    
    1090
    +   * convert it to a string.
    
    1091
    +   *
    
    1092
    +   * @param {string} procedure - The request endpoint.
    
    1093
    +   * @param {?string} request - The request data, as a JSON string containing a
    
    1094
    +   *   "request" field. Or `null` to send no data.
    
    1095
    +   *
    
    1096
    +   * @returns {string} - The stringified JSON response.
    
    1097
    +   */
    
    1098
    +  async #makeRequest(procedure, request) {
    
    1099
    +    // Verify that the response is valid json, by parsing.
    
    1100
    +    const jsonResponse = JSON.parse(
    
    1101
    +      await this.#fetch(
    
    1102
    +        procedure,
    
    1103
    +        request ? JSON.stringify(JSON.parse(request).request) : ""
    
    1104
    +      )
    
    1105
    +    );
    
    1106
    +    lazy.logger.debug(`${procedure} response:`, jsonResponse);
    
    1107
    +    if (Object.hasOwn(jsonResponse, "error")) {
    
    1108
    +      // TODO: Figure out if any of the "error" responses should be treated as
    
    1109
    +      // an error. I.e. which of the procedures have soft failures and hard
    
    1110
    +      // failures.
    
    1111
    +      throw LoxError(
    
    1112
    +        `Error response to ${procedure}: ${jsonResponse.error}`,
    
    1113
    +        LoxError.ErrorResponse
    
    1114
    +      );
    
    1115
    +    }
    
    1116
    +    return JSON.stringify(jsonResponse);
    
    1117
    +  }
    
    1152 1118
     }
    
    1153 1119
     
    
    1154 1120
     export const Lox = new LoxImpl();

  • toolkit/modules/DomainFrontedRequests.sys.mjs
    ... ... @@ -523,34 +523,31 @@ export class DomainFrontRequestBuilder {
    523 523
       }
    
    524 524
     
    
    525 525
       /**
    
    526
    -   * Make a POST request with a JSON body and a JSON response.
    
    526
    +   * Make a request.
    
    527 527
        *
    
    528
    -   * @param {string} url The URL to load
    
    529
    -   * @param {object} args The arguments to send to the procedure. It will be
    
    530
    -   * serialized to JSON by this function and then set as POST body
    
    531
    -   * @returns {Promise<object>} A promise with the parsed response
    
    528
    +   * @param {string} url The URL to request.
    
    529
    +   * @param {object} args The arguments to send to the procedure.
    
    530
    +   * @param {string} args.method The request method.
    
    531
    +   * @param {string} args.body The request body.
    
    532
    +   * @param {string} args.contentType The "Content-Type" header to set.
    
    533
    +   * @returns {string} The response body.
    
    532 534
        */
    
    533
    -  async buildPostRequest(url, args) {
    
    535
    +  async buildRequest(url, args) {
    
    534 536
         const ch = this.buildHttpHandler(url);
    
    535 537
     
    
    536
    -    const argsJson = JSON.stringify(args);
    
    537 538
         const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    
    538 539
           Ci.nsIStringInputStream
    
    539 540
         );
    
    540
    -    inStream.setData(argsJson, argsJson.length);
    
    541
    +    inStream.setData(args.body, args.body.length);
    
    541 542
         const upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
    
    542
    -    const contentType = "application/vnd.api+json";
    
    543
    -    upChannel.setUploadStream(inStream, contentType, argsJson.length);
    
    544
    -    ch.requestMethod = "POST";
    
    543
    +    upChannel.setUploadStream(inStream, args.contentType, args.body.length);
    
    544
    +    ch.requestMethod = args.method;
    
    545 545
     
    
    546 546
         // Make request
    
    547 547
         const listener = new ResponseListener();
    
    548 548
         await ch.asyncOpen(listener, ch);
    
    549 549
     
    
    550 550
         // wait for response
    
    551
    -    const responseJSON = await listener.response();
    
    552
    -
    
    553
    -    // parse that JSON
    
    554
    -    return JSON.parse(responseJSON);
    
    551
    +    return listener.response();
    
    555 552
       }
    
    556 553
     }

  • toolkit/modules/Moat.sys.mjs
    ... ... @@ -108,7 +108,13 @@ export class MoatRPC {
    108 108
         const procedureURIString = `${Services.prefs.getStringPref(
    
    109 109
           TorLauncherPrefs.moat_service
    
    110 110
         )}/${procedure}`;
    
    111
    -    return this.#requestBuilder.buildPostRequest(procedureURIString, args);
    
    111
    +    return JSON.parse(
    
    112
    +      await this.#requestBuilder.buildRequest(procedureURIString, {
    
    113
    +        method: "POST",
    
    114
    +        contentType: "application/vnd.api+json",
    
    115
    +        body: JSON.stringify(args),
    
    116
    +      })
    
    117
    +    );
    
    112 118
       }
    
    113 119
     
    
    114 120
       async testInternetConnection() {