| /* |
| * 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; |
| } |
| } |
| } |