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