Bug 774300 - Sync authentication errors if passwords contain non-ASCII characters. r=nalexander

* * *
Bug 774300 - Fix omission. r=me
This commit is contained in:
Richard Newman 2012-08-02 22:00:43 -07:00
parent 4d1ff3cc48
commit 1aa5365609
4 changed files with 107 additions and 4 deletions

View File

@ -504,4 +504,20 @@ public class Utils {
final long duration = endMillis - startMillis;
return new DecimalFormat("#0.00 seconds").format(((double) duration) / 1000);
}
}
/**
* 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");
}
}

View File

@ -129,14 +129,22 @@ public class BaseResource implements Resource {
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.");
}

View File

@ -4,6 +4,7 @@
package org.mozilla.gecko.sync.setup.activities;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import org.json.simple.JSONObject;
@ -11,6 +12,7 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.sync.GlobalConstants;
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;
@ -392,6 +394,13 @@ public class SetupSyncActivity extends AccountAuthenticatorActivity {
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);
createAccountOnThread(syncAccount);

View File

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.net.BaseResource;
import ch.boye.httpclientandroidlib.Header;
/**
* Test the transfer of a UTF-8 string from desktop, and ensure that it results in the
* correct hashed Basic Auth header.
*/
public class TestCredentialsEndToEnd {
public static final String REAL_PASSWORD = "pïgéons1";
public static final String USERNAME = "utvm3mk6hnngiir2sp4jsxf2uvoycrv6";
public static final String DESKTOP_PASSWORD_JSON = "{\"password\":\"pïgéons1\"}";
public static final String BTOA_PASSWORD = "cMOvZ8Opb25zMQ==";
public static final int DESKTOP_ASSERTED_SIZE = 10;
public static final String DESKTOP_BASIC_AUTH = "Basic dXR2bTNtazZobm5naWlyMnNwNGpzeGYydXZveWNydjY6cMOvZ8Opb25zMQ==";
private String getCreds(String password) {
Header authenticate = BaseResource.getBasicAuthHeader(USERNAME + ":" + password);
return authenticate.getValue();
}
@SuppressWarnings("static-method")
@Test
public void testUTF8() throws UnsupportedEncodingException {
final String in = "pïgéons1";
final String out = "pïgéons1";
assertEquals(out, Utils.decodeUTF8(in));
}
@Test
public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException, ParseException {
final ExtendedJSONObject parsed = new ExtendedJSONObject(DESKTOP_PASSWORD_JSON);
final String password = parsed.getString("password");
final String decoded = Utils.decodeUTF8(password);
final byte[] expectedBytes = Utils.decodeBase64(BTOA_PASSWORD);
final String expected = new String(expectedBytes, "UTF-8");
assertEquals(DESKTOP_ASSERTED_SIZE, password.length());
assertEquals(expected, decoded);
System.out.println("Retrieved password: " + password);
System.out.println("Expected password: " + expected);
System.out.println("Rescued password: " + decoded);
assertEquals(getCreds(expected), getCreds(decoded));
assertEquals(getCreds(decoded), DESKTOP_BASIC_AUTH);
}
// Note that we do *not* have a test for the J-PAKE setup process
// (SetupSyncActivity) that actually stores credentials and requires
// decodeUTF8. This will have to suffice.
}