blob: bab60b44e2ee19c52bc453589fcd6d68561a041d [file] [log] [blame]
/*
* Copyright (C) 2014 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 org.conscrypt;
import junit.framework.TestCase;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.security.Key;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class DuckTypedPSKKeyManagerTest extends TestCase {
private SSLSocket mSSLSocket;
private SSLEngine mSSLEngine;
@Override
protected void setUp() throws Exception {
super.setUp();
SSLContext sslContext = SSLContext.getDefault();
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
mSSLSocket = (SSLSocket) sslSocketFactory.createSocket();
mSSLEngine = sslContext.createSSLEngine();
}
@Override
protected void tearDown() throws Exception {
try {
if (mSSLSocket != null) {
try {
mSSLSocket.close();
} catch (Exception ignored) {}
}
} finally {
super.tearDown();
}
}
public void testDuckTypingFailsWhenOneMethodMissing() throws Exception {
try {
DuckTypedPSKKeyManager.getInstance(new AlmostPSKKeyManager());
fail();
} catch (NoSuchMethodException expected) {}
}
public void testDuckTypingFailsWhenOneMethodReturnTypeIncompatible()
throws Exception {
try {
assertNotNull(DuckTypedPSKKeyManager.getInstance(
new KeyManagerOfferingAllPSKKeyManagerMethodsWithIncompatibleReturnTypes()));
fail();
} catch (NoSuchMethodException expected) {}
}
public void testDuckTypingSucceedsWhenAllMethodsPresentWithExactReturnTypes() throws Exception {
assertNotNull(DuckTypedPSKKeyManager.getInstance(
new KeyManagerOfferingAllPSKKeyManagerMethodsWithExactReturnTypes()));
}
public void testDuckTypingSucceedsWhenAllMethodsPresentWithDifferentButCompatibleReturnTypes()
throws Exception {
assertNotNull(DuckTypedPSKKeyManager.getInstance(
new KeyManagerOfferingAllPSKKeyManagerMethodsWithCompatibleReturnTypes()));
}
public void testMethodInvocationDelegation() throws Exception {
// IMPLEMENTATION NOTE: We create a DuckTypedPSKKeyManager wrapping a Reflection Proxy,
// invoke each method of the PSKKeyManager interface on the DuckTypedPSKKeyManager instance,
// and assert that invocations on the Proxy are as expected and that values returned by the
// Proxy are returned to us.
MockInvocationHandler mockInvocationHandler = new MockInvocationHandler();
PSKKeyManager delegate = (PSKKeyManager) Proxy.newProxyInstance(
DuckTypedPSKKeyManager.class.getClassLoader(),
new Class[] {PSKKeyManager.class},
mockInvocationHandler);
PSKKeyManager pskKeyManager = DuckTypedPSKKeyManager.getInstance(delegate);
String identityHint = "hint";
String identity = "identity";
mockInvocationHandler.returnValue = identityHint;
assertSame(identityHint, pskKeyManager.chooseServerKeyIdentityHint(mSSLSocket));
assertEquals("chooseServerKeyIdentityHint",
mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {Socket.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(1, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(mSSLSocket, mockInvocationHandler.lastInvokedMethodArgs[0]);
mockInvocationHandler.returnValue = identityHint;
assertSame(identityHint, pskKeyManager.chooseServerKeyIdentityHint(mSSLEngine));
assertEquals("chooseServerKeyIdentityHint",
mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {SSLEngine.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(1, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(mSSLEngine, mockInvocationHandler.lastInvokedMethodArgs[0]);
mockInvocationHandler.returnValue = identity;
assertSame(identity, pskKeyManager.chooseClientKeyIdentity(identityHint, mSSLSocket));
assertEquals("chooseClientKeyIdentity", mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {String.class, Socket.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(2, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(identityHint, mockInvocationHandler.lastInvokedMethodArgs[0]);
assertSame(mSSLSocket, mockInvocationHandler.lastInvokedMethodArgs[1]);
mockInvocationHandler.returnValue = identity;
assertSame(identity, pskKeyManager.chooseClientKeyIdentity(identityHint, mSSLEngine));
assertEquals("chooseClientKeyIdentity", mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {String.class, SSLEngine.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(2, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(identityHint, mockInvocationHandler.lastInvokedMethodArgs[0]);
assertSame(mSSLEngine, mockInvocationHandler.lastInvokedMethodArgs[1]);
SecretKey key = new SecretKeySpec("arbitrary".getBytes(), "RAW");
mockInvocationHandler.returnValue = key;
assertSame(key, pskKeyManager.getKey(identityHint, identity, mSSLSocket));
assertEquals("getKey", mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {String.class, String.class, Socket.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(3, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(identityHint, mockInvocationHandler.lastInvokedMethodArgs[0]);
assertSame(identity, mockInvocationHandler.lastInvokedMethodArgs[1]);
assertSame(mSSLSocket, mockInvocationHandler.lastInvokedMethodArgs[2]);
mockInvocationHandler.returnValue = key;
assertSame(key, pskKeyManager.getKey(identityHint, identity, mSSLEngine));
assertEquals("getKey", mockInvocationHandler.lastInvokedMethod.getName());
assertEquals(Arrays.asList(new Class[] {String.class, String.class, SSLEngine.class}),
Arrays.asList(mockInvocationHandler.lastInvokedMethod.getParameterTypes()));
assertEquals(3, mockInvocationHandler.lastInvokedMethodArgs.length);
assertSame(identityHint, mockInvocationHandler.lastInvokedMethodArgs[0]);
assertSame(identity, mockInvocationHandler.lastInvokedMethodArgs[1]);
assertSame(mSSLEngine, mockInvocationHandler.lastInvokedMethodArgs[2]);
}
public void testMethodInvocationDelegationWithDifferentButCompatibleReturnType()
throws Exception {
// Check that nothing blows up when we invoke getKey which is declared to return
// SecretKeySpec rather than SecretKey as declared in the PSKKeyManager interface.
PSKKeyManager pskKeyManager = DuckTypedPSKKeyManager.getInstance(
new KeyManagerOfferingAllPSKKeyManagerMethodsWithCompatibleReturnTypes());
pskKeyManager.getKey(null, "", mSSLSocket);
pskKeyManager.getKey(null, "", mSSLEngine);
}
/**
* {@link KeyManager} which implements all methods of {@link PSKKeyManager} except for one.
*/
@SuppressWarnings("unused")
private static class AlmostPSKKeyManager implements KeyManager {
public String chooseServerKeyIdentityHint(Socket socket) {
return null;
}
public String chooseServerKeyIdentityHint(SSLEngine engine) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, Socket socket) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, SSLEngine engine) {
return null;
}
public SecretKey getKey(String identityHint, String identity, Socket socket) {
return null;
}
// Missing method from the PSKKeyManager interface:
// SecretKey getKey(String identityHint, String identity, SSLEngine engine);
}
/**
* {@link KeyManager} which exposes all methods of the {@link PSKKeyManager} interface but does
* not implement the interface.
*/
@SuppressWarnings("unused")
private static class KeyManagerOfferingAllPSKKeyManagerMethodsWithExactReturnTypes
implements KeyManager {
public String chooseServerKeyIdentityHint(Socket socket) {
return null;
}
public String chooseServerKeyIdentityHint(SSLEngine engine) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, Socket socket) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, SSLEngine engine) {
return null;
}
public SecretKey getKey(String identityHint, String identity, Socket socket) {
return null;
}
public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
return null;
}
}
/**
* {@link KeyManager} which exposes all methods of the {@link PSKKeyManager} interface but does
* not implement the interface. Additionally, the return types of some methods are different
* but compatible with the {@code PSKKeyManager} interface.
*/
@SuppressWarnings("unused")
private static class KeyManagerOfferingAllPSKKeyManagerMethodsWithCompatibleReturnTypes
implements KeyManager {
public String chooseServerKeyIdentityHint(Socket socket) {
return null;
}
public String chooseServerKeyIdentityHint(SSLEngine engine) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, Socket socket) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, SSLEngine engine) {
return null;
}
// PSKKeyManager's return type: SecretKey
public SecretKeySpec getKey(String identityHint, String identity, Socket socket) {
return null;
}
// PSKKeyManager's return type: SecretKey
public SecretKeySpec getKey(String identityHint, String identity, SSLEngine engine) {
return null;
}
}
/**
* {@link KeyManager} which exposes all methods of the {@link PSKKeyManager} interface but does
* not implement the interface. Additionally, the return types of some methods are incompatible
* with the {@code PSKKeyManager} interface.
*/
@SuppressWarnings("unused")
private static class KeyManagerOfferingAllPSKKeyManagerMethodsWithIncompatibleReturnTypes
implements KeyManager {
public String chooseServerKeyIdentityHint(Socket socket) {
return null;
}
public String chooseServerKeyIdentityHint(SSLEngine engine) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, Socket socket) {
return null;
}
public String chooseClientKeyIdentity(String identityHint, SSLEngine engine) {
return null;
}
public SecretKey getKey(String identityHint, String identity, Socket socket) {
return null;
}
// PSKKeyManager's return type: SecretKey
public Key getKey(String identityHint, String identity, SSLEngine engine) {
return null;
}
}
static class MockInvocationHandler implements InvocationHandler {
Object returnValue;
Method lastInvokedMethod;
Object[] lastInvokedMethodArgs;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
lastInvokedMethod = method;
lastInvokedMethodArgs = args;
return returnValue;
}
}
}