1 /* 2 * Copyright (C) 2018 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.annotation.NonNull; 20 import android.hardware.security.keymint.KeyParameter; 21 import android.security.keymaster.KeymasterDefs; 22 import android.security.keystore.ArrayUtils; 23 import android.security.keystore.KeyProperties; 24 25 import java.security.AlgorithmParameters; 26 import java.security.InvalidAlgorithmParameterException; 27 import java.security.InvalidKeyException; 28 import java.security.Key; 29 import java.security.NoSuchAlgorithmException; 30 import java.security.ProviderException; 31 import java.security.spec.AlgorithmParameterSpec; 32 import java.security.spec.InvalidParameterSpecException; 33 import java.util.Arrays; 34 import java.util.List; 35 36 import javax.crypto.CipherSpi; 37 import javax.crypto.spec.IvParameterSpec; 38 39 /** 40 * Base class for Android Keystore 3DES {@link CipherSpi} implementations. 41 * 42 * @hide 43 */ 44 public abstract class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { 45 46 private static final int BLOCK_SIZE_BYTES = 8; 47 48 private final int mKeymasterBlockMode; 49 private final int mKeymasterPadding; 50 /** Whether this transformation requires an IV. */ 51 private final boolean mIvRequired; 52 53 private byte[] mIv; 54 55 /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ 56 private boolean mIvHasBeenUsed; 57 AndroidKeyStore3DESCipherSpi( int keymasterBlockMode, int keymasterPadding, boolean ivRequired)58 AndroidKeyStore3DESCipherSpi( 59 int keymasterBlockMode, 60 int keymasterPadding, 61 boolean ivRequired) { 62 mKeymasterBlockMode = keymasterBlockMode; 63 mKeymasterPadding = keymasterPadding; 64 mIvRequired = ivRequired; 65 } 66 67 abstract static class ECB extends AndroidKeyStore3DESCipherSpi { ECB(int keymasterPadding)68 protected ECB(int keymasterPadding) { 69 super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); 70 } 71 72 public static class NoPadding extends ECB { NoPadding()73 public NoPadding() { 74 super(KeymasterDefs.KM_PAD_NONE); 75 } 76 77 @Override getTransform()78 protected final String getTransform() { 79 return "DESede/ECB/NoPadding"; 80 } 81 } 82 83 public static class PKCS7Padding extends ECB { PKCS7Padding()84 public PKCS7Padding() { 85 super(KeymasterDefs.KM_PAD_PKCS7); 86 } 87 88 @Override getTransform()89 protected final String getTransform() { 90 return "DESede/ECB/PKCS7Padding"; 91 } 92 } 93 } 94 95 abstract static class CBC extends AndroidKeyStore3DESCipherSpi { CBC(int keymasterPadding)96 protected CBC(int keymasterPadding) { 97 super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); 98 } 99 100 public static class NoPadding extends CBC { NoPadding()101 public NoPadding() { 102 super(KeymasterDefs.KM_PAD_NONE); 103 } 104 105 @Override getTransform()106 protected final String getTransform() { 107 return "DESede/CBC/NoPadding"; 108 } 109 110 } 111 112 public static class PKCS7Padding extends CBC { PKCS7Padding()113 public PKCS7Padding() { 114 super(KeymasterDefs.KM_PAD_PKCS7); 115 } 116 117 @Override getTransform()118 protected final String getTransform() { 119 return "DESede/CBC/PKCS7Padding"; 120 } 121 } 122 } 123 124 @Override initKey(int i, Key key)125 protected void initKey(int i, Key key) throws InvalidKeyException { 126 if (!(key instanceof AndroidKeyStoreSecretKey)) { 127 throw new InvalidKeyException( 128 "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); 129 } 130 if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) { 131 throw new InvalidKeyException( 132 "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + 133 KeyProperties.KEY_ALGORITHM_3DES + " supported"); 134 } 135 setKey((AndroidKeyStoreSecretKey) key); 136 } 137 138 @Override engineGetBlockSize()139 protected int engineGetBlockSize() { 140 return BLOCK_SIZE_BYTES; 141 } 142 143 @Override engineGetOutputSize(int inputLen)144 protected int engineGetOutputSize(int inputLen) { 145 return inputLen + 3 * BLOCK_SIZE_BYTES; 146 } 147 148 @Override engineGetIV()149 protected final byte[] engineGetIV() { 150 return ArrayUtils.cloneIfNotEmpty(mIv); 151 } 152 153 @Override engineGetParameters()154 protected AlgorithmParameters engineGetParameters() { 155 if (!mIvRequired) { 156 return null; 157 } 158 if ((mIv != null) && (mIv.length > 0)) { 159 try { 160 AlgorithmParameters params = AlgorithmParameters.getInstance("DESede"); 161 params.init(new IvParameterSpec(mIv)); 162 return params; 163 } catch (NoSuchAlgorithmException e) { 164 throw new ProviderException( 165 "Failed to obtain 3DES AlgorithmParameters", e); 166 } catch (InvalidParameterSpecException e) { 167 throw new ProviderException( 168 "Failed to initialize 3DES AlgorithmParameters with an IV", 169 e); 170 } 171 } 172 return null; 173 } 174 175 @Override initAlgorithmSpecificParameters()176 protected void initAlgorithmSpecificParameters() throws InvalidKeyException { 177 if (!mIvRequired) { 178 return; 179 } 180 181 // IV is used 182 if (!isEncrypting()) { 183 throw new InvalidKeyException("IV required when decrypting" 184 + ". Use IvParameterSpec or AlgorithmParameters to provide it."); 185 } 186 } 187 188 @Override initAlgorithmSpecificParameters(AlgorithmParameterSpec params)189 protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) 190 throws InvalidAlgorithmParameterException { 191 if (!mIvRequired) { 192 if (params != null) { 193 throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); 194 } 195 return; 196 } 197 198 // IV is used 199 if (params == null) { 200 if (!isEncrypting()) { 201 // IV must be provided by the caller 202 throw new InvalidAlgorithmParameterException( 203 "IvParameterSpec must be provided when decrypting"); 204 } 205 return; 206 } 207 if (!(params instanceof IvParameterSpec)) { 208 throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); 209 } 210 mIv = ((IvParameterSpec) params).getIV(); 211 if (mIv == null) { 212 throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); 213 } 214 } 215 216 @Override initAlgorithmSpecificParameters(AlgorithmParameters params)217 protected void initAlgorithmSpecificParameters(AlgorithmParameters params) 218 throws InvalidAlgorithmParameterException { 219 if (!mIvRequired) { 220 if (params != null) { 221 throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); 222 } 223 return; 224 } 225 226 // IV is used 227 if (params == null) { 228 if (!isEncrypting()) { 229 // IV must be provided by the caller 230 throw new InvalidAlgorithmParameterException("IV required when decrypting" 231 + ". Use IvParameterSpec or AlgorithmParameters to provide it."); 232 } 233 return; 234 } 235 236 if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) { 237 throw new InvalidAlgorithmParameterException( 238 "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() 239 + ". Supported: DESede"); 240 } 241 242 IvParameterSpec ivSpec; 243 try { 244 ivSpec = params.getParameterSpec(IvParameterSpec.class); 245 } catch (InvalidParameterSpecException e) { 246 if (!isEncrypting()) { 247 // IV must be provided by the caller 248 throw new InvalidAlgorithmParameterException("IV required when decrypting" 249 + ", but not found in parameters: " + params, e); 250 } 251 mIv = null; 252 return; 253 } 254 mIv = ivSpec.getIV(); 255 if (mIv == null) { 256 throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); 257 } 258 } 259 260 @Override getAdditionalEntropyAmountForBegin()261 protected final int getAdditionalEntropyAmountForBegin() { 262 if ((mIvRequired) && (mIv == null) && (isEncrypting())) { 263 // IV will need to be generated 264 return BLOCK_SIZE_BYTES; 265 } 266 267 return 0; 268 } 269 270 @Override getAdditionalEntropyAmountForFinish()271 protected int getAdditionalEntropyAmountForFinish() { 272 return 0; 273 } 274 275 @Override addAlgorithmSpecificParametersToBegin(@onNull List<KeyParameter> parameters)276 protected void addAlgorithmSpecificParametersToBegin(@NonNull List<KeyParameter> parameters) { 277 if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { 278 // IV is being reused for encryption: this violates security best practices. 279 throw new IllegalStateException( 280 "IV has already been used. Reusing IV in encryption mode violates security best" 281 + " practices."); 282 } 283 284 parameters.add(KeyStore2ParameterUtils.makeEnum( 285 KeymasterDefs.KM_TAG_ALGORITHM, 286 KeymasterDefs.KM_ALGORITHM_3DES 287 )); 288 parameters.add(KeyStore2ParameterUtils.makeEnum( 289 KeymasterDefs.KM_TAG_BLOCK_MODE, 290 mKeymasterBlockMode 291 )); 292 parameters.add(KeyStore2ParameterUtils.makeEnum( 293 KeymasterDefs.KM_TAG_PADDING, 294 mKeymasterPadding 295 )); 296 297 if (mIvRequired && (mIv != null)) { 298 parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv)); 299 } 300 } 301 302 @Override loadAlgorithmSpecificParametersFromBeginResult( KeyParameter[] parameters)303 protected void loadAlgorithmSpecificParametersFromBeginResult( 304 KeyParameter[] parameters) { 305 mIvHasBeenUsed = true; 306 307 // NOTE: Keymaster doesn't always return an IV, even if it's used. 308 byte[] returnedIv = null; 309 if (parameters != null) { 310 for (KeyParameter p : parameters) { 311 if (p.tag == KeymasterDefs.KM_TAG_NONCE) { 312 returnedIv = p.value.getBlob(); 313 break; 314 } 315 } 316 } 317 318 if (mIvRequired) { 319 if (mIv == null) { 320 mIv = returnedIv; 321 } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { 322 throw new ProviderException("IV in use differs from provided IV"); 323 } 324 } else { 325 if (returnedIv != null) { 326 throw new ProviderException( 327 "IV in use despite IV not being used by this transformation"); 328 } 329 } 330 } 331 332 @Override resetAll()333 protected final void resetAll() { 334 mIv = null; 335 mIvHasBeenUsed = false; 336 super.resetAll(); 337 } 338 } 339