1 /*
2  * Copyright (C) 2022 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 
17 package android.keystore.cts;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertThrows;
22 import static org.junit.Assert.fail;
23 import static org.junit.Assume.assumeTrue;
24 
25 import android.content.Context;
26 import android.keystore.cts.util.ImportedKey;
27 import android.keystore.cts.util.TestUtils;
28 import android.security.keystore.KeyGenParameterSpec;
29 import android.security.keystore.KeyProperties;
30 import android.security.keystore.KeyProtection;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import com.android.compatibility.common.util.ApiTest;
36 
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.security.InvalidAlgorithmParameterException;
41 import java.security.InvalidKeyException;
42 import java.security.KeyPair;
43 import java.security.KeyPairGenerator;
44 import java.security.KeyStore;
45 import java.security.KeyStoreException;
46 import java.security.NoSuchAlgorithmException;
47 import java.security.NoSuchProviderException;
48 import java.security.Signature;
49 import java.security.SignatureException;
50 import java.security.interfaces.EdECPublicKey;
51 import java.security.spec.ECGenParameterSpec;
52 import java.security.spec.InvalidKeySpecException;
53 import java.security.spec.NamedParameterSpec;
54 import java.util.Arrays;
55 import java.util.Base64;
56 
57 import javax.crypto.KeyAgreement;
58 
59 @RunWith(AndroidJUnit4.class)
60 public class Curve25519Test {
deleteEntry(String entry)61     private void deleteEntry(String entry) {
62         try {
63             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
64             keyStore.deleteEntry(entry);
65         } catch (KeyStoreException e) {
66             // Skipped
67         }
68     }
69 
getContext()70     private Context getContext() {
71         return InstrumentationRegistry.getInstrumentation().getTargetContext();
72     }
73 
74     @Test
x25519KeyAgreementTest()75     public void x25519KeyAgreementTest() throws NoSuchAlgorithmException, NoSuchProviderException,
76             InvalidAlgorithmParameterException, InvalidKeySpecException, InvalidKeyException {
77         KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
78         // Aliases for both keys.
79         final String firstKeyAlias = "x25519-alias";
80         deleteEntry(firstKeyAlias);
81         final String secondKeyAlias = "x25519-alias-second";
82         deleteEntry(secondKeyAlias);
83 
84         // Generate first x25519 key pair.
85         KeyGenParameterSpec firstKeySpec = new KeyGenParameterSpec.Builder(firstKeyAlias,
86                 KeyProperties.PURPOSE_AGREE_KEY)
87                 .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
88         kpg.initialize(firstKeySpec);
89         KeyPair firstKeyPair = kpg.generateKeyPair();
90 
91         // Generate second x25519 key pair.
92         KeyGenParameterSpec secondKeySpec = new KeyGenParameterSpec.Builder(secondKeyAlias,
93                 KeyProperties.PURPOSE_AGREE_KEY)
94                 .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
95         kpg.initialize(secondKeySpec);
96         KeyPair secondKeyPair = kpg.generateKeyPair();
97 
98         // Attempt a key agreement with the private key from the first key pair and the public
99         // key from the second key pair.
100         KeyAgreement secondKa = KeyAgreement.getInstance("XDH");
101         secondKa.init(firstKeyPair.getPrivate());
102         secondKa.doPhase(secondKeyPair.getPublic(), true);
103         byte[] secondSecret = secondKa.generateSecret();
104 
105         // Attempt a key agreement "the other way around": using the private key from the second
106         // key pair and the public key from the first key pair.
107         KeyAgreement firstKa = KeyAgreement.getInstance("XDH");
108         firstKa.init(secondKeyPair.getPrivate());
109         firstKa.doPhase(firstKeyPair.getPublic(), true);
110         byte[] firstSecret = firstKa.generateSecret();
111 
112         // Both secrets being equal means the key agreement was successful.
113         assertThat(Arrays.compare(firstSecret, secondSecret)).isEqualTo(0);
114     }
115 
116     @Test
117     @ApiTest(apis = {"java.security.KeyStore#setEntry", "javax.crypto.KeyAgreement#doPhase"})
x25519KeyImportAndAgreementTest()118     public void x25519KeyImportAndAgreementTest() throws Exception {
119         final String alias = "import-x25519";
120         deleteEntry(alias);
121 
122         KeyProtection importParams = new KeyProtection.Builder(KeyProperties.PURPOSE_AGREE_KEY)
123                 .build();
124         ImportedKey importedKey = TestUtils.importIntoAndroidKeyStore(
125                     alias,
126                     getContext(),
127                     R.raw.ec_key8_x25519,
128                     R.raw.ec_key8_x25519_cert,
129                     importParams);
130 
131         assertThat(importedKey).isNotNull();
132         //Second key generate from AndroidOpenSSL
133         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH");
134         KeyPair secondKeyPair = kpg.generateKeyPair();
135 
136         // Attempt a key agreement with the private key from the first key pair and the public
137         // key from the second key pair.
138         KeyAgreement secondKa = KeyAgreement.getInstance("XDH");
139         secondKa.init(importedKey.getKeystoreBackedKeyPair().getPrivate());
140         secondKa.doPhase(secondKeyPair.getPublic(), true);
141         byte[] secondSecret = secondKa.generateSecret();
142 
143         // Attempt a key agreement "the other way around": using the private key from the second
144         // key pair and the public key from the first key pair.
145         KeyAgreement firstKa = KeyAgreement.getInstance("XDH");
146         firstKa.init(secondKeyPair.getPrivate());
147         firstKa.doPhase(importedKey.getKeystoreBackedKeyPair().getPublic(), true);
148         byte[] firstSecret = firstKa.generateSecret();
149 
150         // Both secrets being equal means the key agreement was successful.
151         assertThat(Arrays.compare(firstSecret, secondSecret)).isEqualTo(0);
152     }
153 
154     @Test
ed25519KeyGenerationAndSigningTest()155     public void ed25519KeyGenerationAndSigningTest()
156             throws NoSuchAlgorithmException, NoSuchProviderException,
157             InvalidAlgorithmParameterException, InvalidKeyException, SignatureException {
158         KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
159         final String alias = "ed25519-alias";
160         deleteEntry(alias);
161 
162         KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
163                 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
164                 .setAlgorithmParameterSpec(new ECGenParameterSpec("ed25519"))
165                 .setDigests(KeyProperties.DIGEST_NONE).build();
166         kpg.initialize(keySpec);
167 
168         KeyPair kp = kpg.generateKeyPair();
169         assertThat(kp.getPublic()).isInstanceOf(EdECPublicKey.class);
170 
171         byte[] data = "helloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".getBytes();
172         Signature signer = Signature.getInstance("Ed25519");
173         signer.initSign(kp.getPrivate());
174         signer.update(data);
175         byte[] sigBytes = signer.sign();
176         assertThat(sigBytes.length).isEqualTo(64);
177         EdECPublicKey publicKey = (EdECPublicKey) kp.getPublic();
178         android.util.Log.i("Curve25519Test", "Manually validate: Payload "
179                 + Base64.getEncoder().encodeToString(data) + " encoded key: "
180                 + Base64.getEncoder().encodeToString(kp.getPublic().getEncoded())
181                 + " signature: " + Base64.getEncoder().encodeToString(sigBytes));
182 
183         //TODO: Verify signature over the data when Conscrypt supports validating Ed25519
184         // signatures.
185     }
186 
187     @Test
testX25519CannotBeUsedForSigning()188     public void testX25519CannotBeUsedForSigning()
189             throws NoSuchAlgorithmException, NoSuchProviderException {
190         KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
191         final String alias = "x25519-baduse-alias";
192         deleteEntry(alias);
193 
194         KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
195                 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
196                 .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
197 
198         assertThrows(InvalidAlgorithmParameterException.class, () -> kpg.initialize(keySpec));
199     }
200 
201     @Test
testEd25519CannotBeUsedForKeyExchange()202     public void testEd25519CannotBeUsedForKeyExchange() throws NoSuchAlgorithmException,
203             NoSuchProviderException {
204         KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
205         final String alias = "ed25519-baduse-alias";
206         deleteEntry(alias);
207 
208         KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
209                 KeyProperties.PURPOSE_AGREE_KEY)
210                 .setAlgorithmParameterSpec(new ECGenParameterSpec("ed25519")).build();
211 
212         assertThrows(InvalidAlgorithmParameterException.class, () -> kpg.initialize(keySpec));
213     }
214 
215     @Test
x25519CannotCreateKeyUsingKPGWithNamedParameterSpec()216     public void x25519CannotCreateKeyUsingKPGWithNamedParameterSpec()
217             throws NoSuchAlgorithmException, NoSuchProviderException,
218             InvalidAlgorithmParameterException {
219         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore");
220 
221         NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
222         try {
223             kpg.initialize(paramSpec);
224             fail("Should not be able to generate keys using NamedParameterSpec");
225         } catch (IllegalArgumentException e) {
226             assertThat(e.getMessage()).contains("cannot be initialized using NamedParameterSpec");
227         }
228     }
229 
230     @Test
ed25519CannotCreateKeyUsingKPGWithNamedParameterSpec()231     public void ed25519CannotCreateKeyUsingKPGWithNamedParameterSpec()
232             throws NoSuchAlgorithmException, NoSuchProviderException,
233             InvalidAlgorithmParameterException {
234         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore");
235 
236         NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
237         try {
238             kpg.initialize(paramSpec);
239             fail("Should not be able to generate keys using NamedParameterSpec");
240         } catch (IllegalArgumentException e) {
241             assertThat(e.getMessage()).contains("cannot be initialized using NamedParameterSpec");
242         }
243     }
244 
245     @Test
x25519KeyPairGenWithInvalidCurve()246     public void x25519KeyPairGenWithInvalidCurve() throws NoSuchAlgorithmException,
247             NoSuchProviderException {
248         assumeTrue("AndroidKeyStore supports key generation of curve Ed25519 from"
249                         + " Android V preview", TestUtils.isEd25519AlgorithmExpectedToSupport());
250 
251         // Create KeyPairGenerator using XDH algorithm.
252         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore");
253         final String firstKeyAlias = "x25519-alias";
254         deleteEntry(firstKeyAlias);
255 
256         // Generate x25519 key pair.
257         KeyGenParameterSpec keyGenSpec = new KeyGenParameterSpec.Builder(firstKeyAlias,
258                 KeyProperties.PURPOSE_AGREE_KEY)
259                 .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")).build();
260         assertThrows("XDH Key generation should accept only X25519 curves.",
261                 InvalidAlgorithmParameterException.class, () -> kpg.initialize(keyGenSpec));
262     }
263 
264     @Test
ed25519KeyPairGenWithInvalidCurve()265     public void ed25519KeyPairGenWithInvalidCurve() throws NoSuchAlgorithmException,
266             NoSuchProviderException {
267         assumeTrue("AndroidKeyStore supports key generation of curve Ed25519 from"
268                         + " Android V preview", TestUtils.isEd25519AlgorithmExpectedToSupport());
269 
270         // Create KeyPairGenerator using Ed25519 algorithm.
271         KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", "AndroidKeyStore");
272         final String secondKeyAlias = "ed25519-alias";
273         deleteEntry(secondKeyAlias);
274 
275         // Generate ed25519 key pair.
276         KeyGenParameterSpec keyGenSpec = new KeyGenParameterSpec.Builder(secondKeyAlias,
277                 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
278                 .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")).build();
279         assertThrows("Ed25519 Key generation should accept only Ed25519 curves.",
280                 InvalidAlgorithmParameterException.class, () -> kpg.initialize(keyGenSpec));
281     }
282 }
283