diff options
3 files changed, 200 insertions, 0 deletions
diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java index 65d8690..a015d19 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java @@ -16,7 +16,9 @@ package libcore.javax.net.ssl; +import java.io.IOException; import java.util.Arrays; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -83,6 +85,20 @@ public class SSLEngineTest extends TestCase { boolean secureRenegotiation) throws Exception { TestSSLContext c = TestSSLContext.create(testKeyStore, testKeyStore); + + // Create a TestSSLContext where the KeyManager returns wrong (randomly generated) private + // keys, matching the algorithm and parameters of the correct keys. + // I couldn't find a more elegant way to achieve this other than temporarily replacing the + // first element of TestKeyStore.keyManagers while invoking TestSSLContext.create. + TestSSLContext cWithWrongPrivateKeys; + { + KeyManager originalKeyManager = testKeyStore.keyManagers[0]; + testKeyStore.keyManagers[0] = + new RandomPrivateKeyX509ExtendedKeyManager(c.serverKeyManager); + cWithWrongPrivateKeys = TestSSLContext.create(testKeyStore, testKeyStore); + testKeyStore.keyManagers[0] = originalKeyManager; + } + String[] cipherSuites = c.clientContext.createSSLEngine().getSupportedCipherSuites(); for (String cipherSuite : cipherSuites) { boolean errorExpected = StandardNames.IS_RI && cipherSuite.endsWith("_SHA256"); @@ -109,6 +125,8 @@ public class SSLEngineTest extends TestCase { ? new String[] { cipherSuite, StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION } : new String[] { cipherSuite }); + + // Check that handshake succeeds. assertConnected(TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { @@ -117,6 +135,27 @@ public class SSLEngineTest extends TestCase { } })); assertFalse(errorExpected); + + // Check that handshake fails when the server does not possess the private key + // corresponding to the server's certificate. This is achieved by using SSLContext + // cWithWrongPrivateKeys whose KeyManager returns wrong private keys that match + // the algorithm (and parameters) of the correct keys. + if (!cipherSuite.contains("_anon_")) { + // The identity of the server is verified only in non-anonymous key exchanges. + try { + TestSSLEnginePair p = TestSSLEnginePair.create( + cWithWrongPrivateKeys, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + client.setEnabledCipherSuites(cipherSuiteArray); + server.setEnabledCipherSuites(cipherSuiteArray); + } + }); + assertConnected(p); + fail("Handshake succeeded for " + cipherSuite + + " despite server not having the correct private key"); + } catch (IOException expected) {} + } } catch (Exception maybeExpected) { if (!errorExpected) { throw new Exception("Problem trying to connect cipher suite " + cipherSuite, diff --git a/support/src/test/java/libcore/javax/net/ssl/ForwardingX509ExtendedKeyManager.java b/support/src/test/java/libcore/javax/net/ssl/ForwardingX509ExtendedKeyManager.java new file mode 100644 index 0000000..e901d34 --- /dev/null +++ b/support/src/test/java/libcore/javax/net/ssl/ForwardingX509ExtendedKeyManager.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 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 libcore.javax.net.ssl; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; + +/** + * {@link X509ExtendedKeyManager} which delegates all calls to the provided + * {@code X509ExtendedKeyManager} instance. + */ +public class ForwardingX509ExtendedKeyManager extends X509ExtendedKeyManager { + private final X509ExtendedKeyManager delegate; + + public ForwardingX509ExtendedKeyManager(X509ExtendedKeyManager delegate) { + this.delegate = delegate; + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + return delegate.chooseClientAlias(keyType, issuers, socket); + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return delegate.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return delegate.getCertificateChain(alias); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return delegate.getClientAliases(keyType, issuers); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return delegate.getServerAliases(keyType, issuers); + } + + @Override + public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + return delegate.chooseEngineClientAlias(keyType, issuers, engine); + } + + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { + return delegate.chooseEngineServerAlias(keyType, issuers, engine); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return delegate.getPrivateKey(alias); + } +} diff --git a/support/src/test/java/libcore/javax/net/ssl/RandomPrivateKeyX509ExtendedKeyManager.java b/support/src/test/java/libcore/javax/net/ssl/RandomPrivateKeyX509ExtendedKeyManager.java new file mode 100644 index 0000000..fd5cc0b --- /dev/null +++ b/support/src/test/java/libcore/javax/net/ssl/RandomPrivateKeyX509ExtendedKeyManager.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 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 libcore.javax.net.ssl; + +import junit.framework.Assert; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.spec.DSAParameterSpec; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.X509ExtendedKeyManager; + +/** + * {@link X509ExtendedKeyManager} which forwards all calls to a delegate while substituting + * the returned private key with its own randomly generated keys of the same type (and parameters). + */ +public class RandomPrivateKeyX509ExtendedKeyManager extends ForwardingX509ExtendedKeyManager { + + private final Map<String, PrivateKey> cachedKeys = new HashMap<String, PrivateKey>(); + + public RandomPrivateKeyX509ExtendedKeyManager(X509ExtendedKeyManager delegate) { + super(delegate); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + PrivateKey originalPrivateKey = super.getPrivateKey(alias); + if (originalPrivateKey == null) { + return null; + } + + PrivateKey result; + String keyAlgorithm = originalPrivateKey.getAlgorithm(); + try { + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm); + if ("RSA".equals(keyAlgorithm)) { + RSAPrivateKeySpec originalKeySpec = + keyFactory.getKeySpec(originalPrivateKey, RSAPrivateKeySpec.class); + int keyLengthBits = originalKeySpec.getModulus().bitLength(); + // Use a cache because RSA key generation is slow. + String cacheKey = keyAlgorithm + "-" + keyLengthBits; + result = cachedKeys.get(cacheKey); + if (result == null) { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm); + keyPairGenerator.initialize(keyLengthBits); + result = keyPairGenerator.generateKeyPair().getPrivate(); + cachedKeys.put(cacheKey, result); + } + } else if ("DSA".equals(keyAlgorithm)) { + DSAPrivateKeySpec originalKeySpec = + keyFactory.getKeySpec(originalPrivateKey, DSAPrivateKeySpec.class); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm); + keyPairGenerator.initialize(new DSAParameterSpec( + originalKeySpec.getP(), originalKeySpec.getQ(), originalKeySpec.getG())); + result = keyPairGenerator.generateKeyPair().getPrivate(); + } else { + Assert.fail("Unsupported key algorithm: " + originalPrivateKey.getAlgorithm()); + result = null; + } + } catch (GeneralSecurityException e) { + Assert.fail("Failed to generate private key: " + e); + result = null; + } + + return result; + } +} |