Commit 7e3bd54e authored by Alex Klyubin's avatar Alex Klyubin Committed by Gerrit Code Review
Browse files

Merge "Support duck-typed PSKKeyManager instances in SSLContext.init."

parents 4372784a 0b4bf3b3
/*
* Copyright 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 java.lang.reflect.Method;
import java.net.Socket;
import javax.crypto.SecretKey;
import javax.net.ssl.SSLEngine;
/**
* Reflection-based {@link PSKKeyManager} adaptor for objects which expose all the methods of the
* {@code PSKKeyManager} interface but do not implement the interface.
*
* <p>This is expected to be useful on platforms where there are multiple instances of the
* {@code PSKKeyManager} interface.
*/
class DuckTypedPSKKeyManager implements PSKKeyManager {
private final Object mDelegate;
private DuckTypedPSKKeyManager(Object delegate) {
mDelegate = delegate;
}
/**
* Gets an instance of {@code DuckTypedPSKKeyManager} which delegates all invocations of methods
* of the {@link PSKKeyManager} interface to the same methods of the provided object.
*
* @throws NoSuchMethodException if {@code obj} does not implement a method of the
* {@code PSKKeyManager} interface.
*/
public static DuckTypedPSKKeyManager getInstance(Object obj) throws NoSuchMethodException {
Class<?> sourceClass = obj.getClass();
for (Method targetMethod : PSKKeyManager.class.getMethods()) {
if (targetMethod.isSynthetic()) {
continue;
}
// Check that obj exposes the target method (same name and parameter types)
Method sourceMethod =
sourceClass.getMethod(targetMethod.getName(), targetMethod.getParameterTypes());
// Check that the return type of obj's method matches the target method.
Class<?> sourceReturnType = sourceMethod.getReturnType();
Class<?> targetReturnType = targetMethod.getReturnType();
if (!targetReturnType.isAssignableFrom(sourceReturnType)) {
throw new NoSuchMethodException(sourceMethod + " return value (" + sourceReturnType
+ ") incompatible with target return value (" + targetReturnType + ")");
}
}
return new DuckTypedPSKKeyManager(obj);
}
@Override
public String chooseServerKeyIdentityHint(Socket socket) {
try {
return (String) mDelegate.getClass()
.getMethod("chooseServerKeyIdentityHint", Socket.class)
.invoke(mDelegate, socket);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke chooseServerKeyIdentityHint", e);
}
}
@Override
public String chooseServerKeyIdentityHint(SSLEngine engine) {
try {
return (String) mDelegate.getClass()
.getMethod("chooseServerKeyIdentityHint", SSLEngine.class)
.invoke(mDelegate, engine);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke chooseServerKeyIdentityHint", e);
}
}
@Override
public String chooseClientKeyIdentity(String identityHint, Socket socket) {
try {
return (String) mDelegate.getClass()
.getMethod("chooseClientKeyIdentity", String.class, Socket.class)
.invoke(mDelegate, identityHint, socket);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke chooseClientKeyIdentity", e);
}
}
@Override
public String chooseClientKeyIdentity(String identityHint, SSLEngine engine) {
try {
return (String) mDelegate.getClass()
.getMethod("chooseClientKeyIdentity", String.class, SSLEngine.class)
.invoke(mDelegate, identityHint, engine);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke chooseClientKeyIdentity", e);
}
}
@Override
public SecretKey getKey(String identityHint, String identity, Socket socket) {
try {
return (SecretKey) mDelegate.getClass()
.getMethod("getKey", String.class, String.class, Socket.class)
.invoke(mDelegate, identityHint, identity, socket);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke getKey", e);
}
}
@Override
public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
try {
return (SecretKey) mDelegate.getClass()
.getMethod("getKey", String.class, String.class, SSLEngine.class)
.invoke(mDelegate, identityHint, identity, engine);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke getKey", e);
}
}
}
......@@ -847,6 +847,10 @@ public class SSLParametersImpl implements Cloneable {
for (KeyManager km : kms) {
if (km instanceof PSKKeyManager) {
return (PSKKeyManager)km;
} else if (km != null) {
try {
return DuckTypedPSKKeyManager.getInstance(km);
} catch (NoSuchMethodException ignored) {}
}
}
return null;
......
/*
* 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;
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment