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