1 /*
2  * Copyright (C) 2017 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 com.android.server.locksettings.recoverablekeystore;
18 
19 import android.annotation.Nullable;
20 import com.android.internal.annotations.VisibleForTesting;
21 import java.math.BigInteger;
22 import java.nio.BufferUnderflowException;
23 import java.nio.ByteBuffer;
24 import java.nio.charset.StandardCharsets;
25 import java.security.InvalidAlgorithmParameterException;
26 import java.security.InvalidKeyException;
27 import java.security.KeyFactory;
28 import java.security.KeyPair;
29 import java.security.KeyPairGenerator;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.PrivateKey;
32 import java.security.PublicKey;
33 import java.security.SecureRandom;
34 import java.security.interfaces.ECPublicKey;
35 import java.security.spec.ECFieldFp;
36 import java.security.spec.ECGenParameterSpec;
37 import java.security.spec.ECParameterSpec;
38 import java.security.spec.ECPoint;
39 import java.security.spec.ECPublicKeySpec;
40 import java.security.spec.EllipticCurve;
41 import java.security.spec.InvalidKeySpecException;
42 import java.util.Arrays;
43 import javax.crypto.AEADBadTagException;
44 import javax.crypto.BadPaddingException;
45 import javax.crypto.Cipher;
46 import javax.crypto.IllegalBlockSizeException;
47 import javax.crypto.KeyAgreement;
48 import javax.crypto.Mac;
49 import javax.crypto.NoSuchPaddingException;
50 import javax.crypto.SecretKey;
51 import javax.crypto.spec.GCMParameterSpec;
52 import javax.crypto.spec.SecretKeySpec;
53 
54 /**
55  * Implementation of the SecureBox v2 crypto functions.
56  *
57  * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
58  * credential types:
59  *
60  * <ul>
61  *   <li>A public key owned by the recipient,
62  *   <li>A secret shared between the sender and the recipient, or
63  *   <li>Both a recipient's public key and a shared secret.
64  * </ul>
65  *
66  * @hide
67  */
68 public class SecureBox {
69 
70     private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
71     private static final byte[] HKDF_SALT =
72             concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
73     private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
74             "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
75     private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
76             "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
77     private static final byte[] CONSTANT_01 = {(byte) 0x01};
78     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
79     private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
80 
81     private static final String CIPHER_ALG = "AES";
82     private static final String EC_ALG = "EC";
83     private static final String EC_P256_COMMON_NAME = "secp256r1";
84     private static final String EC_P256_OPENSSL_NAME = "prime256v1";
85     private static final String ENC_ALG = "AES/GCM/NoPadding";
86     private static final String KA_ALG = "ECDH";
87     private static final String MAC_ALG = "HmacSHA256";
88 
89     private static final int EC_COORDINATE_LEN_BYTES = 32;
90     private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
91     private static final int GCM_NONCE_LEN_BYTES = 12;
92     private static final int GCM_KEY_LEN_BYTES = 16;
93     private static final int GCM_TAG_LEN_BYTES = 16;
94 
95     private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
96 
97     private enum AesGcmOperation {
98         ENCRYPT,
99         DECRYPT
100     }
101 
102     // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
103     private static final BigInteger EC_PARAM_P =
104             new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
105     private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
106     private static final BigInteger EC_PARAM_B =
107             new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
108 
109     @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
110 
111     static {
112         EllipticCurve curveSpec =
113                 new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
114         ECPoint generator =
115                 new ECPoint(
116                         new BigInteger(
117                                 "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
118                                 16),
119                         new BigInteger(
120                                 "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
121                                 16));
122         BigInteger generatorOrder =
123                 new BigInteger(
124                         "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
125         EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
126     }
127 
SecureBox()128     private SecureBox() {}
129 
130     /**
131      * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
132      * {@link #decrypt}.
133      *
134      * @return the randomly generated public-key pair
135      * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
136      * @hide
137      */
genKeyPair()138     public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
139         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
140         try {
141             // Try using the OpenSSL provider first
142             keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
143             return keyPairGenerator.generateKeyPair();
144         } catch (InvalidAlgorithmParameterException ex) {
145             // Try another name for NIST P-256
146         }
147         try {
148             keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
149             return keyPairGenerator.generateKeyPair();
150         } catch (InvalidAlgorithmParameterException ex) {
151             throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
152         }
153     }
154 
155     /**
156      * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
157      * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
158      * {@code sharedSecret} is equivalent to null.
159      *
160      * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
161      * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
162      *
163      * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
164      *     only with the shared secret
165      * @param sharedSecret the secret shared between the sender and the recipient, or null if the
166      *     payload is to be encrypted only with the recipient's public key
167      * @param header the data that will be authenticated with {@code payload} but not encrypted, or
168      *     null if the data is empty
169      * @param payload the data to be encrypted, or null if the data is empty
170      * @return the encrypted payload
171      * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
172      * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
173      * @hide
174      */
encrypt( @ullable PublicKey theirPublicKey, @Nullable byte[] sharedSecret, @Nullable byte[] header, @Nullable byte[] payload)175     public static byte[] encrypt(
176             @Nullable PublicKey theirPublicKey,
177             @Nullable byte[] sharedSecret,
178             @Nullable byte[] header,
179             @Nullable byte[] payload)
180             throws NoSuchAlgorithmException, InvalidKeyException {
181         sharedSecret = emptyByteArrayIfNull(sharedSecret);
182         if (theirPublicKey == null && sharedSecret.length == 0) {
183             throw new IllegalArgumentException("Both the public key and shared secret are empty");
184         }
185         header = emptyByteArrayIfNull(header);
186         payload = emptyByteArrayIfNull(payload);
187 
188         KeyPair senderKeyPair;
189         byte[] dhSecret;
190         byte[] hkdfInfo;
191         if (theirPublicKey == null) {
192             senderKeyPair = null;
193             dhSecret = EMPTY_BYTE_ARRAY;
194             hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
195         } else {
196             senderKeyPair = genKeyPair();
197             dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
198             hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
199         }
200 
201         byte[] randNonce = genRandomNonce();
202         byte[] keyingMaterial = concat(dhSecret, sharedSecret);
203         SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
204         byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
205         if (senderKeyPair == null) {
206             return concat(VERSION, randNonce, ciphertext);
207         } else {
208             return concat(
209                     VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
210         }
211     }
212 
213     /**
214      * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
215      * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
216      * {@code sharedSecret} is equivalent to null.
217      *
218      * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
219      * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
220      * AEADBadTagException} will be thrown.
221      *
222      * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
223      *     with the shared secret
224      * @param sharedSecret the secret shared between the sender and the recipient, or null if the
225      *     payload was encrypted only with the recipient's public key
226      * @param header the data that was authenticated with the original payload but not encrypted, or
227      *     null if the data is empty
228      * @param encryptedPayload the data to be decrypted
229      * @return the original payload that was encrypted
230      * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
231      * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
232      * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
233      *     cannot be validated, or if the payload is not a valid SecureBox V2 payload.
234      * @hide
235      */
decrypt( @ullable PrivateKey ourPrivateKey, @Nullable byte[] sharedSecret, @Nullable byte[] header, byte[] encryptedPayload)236     public static byte[] decrypt(
237             @Nullable PrivateKey ourPrivateKey,
238             @Nullable byte[] sharedSecret,
239             @Nullable byte[] header,
240             byte[] encryptedPayload)
241             throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
242         sharedSecret = emptyByteArrayIfNull(sharedSecret);
243         if (ourPrivateKey == null && sharedSecret.length == 0) {
244             throw new IllegalArgumentException("Both the private key and shared secret are empty");
245         }
246         header = emptyByteArrayIfNull(header);
247         if (encryptedPayload == null) {
248             throw new NullPointerException("Encrypted payload must not be null.");
249         }
250 
251         ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
252         byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
253         if (!Arrays.equals(version, VERSION)) {
254             throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
255         }
256 
257         byte[] senderPublicKeyBytes;
258         byte[] dhSecret;
259         byte[] hkdfInfo;
260         if (ourPrivateKey == null) {
261             dhSecret = EMPTY_BYTE_ARRAY;
262             hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
263         } else {
264             senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
265             dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
266             hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
267         }
268 
269         byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
270         byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
271         byte[] keyingMaterial = concat(dhSecret, sharedSecret);
272         SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
273         return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
274     }
275 
readEncryptedPayload(ByteBuffer buffer, int length)276     private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
277             throws AEADBadTagException {
278         byte[] output = new byte[length];
279         try {
280             buffer.get(output);
281         } catch (BufferUnderflowException ex) {
282             throw new AEADBadTagException("The encrypted payload is too short");
283         }
284         return output;
285     }
286 
dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)287     private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
288             throws NoSuchAlgorithmException, InvalidKeyException {
289         KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
290         try {
291             agreement.init(ourPrivateKey);
292         } catch (RuntimeException ex) {
293             // Rethrow the RuntimeException as InvalidKeyException
294             throw new InvalidKeyException(ex);
295         }
296         agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
297         return agreement.generateSecret();
298     }
299 
300     /** Derives a 128-bit AES key. */
hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)301     private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
302             throws NoSuchAlgorithmException {
303         Mac mac = Mac.getInstance(MAC_ALG);
304         try {
305             mac.init(new SecretKeySpec(salt, MAC_ALG));
306         } catch (InvalidKeyException ex) {
307             // This should never happen
308             throw new RuntimeException(ex);
309         }
310         byte[] pseudorandomKey = mac.doFinal(secret);
311 
312         try {
313             mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
314         } catch (InvalidKeyException ex) {
315             // This should never happen
316             throw new RuntimeException(ex);
317         }
318         mac.update(info);
319         // Hashing just one block will yield 256 bits, which is enough to construct the AES key
320         byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
321 
322         return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
323     }
324 
aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)325     private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
326             throws NoSuchAlgorithmException, InvalidKeyException {
327         try {
328             return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
329         } catch (AEADBadTagException ex) {
330             // This should never happen
331             throw new RuntimeException(ex);
332         }
333     }
334 
aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)335     private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
336             throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
337         return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
338     }
339 
aesGcmInternal( AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)340     private static byte[] aesGcmInternal(
341             AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
342             throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
343         Cipher cipher;
344         try {
345             cipher = Cipher.getInstance(ENC_ALG);
346         } catch (NoSuchPaddingException ex) {
347             // This should never happen because AES-GCM doesn't use padding
348             throw new RuntimeException(ex);
349         }
350         GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
351         try {
352             if (operation == AesGcmOperation.DECRYPT) {
353                 cipher.init(Cipher.DECRYPT_MODE, key, spec);
354             } else {
355                 cipher.init(Cipher.ENCRYPT_MODE, key, spec);
356             }
357         } catch (InvalidAlgorithmParameterException ex) {
358             // This should never happen
359             throw new RuntimeException(ex);
360         }
361         try {
362             cipher.updateAAD(aad);
363             return cipher.doFinal(text);
364         } catch (AEADBadTagException ex) {
365             // Catch and rethrow AEADBadTagException first because it's a subclass of
366             // BadPaddingException
367             throw ex;
368         } catch (IllegalBlockSizeException | BadPaddingException ex) {
369             // This should never happen because AES-GCM can handle inputs of any length without
370             // padding
371             throw new RuntimeException(ex);
372         }
373     }
374 
375     /**
376      * Encodes public key in format expected by the secure hardware module. This is used as part
377      * of the vault params.
378      *
379      * @param publicKey The public key.
380      * @return The key packed into a 65-byte array.
381      */
encodePublicKey(PublicKey publicKey)382     static byte[] encodePublicKey(PublicKey publicKey) {
383         ECPoint point = ((ECPublicKey) publicKey).getW();
384         byte[] x = point.getAffineX().toByteArray();
385         byte[] y = point.getAffineY().toByteArray();
386 
387         byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
388         // The order of arraycopy() is important, because the coordinates may have a one-byte
389         // leading 0 for the sign bit of two's complement form
390         System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
391         System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
392         output[0] = EC_PUBLIC_KEY_PREFIX;
393         return output;
394     }
395 
396     @VisibleForTesting
decodePublicKey(byte[] keyBytes)397     static PublicKey decodePublicKey(byte[] keyBytes)
398             throws NoSuchAlgorithmException, InvalidKeyException {
399         BigInteger x =
400                 new BigInteger(
401                         /*signum=*/ 1,
402                         Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
403         BigInteger y =
404                 new BigInteger(
405                         /*signum=*/ 1,
406                         Arrays.copyOfRange(
407                                 keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
408 
409         // Checks if the point is indeed on the P-256 curve for security considerations
410         validateEcPoint(x, y);
411 
412         KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
413         try {
414             return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
415         } catch (InvalidKeySpecException ex) {
416             // This should never happen
417             throw new RuntimeException(ex);
418         }
419     }
420 
validateEcPoint(BigInteger x, BigInteger y)421     private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
422         if (x.compareTo(EC_PARAM_P) >= 0
423                 || y.compareTo(EC_PARAM_P) >= 0
424                 || x.signum() == -1
425                 || y.signum() == -1) {
426             throw new InvalidKeyException("Point lies outside of the expected curve");
427         }
428 
429         // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
430         BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
431         BigInteger rhs =
432                 x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
433                         .add(EC_PARAM_A) // x^2 + a
434                         .mod(EC_PARAM_P) // This will speed up the next multiplication
435                         .multiply(x) // (x^2 + a) * x = x^3 + ax
436                         .add(EC_PARAM_B) // x^3 + ax + b
437                         .mod(EC_PARAM_P);
438         if (!lhs.equals(rhs)) {
439             throw new InvalidKeyException("Point lies outside of the expected curve");
440         }
441     }
442 
genRandomNonce()443     private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
444         byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
445         new SecureRandom().nextBytes(nonce);
446         return nonce;
447     }
448 
449     @VisibleForTesting
concat(byte[]... inputs)450     static byte[] concat(byte[]... inputs) {
451         int length = 0;
452         for (int i = 0; i < inputs.length; i++) {
453             if (inputs[i] == null) {
454                 inputs[i] = EMPTY_BYTE_ARRAY;
455             }
456             length += inputs[i].length;
457         }
458 
459         byte[] output = new byte[length];
460         int outputPos = 0;
461         for (byte[] input : inputs) {
462             System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
463             outputPos += input.length;
464         }
465         return output;
466     }
467 
emptyByteArrayIfNull(@ullable byte[] input)468     private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
469         return input == null ? EMPTY_BYTE_ARRAY : input;
470     }
471 }
472