Bug 774300 - Sync authentication errors if passwords contain non-ASCII characters. r=nalexander, a=lsblakk
authorRichard Newman <rnewman@mozilla.com>
Thu, 02 Aug 2012 22:00:43 -0700
changeset 100482 bd0b4c2ac18e557637e2a0a03c4eb0f9b51121d1
parent 100481 19ad43a4a27b4deb009eecf1e6a1e33c199107ec
child 100483 59e1040773a10069029ccf282837fdcef3d28cbe
push id1264
push userrnewman@mozilla.com
push dateMon, 13 Aug 2012 06:47:11 +0000
treeherdermozilla-beta@bd0b4c2ac18e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander, lsblakk
bugs774300
milestone15.0
Bug 774300 - Sync authentication errors if passwords contain non-ASCII characters. r=nalexander, a=lsblakk
mobile/android/base/sync/Utils.java
mobile/android/base/sync/net/BaseResource.java
mobile/android/base/sync/setup/activities/SetupSyncActivity.java
--- a/mobile/android/base/sync/Utils.java
+++ b/mobile/android/base/sync/Utils.java
@@ -343,9 +343,25 @@ public class Utils {
         try {
           fis.close();
         } catch (IOException e) {
           // Ignore.
         }
       }
     }
   }
+
+  /**
+   * This will take a string containing a UTF-8 representation of a UTF-8
+   * byte array — e.g., "pïgéons1" — and return UTF-8 (e.g., "pïgéons1").
+   *
+   * This is the format produced by desktop Firefox when exchanging credentials
+   * containing non-ASCII characters.
+   */
+  public static String decodeUTF8(final String in) throws UnsupportedEncodingException {
+    final int length = in.length();
+    final byte[] asciiBytes = new byte[length];
+    for (int i = 0; i < length; ++i) {
+      asciiBytes[i] = (byte) in.codePointAt(i);
+    }
+    return new String(asciiBytes, "UTF-8");
+  }
 }
--- a/mobile/android/base/sync/net/BaseResource.java
+++ b/mobile/android/base/sync/net/BaseResource.java
@@ -125,23 +125,31 @@ public class BaseResource implements Res
    * being no auth cache in the context.
    */
   private static void addAuthCacheToContext(HttpUriRequest request, HttpContext context) {
     AuthCache authCache = new BasicAuthCache();                // Not thread safe.
     context.setAttribute(ClientContext.AUTH_CACHE, authCache);
   }
 
   /**
+   * Return a Header object representing an Authentication header for HTTP Basic.
+   */
+  public static Header getBasicAuthHeader(final String credentials) {
+    Credentials creds = new UsernamePasswordCredentials(credentials);
+
+    // This must be UTF-8 to generate the same Basic Auth headers as desktop for non-ASCII passwords.
+    return BasicScheme.authenticate(creds, "UTF-8", false);
+  }
+
+  /**
    * Apply the provided credentials string to the provided request.
    * @param credentials a string, "user:pass".
    */
   private static void applyCredentials(String credentials, HttpUriRequest request, HttpContext context) {
-    Credentials creds = new UsernamePasswordCredentials(credentials);
-    Header header = BasicScheme.authenticate(creds, "US-ASCII", false);
-    request.addHeader(header);
+    request.addHeader(getBasicAuthHeader(credentials));
     Logger.trace(LOG_TAG, "Adding Basic Auth header.");
   }
 
   /**
    * Invoke this after delegate and request have been set.
    * @throws NoSuchAlgorithmException
    * @throws KeyManagementException
    */
--- a/mobile/android/base/sync/setup/activities/SetupSyncActivity.java
+++ b/mobile/android/base/sync/setup/activities/SetupSyncActivity.java
@@ -1,20 +1,22 @@
 /* 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/. */
 
 package org.mozilla.gecko.sync.setup.activities;
 
+import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.ThreadPool;
+import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.jpake.JPakeNoActivePairingException;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
 
 import android.accounts.Account;
 import android.accounts.AccountAuthenticatorActivity;
@@ -386,16 +388,23 @@ public class SetupSyncActivity extends A
     boolean result = true;
 
     if (!pairWithPin) {
       String accountName  = (String) jCreds.get(Constants.JSON_KEY_ACCOUNT);
       String password     = (String) jCreds.get(Constants.JSON_KEY_PASSWORD);
       String syncKey      = (String) jCreds.get(Constants.JSON_KEY_SYNCKEY);
       String serverURL    = (String) jCreds.get(Constants.JSON_KEY_SERVER);
 
+      // The password we get is double-encoded.
+      try {
+        password = Utils.decodeUTF8(password);
+      } catch (UnsupportedEncodingException e) {
+        Logger.warn(LOG_TAG, "Unsupported encoding when decoding UTF-8 ASCII J-PAKE message. Ignoring.");
+      }
+
       final SyncAccountParameters syncAccount = new SyncAccountParameters(mContext, mAccountManager,
           accountName, syncKey, password, serverURL);
       final Account account = SyncAccounts.createSyncAccount(syncAccount);
       result = (account != null);
 
       final Intent intent = new Intent(); // The intent to return.
       intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username);
       intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNTTYPE_SYNC);