• 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.generateNonce;
20 
21 import static com.google.common.primitives.Bytes.concat;
22 
23 import java.security.GeneralSecurityException;
24 import java.util.Arrays;
25 
26 /**
27  * Message stream utilities for encoding raw packet with HMAC.
28  *
29  * <p>Encoded packet is:
30  *
31  * <ol>
32  *   <li>Packet[0 - (data length - 1)]: the raw data.
33  *   <li>Packet[data length - (data length + 7)]: the 8-byte message nonce.
34  *   <li>Packet[(data length + 8) - (data length + 15)]: the 8-byte of HMAC.
35  * </ol>
36  */
37 public class MessageStreamHmacEncoder {
38     public static final int EXTRACT_HMAC_SIZE = 8;
39     public static final int SECTION_NONCE_LENGTH = 8;
40 
MessageStreamHmacEncoder()41     private MessageStreamHmacEncoder() {}
42 
43     /** Encodes Message Packet. */
encodeMessagePacket(byte[] accountKey, byte[] sectionNonce, byte[] data)44     public static byte[] encodeMessagePacket(byte[] accountKey, byte[] sectionNonce, byte[] data)
45             throws GeneralSecurityException {
46         checkAccountKeyAndSectionNonce(accountKey, sectionNonce);
47 
48         if (data == null || data.length == 0) {
49             throw new GeneralSecurityException("No input data for encodeMessagePacket");
50         }
51 
52         byte[] messageNonce = generateNonce();
53         byte[] extractedHmac =
54                 Arrays.copyOf(
55                         HmacSha256.buildWith64BytesKey(
56                                 accountKey, concat(sectionNonce, messageNonce, data)),
57                         EXTRACT_HMAC_SIZE);
58 
59         return concat(data, messageNonce, extractedHmac);
60     }
61 
62     /** Verifies Hmac. */
verifyHmac(byte[] accountKey, byte[] sectionNonce, byte[] data)63     public static boolean verifyHmac(byte[] accountKey, byte[] sectionNonce, byte[] data)
64             throws GeneralSecurityException {
65         checkAccountKeyAndSectionNonce(accountKey, sectionNonce);
66         if (data == null) {
67             throw new GeneralSecurityException("data is null");
68         }
69         if (data.length <= EXTRACT_HMAC_SIZE + SECTION_NONCE_LENGTH) {
70             throw new GeneralSecurityException("data.length too short");
71         }
72 
73         byte[] hmac = Arrays.copyOfRange(data, data.length - EXTRACT_HMAC_SIZE, data.length);
74         byte[] messageNonce =
75                 Arrays.copyOfRange(
76                         data,
77                         data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH,
78                         data.length - EXTRACT_HMAC_SIZE);
79         byte[] rawData = Arrays.copyOf(
80                 data, data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH);
81         return Arrays.equals(
82                 Arrays.copyOf(
83                         HmacSha256.buildWith64BytesKey(
84                                 accountKey, concat(sectionNonce, messageNonce, rawData)),
85                         EXTRACT_HMAC_SIZE),
86                 hmac);
87     }
88 
checkAccountKeyAndSectionNonce(byte[] accountKey, byte[] sectionNonce)89     private static void checkAccountKeyAndSectionNonce(byte[] accountKey, byte[] sectionNonce)
90             throws GeneralSecurityException {
91         if (accountKey == null || accountKey.length == 0) {
92             throw new GeneralSecurityException(
93                     "Incorrect accountKey for encoding message packet, accountKey.length = "
94                             + (accountKey == null ? "NULL" : accountKey.length));
95         }
96 
97         if (sectionNonce == null || sectionNonce.length != SECTION_NONCE_LENGTH) {
98             throw new GeneralSecurityException(
99                     "Incorrect sectionNonce for encoding message packet, sectionNonce.length = "
100                             + (sectionNonce == null ? "NULL" : sectionNonce.length));
101         }
102     }
103 }
104