summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Qiu <zqiu@google.com>2017-03-20 14:55:45 -0700
committerPeter Qiu <zqiu@google.com>2017-03-29 10:32:25 -0700
commitf331a6bf511cc9d105a45219548d0ad3feab5a70 (patch)
tree8bf3b5aeba076a408607271645ee88bbf525442d
parentbb2fdf553e6580923e1dc3e6ac90ca777c5d7226 (diff)
hotspot2: verify CA certificate before installing Passpoint provider
For Hotspot 2.0 Release 1, the provisioning method is not standardized, so to improve security, the CA certificate must be trusted by one of the pre-loaded public CAs in the system key store. So verify the CA certificate if one is provided before installing the Passpoint provider. A utility class CertificateVerifier is created in order to easily mock the certificate verification in unit tests. Bug: 34460350 Test: manual test Change-Id: I627ef9da2876ffbaf29aadb2d5b281c75ec1d145
-rw-r--r--service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java57
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointManager.java17
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java9
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java36
4 files changed, 119 insertions, 0 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java b/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java
new file mode 100644
index 000000000..004a32fbd
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.server.wifi.hotspot2;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * Utility class used for verifying certificates against the pre-loaded public CAs in the
+ * system key store. This class is created to allow the certificate verification to be mocked in
+ * unit tests.
+ */
+public class CertificateVerifier {
+
+ /**
+ * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the
+ * system key store.
+ *
+ * @param caCert The CA Certificate to verify
+ * @throws GeneralSecurityException
+ * @throws IOException
+ */
+ public void verifyCaCert(X509Certificate caCert)
+ throws GeneralSecurityException, IOException {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ CertPathValidator validator =
+ CertPathValidator.getInstance(CertPathValidator.getDefaultType());
+ CertPath path = factory.generateCertPath(
+ Arrays.asList(caCert));
+ KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+ ks.load(null, null);
+ PKIXParameters params = new PKIXParameters(ks);
+ params.setRevocationEnabled(false);
+ validator.validate(path, params);
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index f472d86db..51781de27 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -93,6 +93,7 @@ public class PasspointManager {
private final AnqpCache mAnqpCache;
private final ANQPRequestManager mAnqpRequestManager;
private final WifiConfigManager mWifiConfigManager;
+ private final CertificateVerifier mCertVerifier;
// Counter used for assigning unique identifier to each provider.
private long mProviderIndex;
@@ -199,6 +200,7 @@ public class PasspointManager {
mProviders = new HashMap<>();
mAnqpCache = objectFactory.makeAnqpCache(clock);
mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock);
+ mCertVerifier = objectFactory.makeCertificateVerifier();
mWifiConfigManager = wifiConfigManager;
mProviderIndex = 0;
wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
@@ -226,6 +228,21 @@ public class PasspointManager {
return false;
}
+ // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded
+ // public CAs in the system key store on the device. Since the provisioning method
+ // for Release 1 is not standardized nor trusted, this is a reasonable restriction
+ // to improve security. The presence of UpdateIdentifier is used to differentiate
+ // between R1 and R2 configuration.
+ if (config.getUpdateIdentifier() == Integer.MIN_VALUE
+ && config.getCredential().getCaCertificate() != null) {
+ try {
+ mCertVerifier.verifyCaCert(config.getCredential().getCaCertificate());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage());
+ return false;
+ }
+ }
+
// Create a provider and install the necessary certificates and keys.
PasspointProvider newProvider = mObjectFactory.makePasspointProvider(
config, mKeyStore, mSimAccessor, mProviderIndex++);
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index 821cee67a..16982969b 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -86,4 +86,13 @@ public class PasspointObjectFactory{
public ANQPRequestManager makeANQPRequestManager(PasspointEventHandler handler, Clock clock) {
return new ANQPRequestManager(handler, clock);
}
+
+ /**
+ * Create an instance of {@link CertificateVerifier}.
+ *
+ * @return {@link CertificateVerifier}
+ */
+ public CertificateVerifier makeCertificateVerifier() {
+ return new CertificateVerifier();
+ }
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index 8008bf68e..e6951f435 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyMap;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -77,6 +78,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -113,6 +116,7 @@ public class PasspointManagerTest {
@Mock PasspointEventHandler.Callbacks mCallbacks;
@Mock AnqpCache mAnqpCache;
@Mock ANQPRequestManager mAnqpRequestManager;
+ @Mock CertificateVerifier mCertVerifier;
@Mock WifiConfigManager mWifiConfigManager;
@Mock WifiConfigStore mWifiConfigStore;
@Mock PasspointConfigStoreData.DataSource mDataSource;
@@ -125,6 +129,7 @@ public class PasspointManagerTest {
when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache);
when(mObjectFactory.makeANQPRequestManager(any(), eq(mClock)))
.thenReturn(mAnqpRequestManager);
+ when(mObjectFactory.makeCertificateVerifier()).thenReturn(mCertVerifier);
mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
mSimAccessor, mObjectFactory, mWifiConfigManager, mWifiConfigStore);
ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
@@ -550,6 +555,37 @@ public class PasspointManagerTest {
}
/**
+ * Verify that adding a provider with an invalid CA certificate will fail.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithInvalidCaCert() throws Exception {
+ PasspointConfiguration config = createTestConfigWithUserCredential();
+ doThrow(new GeneralSecurityException())
+ .when(mCertVerifier).verifyCaCert(any(X509Certificate.class));
+ assertFalse(mManager.addOrUpdateProvider(config));
+ }
+
+ /**
+ * Verify that adding a provider with R2 configuration will not perform CA certificate
+ * verification.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithR2Config() throws Exception {
+ PasspointConfiguration config = createTestConfigWithUserCredential();
+ config.setUpdateIdentifier(1);
+ PasspointProvider provider = createMockProvider(config);
+ when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+ eq(mSimAccessor), anyLong())).thenReturn(provider);
+ assertTrue(mManager.addOrUpdateProvider(config));
+ verify(mCertVerifier, never()).verifyCaCert(any(X509Certificate.class));
+ verifyInstalledConfig(config);
+ }
+
+ /**
* Verify that removing a non-existing provider will fail.
*
* @throws Exception