1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.conscrypt.javax.crypto;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNull;
22import static org.junit.Assert.assertSame;
23import static org.junit.Assert.assertTrue;
24import static org.junit.Assert.fail;
25
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Method;
28import java.security.GeneralSecurityException;
29import java.security.InvalidAlgorithmParameterException;
30import java.security.InvalidKeyException;
31import java.security.KeyFactory;
32import java.security.NoSuchAlgorithmException;
33import java.security.PrivateKey;
34import java.security.Provider;
35import java.security.PublicKey;
36import java.security.Security;
37import java.security.interfaces.ECPrivateKey;
38import java.security.interfaces.ECPublicKey;
39import java.security.spec.ECGenParameterSpec;
40import java.security.spec.PKCS8EncodedKeySpec;
41import java.security.spec.X509EncodedKeySpec;
42import java.util.ArrayList;
43import java.util.Arrays;
44import java.util.Comparator;
45import java.util.List;
46import javax.crypto.KeyAgreement;
47import javax.crypto.SecretKey;
48import javax.crypto.ShortBufferException;
49import junit.framework.AssertionFailedError;
50import org.conscrypt.Conscrypt;
51import org.conscrypt.TestUtils;
52import org.junit.AfterClass;
53import org.junit.BeforeClass;
54import org.junit.Test;
55import org.junit.runner.RunWith;
56import org.junit.runners.JUnit4;
57import dalvik.system.VMRuntime;
58import sun.security.jca.Providers;
59
60/**
61 * Tests for all registered Elliptic Curve Diffie-Hellman {@link KeyAgreement} providers.
62 */
63@RunWith(JUnit4.class)
64public class ECDHKeyAgreementTest {
65
66    // BEGIN Android-Added: Allow access to deprecated BC algorithms.
67    // Allow access to deprecated BC algorithms in this test, so we can ensure they
68    // continue to work
69    @BeforeClass
70    public static void enableDeprecatedAlgorithms() {
71        Providers.setMaximumAllowableApiLevelForBcDeprecation(
72                VMRuntime.getRuntime().getTargetSdkVersion());
73    }
74
75    @AfterClass
76    public static void restoreDeprecatedAlgorithms() {
77        Providers.setMaximumAllowableApiLevelForBcDeprecation(
78                Providers.DEFAULT_MAXIMUM_ALLOWABLE_TARGET_API_LEVEL_FOR_BC_DEPRECATION);
79    }
80    // END Android-Added: Allow access to deprecated BC algorithms.
81
82    // Two key pairs and the resulting shared secret for the Known Answer Test
83    private static final byte[] KAT_PUBLIC_KEY1_X509 = TestUtils.decodeHex(
84            "3059301306072a8648ce3d020106082a8648ce3d030107034200049fc2f71f85446b1371244491d83"
85            + "9cf97b5d27cedbb04d2c0058b59709df3a216e6b4ca1b2d622588c5a0e6968144a8965e816a600c"
86            + "05305a1da3df2bf02b41d1");
87    private static final byte[] KAT_PRIVATE_KEY1_PKCS8 = TestUtils.decodeHex(
88            "308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420e1e683003"
89            + "c8b963a92742e5f955ce7fddc81d0c3ae9b149d6af86a0cacb2271ca00a06082a8648ce3d030107"
90            + "a144034200049fc2f71f85446b1371244491d839cf97b5d27cedbb04d2c0058b59709df3a216e6b"
91            + "4ca1b2d622588c5a0e6968144a8965e816a600c05305a1da3df2bf02b41d1");
92
93    private static final byte[] KAT_PUBLIC_KEY2_X509 = TestUtils.decodeHex(
94            "3059301306072a8648ce3d020106082a8648ce3d03010703420004358efb6d91e5bbcae21774af3f6"
95            + "d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7a1d1bb249f92861c7c9153fff33f45ab5b171eb"
96            + "e8cad741125e6bb4fc6b07");
97    private static final byte[] KAT_PRIVATE_KEY2_PKCS8 = TestUtils.decodeHex(
98            "308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104202b1810a69"
99            + "e12b74d50bf0343168f705f0104f76299855268aa526fdb31e6eec0a00a06082a8648ce3d030107"
100            + "a14403420004358efb6d91e5bbcae21774af3f6d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7"
101            + "a1d1bb249f92861c7c9153fff33f45ab5b171ebe8cad741125e6bb4fc6b07");
102
103    private static final byte[] KAT_SECRET =
104            TestUtils.decodeHex("4faa0594c0e773eb26c8df2163af2443e88aab9578b9e1f324bc61e42d222783");
105
106    private static final ECPublicKey KAT_PUBLIC_KEY1;
107    private static final ECPrivateKey KAT_PRIVATE_KEY1;
108    private static final ECPublicKey KAT_PUBLIC_KEY2;
109    private static final ECPrivateKey KAT_PRIVATE_KEY2;
110    static {
111        try {
112            KAT_PUBLIC_KEY1 = getPublicKey(KAT_PUBLIC_KEY1_X509);
113            KAT_PRIVATE_KEY1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8);
114            KAT_PUBLIC_KEY2 = getPublicKey(KAT_PUBLIC_KEY2_X509);
115            KAT_PRIVATE_KEY2 = getPrivateKey(KAT_PRIVATE_KEY2_PKCS8);
116        } catch (Exception e) {
117            throw new RuntimeException("Failed to decode KAT key pairs using default provider", e);
118        }
119    }
120
121    @BeforeClass
122    public static void setUp() {
123        TestUtils.assumeAllowsUnsignedCrypto();
124    }
125
126    /**
127     * Performs a known-answer test of the shared secret for all permutations of {@code Providers}
128     * of: first key pair, second key pair, and the {@code KeyAgreement}. This is to check that
129     * the {@code KeyAgreement} instances work with keys of all registered providers.
130     */
131    @Test
132    public void testKnownAnswer() throws Exception {
133        for (Provider keyFactoryProvider1 : getKeyFactoryProviders()) {
134            ECPrivateKey privateKey1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8, keyFactoryProvider1);
135            ECPublicKey publicKey1 = getPublicKey(KAT_PUBLIC_KEY1_X509, keyFactoryProvider1);
136            for (Provider keyFactoryProvider2 : getKeyFactoryProviders()) {
137                ECPrivateKey privateKey2 =
138                        getPrivateKey(KAT_PRIVATE_KEY2_PKCS8, keyFactoryProvider2);
139                ECPublicKey publicKey2 =
140                        getPublicKey(KAT_PUBLIC_KEY2_X509, keyFactoryProvider2);
141                for (Provider keyAgreementProvider : getKeyAgreementProviders()) {
142                    try {
143                        testKnownAnswer(publicKey1, privateKey1, publicKey2, privateKey2,
144                                keyAgreementProvider);
145                    } catch (Throwable e) {
146                        throw new RuntimeException(getClass().getSimpleName() + ".testKnownAnswer("
147                                + keyFactoryProvider1.getName()
148                                + ", " + keyFactoryProvider2.getName()
149                                + ", " + keyAgreementProvider.getName() + ")",
150                                e);
151                    }
152                }
153            }
154        }
155    }
156
157    void testKnownAnswer(
158            ECPublicKey publicKey1, ECPrivateKey privateKey1,
159            ECPublicKey publicKey2, ECPrivateKey privateKey2,
160            Provider keyAgreementProvider) throws Exception {
161        assertTrue(Arrays.equals(
162                KAT_SECRET, generateSecret(keyAgreementProvider, privateKey1, publicKey2)));
163        assertTrue(Arrays.equals(
164                KAT_SECRET, generateSecret(keyAgreementProvider, privateKey2, publicKey1)));
165    }
166
167    @Test
168    public void testGetAlgorithm() throws Exception {
169        invokeCallingMethodForEachKeyAgreementProvider();
170    }
171
172    void testGetAlgorithm(Provider provider) throws Exception {
173        assertEquals("ECDH", getKeyAgreement(provider).getAlgorithm());
174    }
175
176    @Test
177    public void testGetProvider() throws Exception {
178        invokeCallingMethodForEachKeyAgreementProvider();
179    }
180
181    void testGetProvider(Provider provider) throws Exception {
182        assertSame(provider, getKeyAgreement(provider).getProvider());
183    }
184
185    @Test
186    public void testInit_withNullPrivateKey() throws Exception {
187        invokeCallingMethodForEachKeyAgreementProvider();
188    }
189
190    void testInit_withNullPrivateKey(Provider provider) throws Exception {
191        KeyAgreement keyAgreement = getKeyAgreement(provider);
192        try {
193            keyAgreement.init(null);
194            fail();
195        } catch (InvalidKeyException expected) {}
196    }
197
198    @Test
199    public void testInit_withUnsupportedPrivateKeyType() throws Exception {
200        invokeCallingMethodForEachKeyAgreementProvider();
201    }
202
203    void testInit_withUnsupportedPrivateKeyType(Provider provider) throws Exception {
204        KeyAgreement keyAgreement = getKeyAgreement(provider);
205        try {
206            keyAgreement.init(KAT_PUBLIC_KEY1);
207            fail();
208        } catch (InvalidKeyException expected) {}
209    }
210
211    @Test
212    public void testInit_withUnsupportedAlgorithmParameterSpec() throws Exception {
213        invokeCallingMethodForEachKeyAgreementProvider();
214    }
215
216    void testInit_withUnsupportedAlgorithmParameterSpec(Provider provider) throws Exception {
217        try {
218            getKeyAgreement(provider).init(KAT_PRIVATE_KEY1, new ECGenParameterSpec("prime256v1"));
219            fail();
220        } catch (InvalidAlgorithmParameterException expected) {}
221    }
222
223    @Test
224    public void testDoPhase_whenNotInitialized() throws Exception {
225        invokeCallingMethodForEachKeyAgreementProvider();
226    }
227
228    void testDoPhase_whenNotInitialized(Provider provider) throws Exception {
229        try {
230            getKeyAgreement(provider).doPhase(KAT_PUBLIC_KEY1, true);
231            fail();
232        } catch (IllegalStateException expected) {}
233    }
234
235    @Test
236    public void testDoPhaseReturnsNull() throws Exception {
237        invokeCallingMethodForEachKeyAgreementProvider();
238    }
239
240    void testDoPhaseReturnsNull(Provider provider) throws Exception {
241        KeyAgreement keyAgreement = getKeyAgreement(provider);
242        keyAgreement.init(KAT_PRIVATE_KEY1);
243        assertNull(keyAgreement.doPhase(KAT_PUBLIC_KEY2, true));
244    }
245
246    @Test
247    public void testDoPhase_withPhaseWhichIsNotLast() throws Exception {
248        invokeCallingMethodForEachKeyAgreementProvider();
249    }
250
251    void testDoPhase_withPhaseWhichIsNotLast(Provider provider) throws Exception {
252        KeyAgreement keyAgreement = getKeyAgreement(provider);
253        keyAgreement.init(KAT_PRIVATE_KEY1);
254        try {
255            keyAgreement.doPhase(KAT_PUBLIC_KEY2, false);
256            fail();
257        } catch (IllegalStateException expected) {}
258    }
259
260    @Test
261    public void testDoPhase_withNullKey() throws Exception {
262        invokeCallingMethodForEachKeyAgreementProvider();
263    }
264
265    void testDoPhase_withNullKey(Provider provider) throws Exception {
266        KeyAgreement keyAgreement = getKeyAgreement(provider);
267        keyAgreement.init(KAT_PRIVATE_KEY1);
268        try {
269            keyAgreement.doPhase(null, true);
270            fail();
271        } catch (InvalidKeyException expected) {}
272    }
273
274    @Test
275    public void testDoPhase_withInvalidKeyType() throws Exception {
276        invokeCallingMethodForEachKeyAgreementProvider();
277    }
278
279    void testDoPhase_withInvalidKeyType(Provider provider) throws Exception {
280        KeyAgreement keyAgreement = getKeyAgreement(provider);
281        keyAgreement.init(KAT_PRIVATE_KEY1);
282        try {
283            keyAgreement.doPhase(KAT_PRIVATE_KEY1, true);
284            fail();
285        } catch (InvalidKeyException expected) {}
286    }
287
288    @Test
289    public void testGenerateSecret_withNullOutputBuffer() throws Exception {
290        invokeCallingMethodForEachKeyAgreementProvider();
291    }
292
293    void testGenerateSecret_withNullOutputBuffer(Provider provider) throws Exception {
294        KeyAgreement keyAgreement = getKeyAgreement(provider);
295        keyAgreement.init(KAT_PRIVATE_KEY1);
296        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
297        try {
298            keyAgreement.generateSecret(null, 0);
299            fail();
300        } catch (NullPointerException expected) {}
301    }
302
303    @Test
304    public void testGenerateSecret_withBufferOfTheRightSize() throws Exception {
305        invokeCallingMethodForEachKeyAgreementProvider();
306    }
307
308    void testGenerateSecret_withBufferOfTheRightSize(Provider provider) throws Exception {
309        KeyAgreement keyAgreement = getKeyAgreement(provider);
310        keyAgreement.init(KAT_PRIVATE_KEY1);
311        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
312
313        byte[] buffer = new byte[KAT_SECRET.length];
314        int secretLengthBytes = keyAgreement.generateSecret(buffer, 0);
315        assertEquals(KAT_SECRET.length, secretLengthBytes);
316        assertTrue(Arrays.equals(KAT_SECRET, buffer));
317    }
318
319    @Test
320    public void testGenerateSecret_withLargerThatNeededBuffer() throws Exception {
321        invokeCallingMethodForEachKeyAgreementProvider();
322    }
323
324    void testGenerateSecret_withLargerThatNeededBuffer(Provider provider) throws Exception {
325        KeyAgreement keyAgreement = getKeyAgreement(provider);
326        keyAgreement.init(KAT_PRIVATE_KEY1);
327        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
328
329        // Place the shared secret in the middle of the larger buffer and check that only that
330        // part of the buffer is affected.
331        byte[] buffer = new byte[KAT_SECRET.length + 2];
332        buffer[0] = (byte) 0x85; // arbitrary canary value
333        buffer[buffer.length - 1] = (byte) 0x3b; // arbitrary canary value
334        int secretLengthBytes = keyAgreement.generateSecret(buffer, 1);
335        assertEquals(KAT_SECRET.length, secretLengthBytes);
336        assertEquals((byte) 0x85, buffer[0]);
337        assertEquals((byte) 0x3b, buffer[buffer.length - 1]);
338        byte[] secret = new byte[KAT_SECRET.length];
339        System.arraycopy(buffer, 1, secret, 0, secret.length);
340        assertTrue(Arrays.equals(KAT_SECRET, secret));
341    }
342
343    @Test
344    public void testGenerateSecret_withSmallerThanNeededBuffer() throws Exception {
345        invokeCallingMethodForEachKeyAgreementProvider();
346    }
347
348    void testGenerateSecret_withSmallerThanNeededBuffer(Provider provider) throws Exception {
349        KeyAgreement keyAgreement = getKeyAgreement(provider);
350        keyAgreement.init(KAT_PRIVATE_KEY1);
351        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
352        try {
353            // Although the buffer is big enough (1024 bytes) the shared secret should be placed
354            // at offset 1020 thus leaving only 4 bytes for the secret, which is not enough.
355            keyAgreement.generateSecret(new byte[1024], 1020);
356            fail();
357        } catch (ShortBufferException expected) {}
358    }
359
360    @Test
361    public void testGenerateSecret_withoutBuffer() throws Exception {
362        invokeCallingMethodForEachKeyAgreementProvider();
363    }
364
365    void testGenerateSecret_withoutBuffer(Provider provider) throws Exception {
366        KeyAgreement keyAgreement = getKeyAgreement(provider);
367        keyAgreement.init(KAT_PRIVATE_KEY2);
368        keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
369
370        byte[] secret = keyAgreement.generateSecret();
371        assertTrue(Arrays.equals(KAT_SECRET, secret));
372    }
373
374    @Test
375    public void testGenerateSecret_withAlgorithm() throws Exception {
376        invokeCallingMethodForEachKeyAgreementProvider();
377    }
378
379    void testGenerateSecret_withAlgorithm(Provider provider) throws Exception {
380        KeyAgreement keyAgreement = getKeyAgreement(provider);
381        keyAgreement.init(KAT_PRIVATE_KEY2);
382        keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
383
384        try {
385            SecretKey key = keyAgreement.generateSecret("AES");
386            assertEquals("AES", key.getAlgorithm());
387            // The check below will need to change if it's a hardware-backed key.
388            // We'll have to encrypt a known plaintext and check that the ciphertext is as
389            // expected.
390            assertTrue(Arrays.equals(KAT_SECRET, key.getEncoded()));
391        } catch (NoSuchAlgorithmException e) {
392            // This provider doesn't support AES, that's fine as long as it's not Conscrypt
393            assertFalse(Conscrypt.isConscrypt(provider));
394        }
395    }
396
397    private void invokeCallingMethodForEachKeyAgreementProvider() throws Exception {
398        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
399        String callingMethodName = null;
400        for (int i = 0; i < stackTrace.length; i++) {
401            if ("invokeCallingMethodForEachKeyAgreementProvider".equals(
402                    stackTrace[i].getMethodName())) {
403                callingMethodName = stackTrace[i + 1].getMethodName();
404            }
405        }
406        if (callingMethodName == null) {
407            throw new RuntimeException("Failed to deduce calling method name from stack trace");
408        }
409
410        String invokedMethodName = callingMethodName;
411        Method method;
412        try {
413            method = getClass().getDeclaredMethod(invokedMethodName, Provider.class);
414        } catch (NoSuchMethodError e) {
415            throw new AssertionFailedError("Failed to find per-Provider test method "
416                    + getClass().getSimpleName() + "." + invokedMethodName + "(Provider)");
417        }
418
419        for (Provider provider : getKeyAgreementProviders()) {
420            try {
421                method.invoke(this, provider);
422            } catch (InvocationTargetException e) {
423                throw new RuntimeException(getClass().getSimpleName() + "." + invokedMethodName
424                        + "(provider: " + provider.getName() + ") failed",
425                        e.getCause());
426            }
427        }
428    }
429
430    private static Provider[] getKeyAgreementProviders() {
431        Provider[] providers = Security.getProviders("KeyAgreement.ECDH");
432        if (providers == null) {
433            return new Provider[0];
434        }
435        // Sort providers by name to guarantee deterministic order in which providers are used in
436        // the tests.
437        return sortByName(providers);
438    }
439
440    private static Provider[] getKeyFactoryProviders() {
441        Provider[] providers = Security.getProviders("KeyFactory.EC");
442        if (providers == null) {
443            return new Provider[0];
444        }
445
446        // Do not test AndroidKeyStore's KeyFactory. It only handles Android Keystore-backed keys.
447        // It's OKish not to test AndroidKeyStore's KeyFactory here because it's tested by
448        // cts/tests/test/keystore.
449        List<Provider> filteredProvidersList = new ArrayList<Provider>(providers.length);
450        for (Provider provider : providers) {
451            if ("AndroidKeyStore".equals(provider.getName())) {
452                continue;
453            }
454            filteredProvidersList.add(provider);
455        }
456        providers = filteredProvidersList.toArray(new Provider[filteredProvidersList.size()]);
457
458        // Sort providers by name to guarantee deterministic order in which providers are used in
459        // the tests.
460        return sortByName(providers);
461    }
462
463    private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey, Provider provider)
464            throws GeneralSecurityException {
465        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
466        return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
467    }
468
469    private static ECPublicKey getPublicKey(byte[] x509EncodedKey, Provider provider)
470            throws GeneralSecurityException {
471        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
472        return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
473    }
474
475    private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey)
476            throws GeneralSecurityException {
477        KeyFactory keyFactory = KeyFactory.getInstance("EC");
478        return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
479    }
480
481    private static ECPublicKey getPublicKey(byte[] x509EncodedKey)
482            throws GeneralSecurityException {
483        KeyFactory keyFactory = KeyFactory.getInstance("EC");
484        return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
485    }
486
487    private static KeyAgreement getKeyAgreement(Provider provider) throws NoSuchAlgorithmException {
488        return KeyAgreement.getInstance("ECDH", provider);
489    }
490
491    private static byte[] generateSecret(
492            Provider keyAgreementProvider, PrivateKey privateKey, PublicKey publicKey)
493            throws GeneralSecurityException {
494        KeyAgreement keyAgreement = getKeyAgreement(keyAgreementProvider);
495        keyAgreement.init(privateKey);
496        keyAgreement.doPhase(publicKey, true);
497        return keyAgreement.generateSecret();
498    }
499
500    private static Provider[] sortByName(Provider[] providers) {
501        Arrays.sort(providers, new Comparator<Provider>() {
502            @Override
503            public int compare(Provider lhs, Provider rhs) {
504                return lhs.getName().compareTo(rhs.getName());
505            }
506        });
507        return providers;
508    }
509}
510