• 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.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
20 
21 import static com.google.common.primitives.Bytes.concat;
22 
23 import static java.nio.charset.StandardCharsets.UTF_8;
24 
25 import android.annotation.TargetApi;
26 import android.os.Build.VERSION_CODES;
27 
28 import com.google.common.base.Utf8;
29 
30 import java.security.GeneralSecurityException;
31 import java.util.Arrays;
32 
33 /**
34  * Naming utilities for encoding naming packet, decoding naming packet and verifying both the data
35  * integrity and the authentication of a message by checking HMAC.
36  *
37  * <p>Naming packet is:
38  *
39  * <ol>
40  *   <li>Naming_Packet[0 - 7]: the first 8-byte of HMAC.
41  *   <li>Naming_Packet[8 - var]: the encrypted name (with 8-byte nonce appended to the front).
42  * </ol>
43  */
44 @TargetApi(VERSION_CODES.M)
45 public final class NamingEncoder {
46 
47     static final int EXTRACT_HMAC_SIZE = 8;
48     static final int MAX_LENGTH_OF_NAME = 48;
49 
NamingEncoder()50     private NamingEncoder() {
51     }
52 
53     /**
54      * Encodes the name to naming packet by the given secret.
55      *
56      * @param secret AES-128 key for encryption.
57      * @param name the given name to be encoded.
58      * @return the encrypted data with the 8-byte extracted HMAC appended to the front.
59      * @throws GeneralSecurityException if the given key or name is invalid for encoding.
60      */
encodeNamingPacket(byte[] secret, String name)61     public static byte[] encodeNamingPacket(byte[] secret, String name)
62             throws GeneralSecurityException {
63         if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
64             throw new GeneralSecurityException(
65                     "Incorrect secret for encoding name packet, secret.length = "
66                             + (secret == null ? "NULL" : secret.length));
67         }
68 
69         if ((name == null) || (name.length() == 0) || (Utf8.encodedLength(name)
70                 > MAX_LENGTH_OF_NAME)) {
71             throw new GeneralSecurityException(
72                     "Invalid name for encoding name packet, Utf8.encodedLength(name) = "
73                             + (name == null ? "NULL" : Utf8.encodedLength(name)));
74         }
75 
76         byte[] encryptedData = AesCtrMultipleBlockEncryption.encrypt(secret, name.getBytes(UTF_8));
77         byte[] extractedHmac =
78                 Arrays.copyOf(HmacSha256.build(secret, encryptedData), EXTRACT_HMAC_SIZE);
79 
80         return concat(extractedHmac, encryptedData);
81     }
82 
83     /**
84      * Decodes the name from naming packet by the given secret.
85      *
86      * @param secret AES-128 key used in the encryption to decrypt data.
87      * @param namingPacket naming packet which is encoded by the given secret..
88      * @return the name decoded from the given packet.
89      * @throws GeneralSecurityException if the given key or naming packet is invalid for decoding.
90      */
decodeNamingPacket(byte[] secret, byte[] namingPacket)91     public static String decodeNamingPacket(byte[] secret, byte[] namingPacket)
92             throws GeneralSecurityException {
93         if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
94             throw new GeneralSecurityException(
95                     "Incorrect secret for decoding name packet, secret.length = "
96                             + (secret == null ? "NULL" : secret.length));
97         }
98         if (namingPacket == null
99                 || namingPacket.length <= EXTRACT_HMAC_SIZE
100                 || namingPacket.length > (MAX_LENGTH_OF_NAME + EXTRACT_HMAC_SIZE + NONCE_SIZE)) {
101             throw new GeneralSecurityException(
102                     "Naming packet size is incorrect, namingPacket.length is "
103                             + (namingPacket == null ? "NULL" : namingPacket.length));
104         }
105 
106         if (!verifyHmac(secret, namingPacket)) {
107             throw new GeneralSecurityException(
108                     "Verify HMAC failed, could be incorrect key or naming packet.");
109         }
110         byte[] encryptedData = Arrays
111                 .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length);
112         return new String(AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData), UTF_8);
113     }
114 
115     // Computes the HMAC of the given key and name, and compares the first 8-byte of the HMAC result
116     // with the one from name packet. Must call constant-time comparison to prevent a possible
117     // timing attack, e.g. time the same MAC with all different first byte for a given ciphertext,
118     // the right one will take longer as it will fail on the second byte's verification.
verifyHmac(byte[] key, byte[] namingPacket)119     private static boolean verifyHmac(byte[] key, byte[] namingPacket)
120             throws GeneralSecurityException {
121         byte[] packetHmac = Arrays.copyOfRange(namingPacket, /* from= */ 0, EXTRACT_HMAC_SIZE);
122         byte[] encryptedData = Arrays
123                 .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length);
124         byte[] computedHmac = Arrays
125                 .copyOf(HmacSha256.build(key, encryptedData), EXTRACT_HMAC_SIZE);
126 
127         return HmacSha256.compareTwoHMACs(packetHmac, computedHmac);
128     }
129 }
130