1/* 2 * Copyright (C) 2015 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 17package android.security.keystore; 18 19import android.os.IBinder; 20import android.security.KeyStore; 21import android.security.KeyStoreException; 22import android.security.keymaster.KeymasterArguments; 23import android.security.keymaster.KeymasterDefs; 24import android.security.keymaster.OperationResult; 25 26import java.security.InvalidAlgorithmParameterException; 27import java.security.InvalidKeyException; 28import java.security.Key; 29import java.security.ProviderException; 30import java.security.spec.AlgorithmParameterSpec; 31 32import javax.crypto.MacSpi; 33 34/** 35 * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. 36 * 37 * @hide 38 */ 39public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { 40 41 public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { 42 public HmacSHA1() { 43 super(KeymasterDefs.KM_DIGEST_SHA1); 44 } 45 } 46 47 public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { 48 public HmacSHA224() { 49 super(KeymasterDefs.KM_DIGEST_SHA_2_224); 50 } 51 } 52 53 public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { 54 public HmacSHA256() { 55 super(KeymasterDefs.KM_DIGEST_SHA_2_256); 56 } 57 } 58 59 public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { 60 public HmacSHA384() { 61 super(KeymasterDefs.KM_DIGEST_SHA_2_384); 62 } 63 } 64 65 public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { 66 public HmacSHA512() { 67 super(KeymasterDefs.KM_DIGEST_SHA_2_512); 68 } 69 } 70 71 private final KeyStore mKeyStore = KeyStore.getInstance(); 72 private final int mKeymasterDigest; 73 private final int mMacSizeBits; 74 75 // Fields below are populated by engineInit and should be preserved after engineDoFinal. 76 private AndroidKeyStoreSecretKey mKey; 77 78 // Fields below are reset when engineDoFinal succeeds. 79 private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; 80 private IBinder mOperationToken; 81 private long mOperationHandle; 82 83 protected AndroidKeyStoreHmacSpi(int keymasterDigest) { 84 mKeymasterDigest = keymasterDigest; 85 mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); 86 } 87 88 @Override 89 protected int engineGetMacLength() { 90 return (mMacSizeBits + 7) / 8; 91 } 92 93 @Override 94 protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, 95 InvalidAlgorithmParameterException { 96 resetAll(); 97 98 boolean success = false; 99 try { 100 init(key, params); 101 ensureKeystoreOperationInitialized(); 102 success = true; 103 } finally { 104 if (!success) { 105 resetAll(); 106 } 107 } 108 } 109 110 private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, 111 InvalidAlgorithmParameterException { 112 if (key == null) { 113 throw new InvalidKeyException("key == null"); 114 } else if (!(key instanceof AndroidKeyStoreSecretKey)) { 115 throw new InvalidKeyException( 116 "Only Android KeyStore secret keys supported. Key: " + key); 117 } 118 mKey = (AndroidKeyStoreSecretKey) key; 119 120 if (params != null) { 121 throw new InvalidAlgorithmParameterException( 122 "Unsupported algorithm parameters: " + params); 123 } 124 125 } 126 127 private void resetAll() { 128 mKey = null; 129 IBinder operationToken = mOperationToken; 130 if (operationToken != null) { 131 mKeyStore.abort(operationToken); 132 } 133 mOperationToken = null; 134 mOperationHandle = 0; 135 mChunkedStreamer = null; 136 } 137 138 private void resetWhilePreservingInitState() { 139 IBinder operationToken = mOperationToken; 140 if (operationToken != null) { 141 mKeyStore.abort(operationToken); 142 } 143 mOperationToken = null; 144 mOperationHandle = 0; 145 mChunkedStreamer = null; 146 } 147 148 @Override 149 protected void engineReset() { 150 resetWhilePreservingInitState(); 151 } 152 153 private void ensureKeystoreOperationInitialized() throws InvalidKeyException { 154 if (mChunkedStreamer != null) { 155 return; 156 } 157 if (mKey == null) { 158 throw new IllegalStateException("Not initialized"); 159 } 160 161 KeymasterArguments keymasterArgs = new KeymasterArguments(); 162 keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); 163 keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); 164 keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); 165 166 OperationResult opResult = mKeyStore.begin( 167 mKey.getAlias(), 168 KeymasterDefs.KM_PURPOSE_SIGN, 169 true, 170 keymasterArgs, 171 null, // no additional entropy needed for HMAC because it's deterministic 172 mKey.getUid()); 173 174 if (opResult == null) { 175 throw new KeyStoreConnectException(); 176 } 177 178 // Store operation token and handle regardless of the error code returned by KeyStore to 179 // ensure that the operation gets aborted immediately if the code below throws an exception. 180 mOperationToken = opResult.token; 181 mOperationHandle = opResult.operationHandle; 182 183 // If necessary, throw an exception due to KeyStore operation having failed. 184 InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( 185 mKeyStore, mKey, opResult.resultCode); 186 if (e != null) { 187 throw e; 188 } 189 190 if (mOperationToken == null) { 191 throw new ProviderException("Keystore returned null operation token"); 192 } 193 if (mOperationHandle == 0) { 194 throw new ProviderException("Keystore returned invalid operation handle"); 195 } 196 197 mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( 198 new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( 199 mKeyStore, mOperationToken)); 200 } 201 202 @Override 203 protected void engineUpdate(byte input) { 204 engineUpdate(new byte[] {input}, 0, 1); 205 } 206 207 @Override 208 protected void engineUpdate(byte[] input, int offset, int len) { 209 try { 210 ensureKeystoreOperationInitialized(); 211 } catch (InvalidKeyException e) { 212 throw new ProviderException("Failed to reinitialize MAC", e); 213 } 214 215 byte[] output; 216 try { 217 output = mChunkedStreamer.update(input, offset, len); 218 } catch (KeyStoreException e) { 219 throw new ProviderException("Keystore operation failed", e); 220 } 221 if ((output != null) && (output.length != 0)) { 222 throw new ProviderException("Update operation unexpectedly produced output"); 223 } 224 } 225 226 @Override 227 protected byte[] engineDoFinal() { 228 try { 229 ensureKeystoreOperationInitialized(); 230 } catch (InvalidKeyException e) { 231 throw new ProviderException("Failed to reinitialize MAC", e); 232 } 233 234 byte[] result; 235 try { 236 result = mChunkedStreamer.doFinal( 237 null, 0, 0, 238 null, // no signature provided -- this invocation will generate one 239 null // no additional entropy needed -- HMAC is deterministic 240 ); 241 } catch (KeyStoreException e) { 242 throw new ProviderException("Keystore operation failed", e); 243 } 244 245 resetWhilePreservingInitState(); 246 return result; 247 } 248 249 @Override 250 public void finalize() throws Throwable { 251 try { 252 IBinder operationToken = mOperationToken; 253 if (operationToken != null) { 254 mKeyStore.abort(operationToken); 255 } 256 } finally { 257 super.finalize(); 258 } 259 } 260 261 @Override 262 public long getOperationHandle() { 263 return mOperationHandle; 264 } 265} 266