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