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