AndroidKeyStoreSignatureSpiBase.java revision 3876b1be27e3aefde9a72eb2e4f856e94fc5f946
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.annotation.CallSuper;
20import android.annotation.NonNull;
21import android.os.IBinder;
22import android.security.KeyStore;
23import android.security.KeyStoreException;
24import android.security.keymaster.KeymasterArguments;
25import android.security.keymaster.KeymasterDefs;
26import android.security.keymaster.OperationResult;
27
28import libcore.util.EmptyArray;
29
30import java.nio.ByteBuffer;
31import java.security.InvalidKeyException;
32import java.security.InvalidParameterException;
33import java.security.PrivateKey;
34import java.security.ProviderException;
35import java.security.PublicKey;
36import java.security.SecureRandom;
37import java.security.SignatureException;
38import java.security.SignatureSpi;
39
40/**
41 * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
42 *
43 * @hide
44 */
45abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
46        implements KeyStoreCryptoOperation {
47    private final KeyStore mKeyStore;
48
49    // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin
50    // and should be preserved after SignatureSpi.engineSign/engineVerify finishes.
51    private boolean mSigning;
52    private AndroidKeyStoreKey mKey;
53
54    /**
55     * Token referencing this operation inside keystore service. It is initialized by
56     * {@code engineInitSign}/{@code engineInitVerify} and is invalidated when
57     * {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between.
58     */
59    private IBinder mOperationToken;
60    private long mOperationHandle;
61    private KeyStoreCryptoOperationStreamer mMessageStreamer;
62
63    /**
64     * Encountered exception which could not be immediately thrown because it was encountered inside
65     * a method that does not throw checked exception. This exception will be thrown from
66     * {@code engineSign} or {@code engineVerify}. Once such an exception is encountered,
67     * {@code engineUpdate} starts ignoring input data.
68     */
69    private Exception mCachedException;
70
71    AndroidKeyStoreSignatureSpiBase() {
72        mKeyStore = KeyStore.getInstance();
73    }
74
75    @Override
76    protected final void engineInitSign(PrivateKey key) throws InvalidKeyException {
77        engineInitSign(key, null);
78    }
79
80    @Override
81    protected final void engineInitSign(PrivateKey privateKey, SecureRandom random)
82            throws InvalidKeyException {
83        resetAll();
84
85        boolean success = false;
86        try {
87            if (privateKey == null) {
88                throw new InvalidKeyException("Unsupported key: null");
89            }
90            AndroidKeyStoreKey keystoreKey;
91            if (privateKey instanceof AndroidKeyStorePrivateKey) {
92                keystoreKey = (AndroidKeyStoreKey) privateKey;
93            } else {
94                throw new InvalidKeyException("Unsupported private key type: " + privateKey);
95            }
96            mSigning = true;
97            initKey(keystoreKey);
98            appRandom = random;
99            ensureKeystoreOperationInitialized();
100            success = true;
101        } finally {
102            if (!success) {
103                resetAll();
104            }
105        }
106    }
107
108    @Override
109    protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
110        resetAll();
111
112        boolean success = false;
113        try {
114            if (publicKey == null) {
115                throw new InvalidKeyException("Unsupported key: null");
116            }
117            AndroidKeyStoreKey keystoreKey;
118            if (publicKey instanceof AndroidKeyStorePublicKey) {
119                keystoreKey = (AndroidKeyStorePublicKey) publicKey;
120            } else {
121                throw new InvalidKeyException("Unsupported public key type: " + publicKey);
122            }
123            mSigning = false;
124            initKey(keystoreKey);
125            appRandom = null;
126            ensureKeystoreOperationInitialized();
127            success = true;
128        } finally {
129            if (!success) {
130                resetAll();
131            }
132        }
133    }
134
135    /**
136     * Configures this signature instance to use the provided key.
137     *
138     * @throws InvalidKeyException if the {@code key} is not suitable.
139     */
140    @CallSuper
141    protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
142        mKey = key;
143    }
144
145    /**
146     * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
147     * cipher instance.
148     *
149     * <p>Subclasses storing additional state should override this method, reset the additional
150     * state, and then chain to superclass.
151     */
152    @CallSuper
153    protected void resetAll() {
154        IBinder operationToken = mOperationToken;
155        if (operationToken != null) {
156            mOperationToken = null;
157            mKeyStore.abort(operationToken);
158        }
159        mSigning = false;
160        mKey = null;
161        appRandom = null;
162        mOperationToken = null;
163        mOperationHandle = 0;
164        mMessageStreamer = null;
165        mCachedException = null;
166    }
167
168    /**
169     * Resets this cipher while preserving the initialized state. This must be equivalent to
170     * rolling back the cipher's state to just after the most recent {@code engineInit} completed
171     * successfully.
172     *
173     * <p>Subclasses storing additional post-init state should override this method, reset the
174     * additional state, and then chain to superclass.
175     */
176    @CallSuper
177    protected void resetWhilePreservingInitState() {
178        IBinder operationToken = mOperationToken;
179        if (operationToken != null) {
180            mOperationToken = null;
181            mKeyStore.abort(operationToken);
182        }
183        mOperationHandle = 0;
184        mMessageStreamer = null;
185        mCachedException = null;
186    }
187
188    private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
189        if (mMessageStreamer != null) {
190            return;
191        }
192        if (mCachedException != null) {
193            return;
194        }
195        if (mKey == null) {
196            throw new IllegalStateException("Not initialized");
197        }
198
199        KeymasterArguments keymasterInputArgs = new KeymasterArguments();
200        addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
201
202        OperationResult opResult = mKeyStore.begin(
203                mKey.getAlias(),
204                mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY,
205                true, // permit aborting this operation if keystore runs out of resources
206                keymasterInputArgs,
207                null, // no additional entropy for begin -- only finish might need some
208                mKey.getUid());
209        if (opResult == null) {
210            throw new KeyStoreConnectException();
211        }
212
213        // Store operation token and handle regardless of the error code returned by KeyStore to
214        // ensure that the operation gets aborted immediately if the code below throws an exception.
215        mOperationToken = opResult.token;
216        mOperationHandle = opResult.operationHandle;
217
218        // If necessary, throw an exception due to KeyStore operation having failed.
219        InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
220                mKeyStore, mKey, opResult.resultCode);
221        if (e != null) {
222            throw e;
223        }
224
225        if (mOperationToken == null) {
226            throw new ProviderException("Keystore returned null operation token");
227        }
228        if (mOperationHandle == 0) {
229            throw new ProviderException("Keystore returned invalid operation handle");
230        }
231
232        mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token);
233    }
234
235    /**
236     * Creates a streamer which sends the message to be signed/verified into the provided KeyStore
237     *
238     * <p>This implementation returns a working streamer.
239     */
240    @NonNull
241    protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
242            KeyStore keyStore, IBinder operationToken) {
243        return new KeyStoreCryptoOperationChunkedStreamer(
244                new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
245                        keyStore, operationToken));
246    }
247
248    @Override
249    public final long getOperationHandle() {
250        return mOperationHandle;
251    }
252
253    @Override
254    protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
255        if (mCachedException != null) {
256            throw new SignatureException(mCachedException);
257        }
258
259        try {
260            ensureKeystoreOperationInitialized();
261        } catch (InvalidKeyException e) {
262            throw new SignatureException(e);
263        }
264
265        if (len == 0) {
266            return;
267        }
268
269        byte[] output;
270        try {
271            output = mMessageStreamer.update(b, off, len);
272        } catch (KeyStoreException e) {
273            throw new SignatureException(e);
274        }
275
276        if (output.length != 0) {
277            throw new ProviderException(
278                    "Update operation unexpectedly produced output: " + output.length + " bytes");
279        }
280    }
281
282    @Override
283    protected final void engineUpdate(byte b) throws SignatureException {
284        engineUpdate(new byte[] {b}, 0, 1);
285    }
286
287    @Override
288    protected final void engineUpdate(ByteBuffer input) {
289        byte[] b;
290        int off;
291        int len = input.remaining();
292        if (input.hasArray()) {
293            b = input.array();
294            off = input.arrayOffset() + input.position();
295            input.position(input.limit());
296        } else {
297            b = new byte[len];
298            off = 0;
299            input.get(b);
300        }
301
302        try {
303            engineUpdate(b, off, len);
304        } catch (SignatureException e) {
305            mCachedException = e;
306        }
307    }
308
309    @Override
310    protected final int engineSign(byte[] out, int outOffset, int outLen)
311            throws SignatureException {
312        return super.engineSign(out, outOffset, outLen);
313    }
314
315    @Override
316    protected final byte[] engineSign() throws SignatureException {
317        if (mCachedException != null) {
318            throw new SignatureException(mCachedException);
319        }
320
321        byte[] signature;
322        try {
323            ensureKeystoreOperationInitialized();
324
325            byte[] additionalEntropy =
326                    KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
327                            appRandom, getAdditionalEntropyAmountForSign());
328            signature = mMessageStreamer.doFinal(
329                    EmptyArray.BYTE, 0, 0,
330                    null, // no signature provided -- it'll be generated by this invocation
331                    additionalEntropy);
332        } catch (InvalidKeyException | KeyStoreException e) {
333            throw new SignatureException(e);
334        }
335
336        resetWhilePreservingInitState();
337        return signature;
338    }
339
340    @Override
341    protected final boolean engineVerify(byte[] signature) throws SignatureException {
342        if (mCachedException != null) {
343            throw new SignatureException(mCachedException);
344        }
345
346        try {
347            ensureKeystoreOperationInitialized();
348        } catch (InvalidKeyException e) {
349            throw new SignatureException(e);
350        }
351
352        boolean verified;
353        try {
354            byte[] output = mMessageStreamer.doFinal(
355                    EmptyArray.BYTE, 0, 0,
356                    signature,
357                    null // no additional entropy needed -- verification is deterministic
358                    );
359            if (output.length != 0) {
360                throw new ProviderException(
361                        "Signature verification unexpected produced output: " + output.length
362                        + " bytes");
363            }
364            verified = true;
365        } catch (KeyStoreException e) {
366            switch (e.getErrorCode()) {
367                case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
368                    verified = false;
369                    break;
370                default:
371                    throw new SignatureException(e);
372            }
373        }
374
375        resetWhilePreservingInitState();
376        return verified;
377    }
378
379    @Override
380    protected final boolean engineVerify(byte[] sigBytes, int offset, int len)
381            throws SignatureException {
382        return engineVerify(ArrayUtils.subarray(sigBytes, offset, len));
383    }
384
385    @Deprecated
386    @Override
387    protected final Object engineGetParameter(String param) throws InvalidParameterException {
388        throw new InvalidParameterException();
389    }
390
391    @Deprecated
392    @Override
393    protected final void engineSetParameter(String param, Object value)
394            throws InvalidParameterException {
395        throw new InvalidParameterException();
396    }
397
398    protected final KeyStore getKeyStore() {
399        return mKeyStore;
400    }
401
402    /**
403     * Returns {@code true} if this signature is initialized for signing, {@code false} if this
404     * signature is initialized for verification.
405     */
406    protected final boolean isSigning() {
407        return mSigning;
408    }
409
410    // The methods below need to be implemented by subclasses.
411
412    /**
413     * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
414     * {@code finish} operation when generating a signature.
415     *
416     * <p>This value should match (or exceed) the amount of Shannon entropy of the produced
417     * signature assuming the key and the message are known. For example, for ECDSA signature this
418     * should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this
419     * should be {@code 0}.
420     */
421    protected abstract int getAdditionalEntropyAmountForSign();
422
423    /**
424     * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
425     *
426     * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
427     *        parameters.
428     */
429    protected abstract void addAlgorithmSpecificParametersToBegin(
430            @NonNull KeymasterArguments keymasterArgs);
431}
432