Introduce scheme name escaping in SSLUtils.
Change-Id: I73f19e7d40d0b19dfd41cfaf7db0879ef2e3a3ea
This commit is contained in:
parent
313586c8eb
commit
724c3a81cd
|
@ -18,6 +18,7 @@
|
|||
package com.android.emailcommon.provider;
|
||||
|
||||
import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
|
||||
import com.android.emailcommon.utility.SSLUtils;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
@ -133,13 +134,10 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
|
|||
throw new IllegalArgumentException(
|
||||
"Can't specify a certificate alias for a non-secure connection");
|
||||
}
|
||||
// TODO: investigate what the certificate aliases look like from the framework
|
||||
// and ensure they're safe scheme names.
|
||||
String safeScheme = clientAlias;
|
||||
if (!security.endsWith("+")) {
|
||||
security += "+";
|
||||
}
|
||||
security += safeScheme;
|
||||
security += SSLUtils.escapeForSchemeName(clientAlias);
|
||||
}
|
||||
|
||||
return protocol + security;
|
||||
|
|
|
@ -42,4 +42,32 @@ public class SSLUtils {
|
|||
return sSecureFactory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the contents a string to be used as a safe scheme name in the URI according to
|
||||
* http://tools.ietf.org/html/rfc3986#section-3.1
|
||||
*
|
||||
* This does not ensure that the first character is a letter (which is required by the RFC).
|
||||
*/
|
||||
public static String escapeForSchemeName(String s) {
|
||||
// According to the RFC, scheme names are case-insensitive.
|
||||
s = s.toLowerCase();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (Character.isLetter(c) || Character.isDigit(c)
|
||||
|| ('-' == c) || ('.' == c)) {
|
||||
// Safe - use as is.
|
||||
sb.append(c);
|
||||
} else if ('+' == c) {
|
||||
// + is used as our escape character, so double it up.
|
||||
sb.append("++");
|
||||
} else {
|
||||
// Unsafe - escape.
|
||||
sb.append('+').append((int) c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -450,15 +450,15 @@ public class HostAuthTests extends AndroidTestCase {
|
|||
scheme = HostAuth.getSchemeString("foo", HostAuth.FLAG_TLS, "custom-client-cert-alias");
|
||||
assertEquals("foo+tls+custom-client-cert-alias", scheme);
|
||||
scheme = HostAuth.getSchemeString(
|
||||
"foo", HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, "custom_client_cert_alias");
|
||||
assertEquals("foo+ssl+trustallcerts+custom_client_cert_alias", scheme);
|
||||
"foo", HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, "custom-client-cert-alias");
|
||||
assertEquals("foo+ssl+trustallcerts+custom-client-cert-alias", scheme);
|
||||
scheme = HostAuth.getSchemeString(
|
||||
"foo", HostAuth.FLAG_TLS | HostAuth.FLAG_TRUST_ALL, "custom_client_cert_alias");
|
||||
assertEquals("foo+tls+trustallcerts+custom_client_cert_alias", scheme);
|
||||
"foo", HostAuth.FLAG_TLS | HostAuth.FLAG_TRUST_ALL, "custom-client-cert-alias");
|
||||
assertEquals("foo+tls+trustallcerts+custom-client-cert-alias", scheme);
|
||||
|
||||
try {
|
||||
scheme = HostAuth.getSchemeString(
|
||||
"foo", 0 /* no security flags */, "custom_client_cert_alias");
|
||||
"foo", 0 /* no security flags */, "custom-client-cert-alias");
|
||||
fail("Should not be able to set a custom client cert on an insecure connection");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.emailcommon.utility;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.MoreAsserts;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Unit tests for SSLUtils.
|
||||
*/
|
||||
@SmallTest
|
||||
public class SSLUtilsTest extends AndroidTestCase {
|
||||
|
||||
String SAFE_SCHEME_PATTERN = "[a-z][a-z0-9+\\-]*";
|
||||
private void assertSchemeNameValid(String s) {
|
||||
assertTrue(Pattern.matches(SAFE_SCHEME_PATTERN, s));
|
||||
}
|
||||
|
||||
public void testSchemeNameEscapeAlreadySafe() {
|
||||
// Safe names are unmodified.
|
||||
assertEquals("http", SSLUtils.escapeForSchemeName("http"));
|
||||
assertEquals("https", SSLUtils.escapeForSchemeName("https"));
|
||||
assertEquals("ftp", SSLUtils.escapeForSchemeName("ftp"));
|
||||
assertEquals("z39.50r", SSLUtils.escapeForSchemeName("z39.50r"));
|
||||
assertEquals("fake-protocol.yes", SSLUtils.escapeForSchemeName("fake-protocol.yes"));
|
||||
}
|
||||
|
||||
public void testSchemeNameEscapeIsSafe() {
|
||||
// Invalid characters are escaped properly
|
||||
assertSchemeNameValid(SSLUtils.escapeForSchemeName("name with spaces"));
|
||||
assertSchemeNameValid(SSLUtils.escapeForSchemeName("odd * & characters"));
|
||||
assertSchemeNameValid(SSLUtils.escapeForSchemeName("f3v!l;891023-47 +"));
|
||||
}
|
||||
|
||||
private static final char[] RANDOM_DICT = new char[] {
|
||||
'x', '.', '^', '4', ';', ' ', 'j', '#', '~', '+'
|
||||
};
|
||||
private String randomString(Random r) {
|
||||
// 5 to 15 characters
|
||||
int length = (r.nextInt() % 5) + 10;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(RANDOM_DICT[Math.abs(r.nextInt()) % RANDOM_DICT.length]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void testSchemeNamesAreMoreOrLessUnique() {
|
||||
assertEquals(
|
||||
SSLUtils.escapeForSchemeName("name with spaces"),
|
||||
SSLUtils.escapeForSchemeName("name with spaces"));
|
||||
|
||||
// As expected, all escaping is case insensitive.
|
||||
assertEquals(
|
||||
SSLUtils.escapeForSchemeName("NAME with spaces"),
|
||||
SSLUtils.escapeForSchemeName("name with spaces"));
|
||||
|
||||
Random random = new Random(314159 /* seed */);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Other strings should more or less be unique.
|
||||
String s1 = randomString(random);
|
||||
String s2 = randomString(random);
|
||||
MoreAsserts.assertNotEqual(
|
||||
SSLUtils.escapeForSchemeName(s1),
|
||||
SSLUtils.escapeForSchemeName(s2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue