• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.nearby.common.bluetooth.fastpair;
18 
19 import static com.google.common.primitives.Bytes.concat;
20 
21 import androidx.annotation.Nullable;
22 
23 import java.math.BigInteger;
24 import java.security.GeneralSecurityException;
25 import java.security.KeyFactory;
26 import java.security.KeyPair;
27 import java.security.KeyPairGenerator;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.security.NoSuchProviderException;
31 import java.security.PrivateKey;
32 import java.security.PublicKey;
33 import java.security.interfaces.ECPrivateKey;
34 import java.security.interfaces.ECPublicKey;
35 import java.security.spec.ECGenParameterSpec;
36 import java.security.spec.ECParameterSpec;
37 import java.security.spec.ECPoint;
38 import java.security.spec.ECPrivateKeySpec;
39 import java.security.spec.ECPublicKeySpec;
40 import java.util.Arrays;
41 
42 import javax.crypto.KeyAgreement;
43 
44 /**
45  * Helper for generating keys based off of the Elliptic-Curve Diffie-Hellman algorithm (ECDH).
46  */
47 public final class EllipticCurveDiffieHellmanExchange {
48 
49     public static final int PUBLIC_KEY_LENGTH = 64;
50     static final int PRIVATE_KEY_LENGTH = 32;
51 
52     private static final String[] PROVIDERS = {"GmsCore_OpenSSL", "AndroidOpenSSL", "SC", "BC"};
53 
54     private static final String EC_ALGORITHM = "EC";
55 
56     /**
57      * Also known as prime256v1 or NIST P-256.
58      */
59     private static final ECGenParameterSpec EC_GEN_PARAMS = new ECGenParameterSpec("secp256r1");
60 
61     @Nullable
62     private final ECPublicKey mPublicKey;
63     private final ECPrivateKey mPrivateKey;
64 
65     /**
66      * Creates a new EllipticCurveDiffieHellmanExchange object.
67      */
create()68     public static EllipticCurveDiffieHellmanExchange create() throws GeneralSecurityException {
69         KeyPair keyPair = generateKeyPair();
70         return new EllipticCurveDiffieHellmanExchange(
71                 (ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate());
72     }
73 
74     /**
75      * Creates a new EllipticCurveDiffieHellmanExchange object.
76      */
create(byte[] privateKey)77     public static EllipticCurveDiffieHellmanExchange create(byte[] privateKey)
78             throws GeneralSecurityException {
79         ECPrivateKey ecPrivateKey = (ECPrivateKey) generatePrivateKey(privateKey);
80         return new EllipticCurveDiffieHellmanExchange(/*publicKey=*/ null, ecPrivateKey);
81     }
82 
EllipticCurveDiffieHellmanExchange( @ullable ECPublicKey publicKey, ECPrivateKey privateKey)83     private EllipticCurveDiffieHellmanExchange(
84             @Nullable ECPublicKey publicKey, ECPrivateKey privateKey) {
85         this.mPublicKey = publicKey;
86         this.mPrivateKey = privateKey;
87     }
88 
89     /**
90      * @param otherPublicKey Another party's public key. See {@link #getPublicKey()} for format.
91      * @return The shared secret. Given our public key (and its private key), the other party can
92      * generate the same secret. This is a key meant for symmetric encryption.
93      */
generateSecret(byte[] otherPublicKey)94     public byte[] generateSecret(byte[] otherPublicKey) throws GeneralSecurityException {
95         KeyAgreement agreement = keyAgreement();
96         agreement.init(mPrivateKey);
97         agreement.doPhase(generatePublicKey(otherPublicKey), /*lastPhase=*/ true);
98         byte[] secret = agreement.generateSecret();
99         // Headsets only support AES with 128-bit keys. So, hash the secret so that the entropy is
100         // high and then take only the first 128-bits.
101         secret = MessageDigest.getInstance("SHA-256").digest(secret);
102         return Arrays.copyOf(secret, 16);
103     }
104 
105     /**
106      * Returns a public point W on the NIST P-256 elliptic curve. First 32 bytes are the X
107      * coordinate, next 32 bytes are the Y coordinate. Each coordinate is an unsigned big-endian
108      * integer.
109      */
getPublicKey()110     public @Nullable byte[] getPublicKey() {
111         if (mPublicKey == null) {
112             return null;
113         }
114         ECPoint w = mPublicKey.getW();
115         // See getPrivateKey for why we're resizing.
116         byte[] x = resizeWithLeadingZeros(w.getAffineX().toByteArray(), 32);
117         byte[] y = resizeWithLeadingZeros(w.getAffineY().toByteArray(), 32);
118         return concat(x, y);
119     }
120 
121     /**
122      * Returns a private value S, an unsigned big-endian integer.
123      */
getPrivateKey()124     public byte[] getPrivateKey() {
125         // Note that BigInteger.toByteArray() returns a signed representation, so it will add an
126         // extra zero byte to the front if the first bit is 1.
127         // We must remove that leading zero (we know the number is unsigned). We must also add
128         // leading zeros if the number is too small.
129         return resizeWithLeadingZeros(mPrivateKey.getS().toByteArray(), 32);
130     }
131 
132     /**
133      * Removes or adds leading zeros until we have an array of size {@code n}.
134      */
resizeWithLeadingZeros(byte[] x, int n)135     private static byte[] resizeWithLeadingZeros(byte[] x, int n) {
136         if (n < x.length) {
137             int start = x.length - n;
138             for (int i = 0; i < start; i++) {
139                 if (x[i] != 0) {
140                     throw new IllegalArgumentException(
141                             "More than " + n + " non-zero bytes in " + Arrays.toString(x));
142                 }
143             }
144             return Arrays.copyOfRange(x, start, x.length);
145         }
146         return concat(new byte[n - x.length], x);
147     }
148 
149     /**
150      * @param publicKey See {@link #getPublicKey()} for format.
151      */
generatePublicKey(byte[] publicKey)152     private static PublicKey generatePublicKey(byte[] publicKey) throws GeneralSecurityException {
153         if (publicKey.length != PUBLIC_KEY_LENGTH) {
154             throw new GeneralSecurityException("Public key length incorrect: " + publicKey.length);
155         }
156         byte[] x = Arrays.copyOf(publicKey, publicKey.length / 2);
157         byte[] y = Arrays.copyOfRange(publicKey, publicKey.length / 2, publicKey.length);
158         return keyFactory()
159                 .generatePublic(
160                         new ECPublicKeySpec(
161                                 new ECPoint(new BigInteger(/*signum=*/ 1, x),
162                                         new BigInteger(/*signum=*/ 1, y)),
163                                 ecParameterSpec()));
164     }
165 
166     /**
167      * @param privateKey See {@link #getPrivateKey()} for format.
168      */
generatePrivateKey(byte[] privateKey)169     private static PrivateKey generatePrivateKey(byte[] privateKey)
170             throws GeneralSecurityException {
171         if (privateKey.length != PRIVATE_KEY_LENGTH) {
172             throw new GeneralSecurityException("Private key length incorrect: "
173                     + privateKey.length);
174         }
175         return keyFactory()
176                 .generatePrivate(
177                         new ECPrivateKeySpec(new BigInteger(/*signum=*/ 1, privateKey),
178                                 ecParameterSpec()));
179     }
180 
ecParameterSpec()181     private static ECParameterSpec ecParameterSpec() throws GeneralSecurityException {
182         // This seems to be the simplest way to get the curve's ECParameterSpec. Verified that it's
183         // the same whether you get it from the public or private key, and that it's the same as the
184         // raw params in SecAggEcUtil.getNistP256Params().
185         return ((ECPublicKey) generateKeyPair().getPublic()).getParams();
186     }
187 
generateKeyPair()188     private static KeyPair generateKeyPair() throws GeneralSecurityException {
189         KeyPairGenerator generator = findProvider(p -> KeyPairGenerator.getInstance(EC_ALGORITHM,
190                 p));
191         generator.initialize(EC_GEN_PARAMS);
192         return generator.generateKeyPair();
193     }
194 
keyAgreement()195     private static KeyAgreement keyAgreement() throws NoSuchProviderException {
196         return findProvider(p -> KeyAgreement.getInstance("ECDH", p));
197     }
198 
keyFactory()199     private static KeyFactory keyFactory() throws NoSuchProviderException {
200         return findProvider(p -> KeyFactory.getInstance(EC_ALGORITHM, p));
201     }
202 
203     private interface ProviderConsumer<T> {
204 
tryProvider(String provider)205         T tryProvider(String provider) throws NoSuchAlgorithmException, NoSuchProviderException;
206     }
207 
findProvider(ProviderConsumer<T> providerConsumer)208     private static <T> T findProvider(ProviderConsumer<T> providerConsumer)
209             throws NoSuchProviderException {
210         for (String provider : PROVIDERS) {
211             try {
212                 return providerConsumer.tryProvider(provider);
213             } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
214                 // No-op
215             }
216         }
217         throw new NoSuchProviderException();
218     }
219 }
220