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 
17 package android.security.keystore2;
18 
19 import android.annotation.NonNull;
20 import android.hardware.security.keymint.KeyParameter;
21 import android.security.KeyStoreException;
22 import android.security.KeyStoreOperation;
23 import android.security.keymaster.KeymasterDefs;
24 import android.security.keystore.KeyProperties;
25 import android.system.keystore2.Authorization;
26 
27 import libcore.util.EmptyArray;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.security.InvalidKeyException;
31 import java.security.SignatureSpi;
32 import java.security.spec.NamedParameterSpec;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Set;
36 
37 /**
38  * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures.
39  *
40  * @hide
41  */
42 abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
43     private static final Set<String> ACCEPTED_SIGNING_SCHEMES = Set.of(
44             KeyProperties.KEY_ALGORITHM_EC.toLowerCase(),
45             NamedParameterSpec.ED25519.getName().toLowerCase(),
46             "eddsa");
47 
48     public final static class NONE extends AndroidKeyStoreECDSASignatureSpi {
NONE()49         public NONE() {
50             super(KeymasterDefs.KM_DIGEST_NONE);
51         }
52 
53         @Override
getAlgorithm()54         protected String getAlgorithm() {
55             return "NONEwithECDSA";
56         }
57 
58         @Override
createMainDataStreamer( KeyStoreOperation operation)59         protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
60                 KeyStoreOperation operation) {
61             return new TruncateToFieldSizeMessageStreamer(
62                     super.createMainDataStreamer(operation),
63                     getGroupSizeBits());
64         }
65 
66         /**
67          * Streamer which buffers all input, then truncates it to field size, and then sends it into
68          * KeyStore via the provided delegate streamer.
69          */
70         private static class TruncateToFieldSizeMessageStreamer
71                 implements KeyStoreCryptoOperationStreamer {
72 
73             private final KeyStoreCryptoOperationStreamer mDelegate;
74             private final int mGroupSizeBits;
75             private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
76             private long mConsumedInputSizeBytes;
77 
TruncateToFieldSizeMessageStreamer( KeyStoreCryptoOperationStreamer delegate, int groupSizeBits)78             private TruncateToFieldSizeMessageStreamer(
79                     KeyStoreCryptoOperationStreamer delegate,
80                     int groupSizeBits) {
81                 mDelegate = delegate;
82                 mGroupSizeBits = groupSizeBits;
83             }
84 
85             @Override
update(byte[] input, int inputOffset, int inputLength)86             public byte[] update(byte[] input, int inputOffset, int inputLength)
87                     throws KeyStoreException {
88                 if (inputLength > 0) {
89                     mInputBuffer.write(input, inputOffset, inputLength);
90                     mConsumedInputSizeBytes += inputLength;
91                 }
92                 return EmptyArray.BYTE;
93             }
94 
95             @Override
doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature)96             public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature)
97                     throws KeyStoreException {
98                 if (inputLength > 0) {
99                     mConsumedInputSizeBytes += inputLength;
100                     mInputBuffer.write(input, inputOffset, inputLength);
101                 }
102 
103                 byte[] bufferedInput = mInputBuffer.toByteArray();
104                 mInputBuffer.reset();
105                 // Truncate input at field size (bytes)
106                 return mDelegate.doFinal(bufferedInput,
107                         0,
108                         Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
109                         signature);
110             }
111 
112             @Override
getConsumedInputSizeBytes()113             public long getConsumedInputSizeBytes() {
114                 return mConsumedInputSizeBytes;
115             }
116 
117             @Override
getProducedOutputSizeBytes()118             public long getProducedOutputSizeBytes() {
119                 return mDelegate.getProducedOutputSizeBytes();
120             }
121         }
122     }
123 
124     public static final class Ed25519 extends AndroidKeyStoreECDSASignatureSpi {
Ed25519()125         public Ed25519() {
126             // Ed25519 uses an internal digest system.
127             super(KeymasterDefs.KM_DIGEST_NONE);
128         }
129 
130         @Override
getAlgorithm()131         protected String getAlgorithm() {
132             return NamedParameterSpec.ED25519.getName();
133         }
134     }
135 
136     public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
SHA1()137         public SHA1() {
138             super(KeymasterDefs.KM_DIGEST_SHA1);
139         }
140         @Override
getAlgorithm()141         protected String getAlgorithm() {
142             return "SHA1withECDSA";
143         }
144     }
145 
146     public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
SHA224()147         public SHA224() {
148             super(KeymasterDefs.KM_DIGEST_SHA_2_224);
149         }
150         @Override
getAlgorithm()151         protected String getAlgorithm() {
152             return "SHA224withECDSA";
153         }
154     }
155 
156     public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
SHA256()157         public SHA256() {
158             super(KeymasterDefs.KM_DIGEST_SHA_2_256);
159         }
160         @Override
getAlgorithm()161         protected String getAlgorithm() {
162             return "SHA256withECDSA";
163         }
164     }
165 
166     public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
SHA384()167         public SHA384() {
168             super(KeymasterDefs.KM_DIGEST_SHA_2_384);
169         }
170         @Override
getAlgorithm()171         protected String getAlgorithm() {
172             return "SHA384withECDSA";
173         }
174     }
175 
176     public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
SHA512()177         public SHA512() {
178             super(KeymasterDefs.KM_DIGEST_SHA_2_512);
179         }
180         @Override
getAlgorithm()181         protected String getAlgorithm() {
182             return "SHA512withECDSA";
183         }
184     }
185 
186     private final int mKeymasterDigest;
187 
188     private int mGroupSizeBits = -1;
189 
AndroidKeyStoreECDSASignatureSpi(int keymasterDigest)190     AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
191         mKeymasterDigest = keymasterDigest;
192     }
193 
194     @Override
initKey(AndroidKeyStoreKey key)195     protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
196         if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) {
197             throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
198                     + ". Only " + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
199                     + " supported");
200         }
201 
202         long keySizeBits = -1;
203         for (Authorization a : key.getAuthorizations()) {
204             if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
205                 keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
206                 break;
207             } else if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
208                 keySizeBits = KeyProperties.EcCurve.fromKeymasterCurve(
209                         a.keyParameter.value.getEcCurve());
210                 break;
211             }
212         }
213 
214         if (keySizeBits == -1) {
215             throw new InvalidKeyException("Size of key not known");
216         } else if (keySizeBits > Integer.MAX_VALUE) {
217             throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
218         }
219         mGroupSizeBits = (int) keySizeBits;
220 
221         super.initKey(key);
222     }
223 
224     @Override
resetAll()225     protected final void resetAll() {
226         mGroupSizeBits = -1;
227         super.resetAll();
228     }
229 
230     @Override
resetWhilePreservingInitState()231     protected final void resetWhilePreservingInitState() {
232         super.resetWhilePreservingInitState();
233     }
234 
235     @Override
addAlgorithmSpecificParametersToBegin( @onNull List<KeyParameter> parameters)236     protected final void addAlgorithmSpecificParametersToBegin(
237             @NonNull List<KeyParameter> parameters) {
238         parameters.add(KeyStore2ParameterUtils.makeEnum(
239                 KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC
240         ));
241         parameters.add(KeyStore2ParameterUtils.makeEnum(
242                 KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
243         ));
244     }
245 
246     @Override
getAdditionalEntropyAmountForSign()247     protected final int getAdditionalEntropyAmountForSign() {
248         return (mGroupSizeBits + 7) / 8;
249     }
250 
getGroupSizeBits()251     protected final int getGroupSizeBits() {
252         if (mGroupSizeBits == -1) {
253             throw new IllegalStateException("Not initialized");
254         }
255         return mGroupSizeBits;
256     }
257 }
258