1 /* 2 * Copyright (C) 2021 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.security.keystore2; 18 19 import android.hardware.security.keymint.Algorithm; 20 import android.hardware.security.keymint.KeyParameter; 21 import android.hardware.security.keymint.KeyPurpose; 22 import android.hardware.security.keymint.Tag; 23 import android.security.KeyStoreException; 24 import android.security.KeyStoreOperation; 25 import android.security.keystore.KeyStoreCryptoOperation; 26 27 import java.security.InvalidAlgorithmParameterException; 28 import java.security.InvalidKeyException; 29 import java.security.Key; 30 import java.security.NoSuchAlgorithmException; 31 import java.security.ProviderException; 32 import java.security.PublicKey; 33 import java.security.SecureRandom; 34 import java.security.interfaces.ECKey; 35 import java.security.spec.AlgorithmParameterSpec; 36 import java.util.ArrayList; 37 import java.util.List; 38 39 import javax.crypto.KeyAgreementSpi; 40 import javax.crypto.SecretKey; 41 import javax.crypto.ShortBufferException; 42 import javax.crypto.spec.SecretKeySpec; 43 44 /** 45 * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore. 46 * 47 * @hide 48 */ 49 public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi 50 implements KeyStoreCryptoOperation { 51 52 private static final String TAG = "AndroidKeyStoreKeyAgreementSpi"; 53 54 /** 55 * ECDH implementation. 56 * 57 * @hide 58 */ 59 public static class ECDH extends AndroidKeyStoreKeyAgreementSpi { ECDH()60 public ECDH() { 61 super(Algorithm.EC); 62 } 63 } 64 65 /** 66 * X25519 key agreement support. 67 * 68 * @hide 69 */ 70 public static class XDH extends AndroidKeyStoreKeyAgreementSpi { XDH()71 public XDH() { 72 super(Algorithm.EC); 73 } 74 } 75 76 private final int mKeymintAlgorithm; 77 78 // Fields below are populated by engineInit and should be preserved after engineDoFinal. 79 private AndroidKeyStorePrivateKey mKey; 80 private PublicKey mOtherPartyKey; 81 82 // Fields below are reset when engineDoFinal succeeds. 83 private KeyStoreOperation mOperation; 84 private long mOperationHandle; 85 AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm)86 protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) { 87 resetAll(); 88 89 mKeymintAlgorithm = keymintAlgorithm; 90 } 91 92 @Override engineInit(Key key, SecureRandom random)93 protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { 94 resetAll(); 95 96 if (key == null) { 97 throw new InvalidKeyException("key == null"); 98 } else if (!(key instanceof AndroidKeyStorePrivateKey)) { 99 throw new InvalidKeyException( 100 "Only Android KeyStore private keys supported. Key: " + key); 101 } 102 // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in 103 // ensureKeystoreOperationInitialized() below. 104 mKey = (AndroidKeyStorePrivateKey) key; 105 106 boolean success = false; 107 try { 108 ensureKeystoreOperationInitialized(); 109 success = true; 110 } finally { 111 if (!success) { 112 resetAll(); 113 } 114 } 115 } 116 117 @Override engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)118 protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) 119 throws InvalidKeyException, InvalidAlgorithmParameterException { 120 if (params != null) { 121 throw new InvalidAlgorithmParameterException( 122 "Unsupported algorithm parameters: " + params); 123 } 124 engineInit(key, random); 125 } 126 127 @Override engineDoPhase(Key key, boolean lastPhase)128 protected Key engineDoPhase(Key key, boolean lastPhase) 129 throws InvalidKeyException, IllegalStateException { 130 ensureKeystoreOperationInitialized(); 131 132 if (key == null) { 133 throw new InvalidKeyException("key == null"); 134 } else if (!(key instanceof PublicKey)) { 135 throw new InvalidKeyException("Only public keys supported. Key: " + key); 136 } else if (mKey instanceof ECKey && !(key instanceof ECKey) 137 /*&& !(mKey instanceof XECKey && key instanceof XECKey)*/) { 138 /** TODO This condition is temporary modified, because OpenSSL implementation does not 139 * implement OpenSSLX25519PublicKey from XECKey interface (b/214203951). 140 * This change has to revert once conscrypt implements OpenSSLX25519PublicKey from 141 * XECKey interface. 142 */ 143 throw new InvalidKeyException( 144 "Public and Private key should be of the same type."); 145 } else if (mKey instanceof ECKey 146 && !((ECKey) key).getParams().getCurve() 147 .equals(((ECKey) mKey).getParams().getCurve())) { 148 throw new InvalidKeyException( 149 "Public and Private key parameters should be same."); 150 } else if (!lastPhase) { 151 throw new IllegalStateException( 152 "Only one other party supported. lastPhase must be set to true."); 153 } else if (mOtherPartyKey != null) { 154 throw new IllegalStateException( 155 "Only one other party supported. doPhase() must only be called exactly once."); 156 } 157 // The other party key will be passed as part of the doFinal() call, to prevent an 158 // additional IPC. 159 mOtherPartyKey = (PublicKey) key; 160 161 return null; // No intermediate key 162 } 163 164 @Override engineGenerateSecret()165 protected byte[] engineGenerateSecret() throws IllegalStateException { 166 try { 167 ensureKeystoreOperationInitialized(); 168 } catch (InvalidKeyException e) { 169 throw new IllegalStateException("Not initialized", e); 170 } 171 172 if (mOtherPartyKey == null) { 173 throw new IllegalStateException("Other party key not provided. Call doPhase() first."); 174 } 175 byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded(); 176 177 try { 178 return mOperation.finish(otherPartyKeyEncoded, null); 179 } catch (KeyStoreException e) { 180 throw new ProviderException("Keystore operation failed", e); 181 } finally { 182 resetWhilePreservingInitState(); 183 } 184 } 185 186 @Override engineGenerateSecret(String algorithm)187 protected SecretKey engineGenerateSecret(String algorithm) 188 throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException { 189 byte[] generatedSecret = engineGenerateSecret(); 190 191 return new SecretKeySpec(generatedSecret, algorithm); 192 } 193 194 @Override engineGenerateSecret(byte[] sharedSecret, int offset)195 protected int engineGenerateSecret(byte[] sharedSecret, int offset) 196 throws IllegalStateException, ShortBufferException { 197 byte[] generatedSecret = engineGenerateSecret(); 198 199 if (generatedSecret.length > sharedSecret.length - offset) { 200 throw new ShortBufferException("Needed: " + generatedSecret.length); 201 } 202 System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length); 203 return generatedSecret.length; 204 } 205 206 @Override getOperationHandle()207 public long getOperationHandle() { 208 return mOperationHandle; 209 } 210 211 @Override finalize()212 protected void finalize() throws Throwable { 213 try { 214 resetAll(); 215 } finally { 216 super.finalize(); 217 } 218 } 219 resetWhilePreservingInitState()220 private void resetWhilePreservingInitState() { 221 KeyStoreCryptoOperationUtils.abortOperation(mOperation); 222 mOperationHandle = 0; 223 mOperation = null; 224 mOtherPartyKey = null; 225 } 226 resetAll()227 private void resetAll() { 228 resetWhilePreservingInitState(); 229 mKey = null; 230 } 231 ensureKeystoreOperationInitialized()232 private void ensureKeystoreOperationInitialized() 233 throws InvalidKeyException, IllegalStateException { 234 if (mKey == null) { 235 throw new IllegalStateException("Not initialized"); 236 } 237 if (mOperation != null) { 238 return; 239 } 240 241 // We don't need to explicitly pass in any other parameters here, as they're part of the 242 // private key that is available to Keymint. 243 List<KeyParameter> parameters = new ArrayList<>(); 244 parameters.add(KeyStore2ParameterUtils.makeEnum( 245 Tag.PURPOSE, KeyPurpose.AGREE_KEY 246 )); 247 248 try { 249 mOperation = 250 mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters); 251 } catch (KeyStoreException keyStoreException) { 252 // If necessary, throw an exception due to KeyStore operation having failed. 253 InvalidKeyException e = 254 KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException); 255 if (e != null) { 256 throw e; 257 } 258 } 259 260 // Set the operation handle. This will be a random number, or the operation challenge if 261 // user authentication is required. If we got a challenge we check if the authorization can 262 // possibly succeed. 263 mOperationHandle = 264 KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey); 265 } 266 } 267