1 /* 2 * Copyright (C) 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.uwb.secure.csml; 18 19 import static com.android.server.uwb.secure.iso7816.Iso7816Constants.EXTENDED_HEAD_LIST; 20 21 import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_ONE_TO_MANY; 22 import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_UNICAST; 23 24 import androidx.annotation.NonNull; 25 26 import com.android.server.uwb.secure.iso7816.TlvDatum; 27 import com.android.server.uwb.secure.iso7816.TlvDatum.Tag; 28 import com.android.server.uwb.secure.iso7816.TlvParser; 29 import com.android.server.uwb.util.ObjectIdentifier; 30 31 import com.google.common.primitives.Bytes; 32 33 import java.security.SecureRandom; 34 import java.util.Objects; 35 import java.util.Optional; 36 import java.util.Random; 37 38 /** 39 * Utils used by the FiRa CSML related modules. 40 */ 41 public final class CsmlUtil { CsmlUtil()42 private CsmlUtil() {} 43 44 public static final Tag OID_TAG = new Tag((byte) 0x06); 45 46 private static final Tag EXTENDED_HEAD_LIST_TAG = new Tag(EXTENDED_HEAD_LIST); 47 // FiRa CSML 8.2.2.7.1.4 48 private static final Tag TERMINATE_SESSION_DO_TAG = new Tag((byte) 0x80); 49 private static final Tag TERMINATE_SESSION_TOP_DO_TAG = new Tag((byte) 0xBF, (byte) 0x79); 50 51 public static final Tag UWB_CONFIG_AVAILABLE_TAG = new Tag((byte) 0x87); 52 public static final Tag SESSION_DATA_DO_TAG = new Tag((byte) 0xBF, (byte) 0x78); 53 public static final Tag SESSION_ID_TAG = new Tag((byte) 0x81); 54 public static final Tag CONTROLEE_INFO_DO_TAG = new Tag((byte) 0xBF, (byte) 0x70); 55 56 /** 57 * Check if the data represents the session data is not available, 58 * which defined as 'UWB config is unavailable in the CSML. 59 * @param data the single TLV data. 60 * @return true the session data is not available, false otherwise. 61 */ isSessionDataNotAvailable(@onNull byte[] data)62 public static boolean isSessionDataNotAvailable(@NonNull byte[] data) { 63 TlvDatum tlvDatum = TlvParser.parseOneTlv(data); 64 if (tlvDatum != null 65 && Objects.equals(tlvDatum.tag, CsmlUtil.UWB_CONFIG_AVAILABLE_TAG) 66 && Objects.deepEquals(tlvDatum.value, new byte[]{(byte) 0x00})) { 67 return true; 68 } 69 return false; 70 } 71 72 /** 73 * check if the data contains the Session Data. 74 */ isSessionDataDo(@onNull byte[] data)75 public static boolean isSessionDataDo(@NonNull byte[] data) { 76 return isSpecifiedDo(SESSION_DATA_DO_TAG, data); 77 } 78 79 /** 80 * Get the TLV for get session data command. 81 */ constructSessionDataGetDoTlv()82 public static TlvDatum constructSessionDataGetDoTlv() { 83 return constructGetDoTlv(SESSION_DATA_DO_TAG); 84 } 85 86 /** 87 * check if the data contains the ControleeInfo DO. 88 * @param data 89 * @return 90 */ isControleeInfoDo(@onNull byte[] data)91 public static boolean isControleeInfoDo(@NonNull byte[] data) { 92 return isSpecifiedDo(CONTROLEE_INFO_DO_TAG, data); 93 } 94 isSpecifiedDo(@onNull Tag specifiedTag, @NonNull byte[] data)95 private static boolean isSpecifiedDo(@NonNull Tag specifiedTag, @NonNull byte[] data) { 96 TlvDatum tlvDatum = TlvParser.parseOneTlv(data); 97 if (tlvDatum != null && Objects.equals(tlvDatum.tag, specifiedTag)) { 98 return true; 99 } 100 101 return false; 102 } 103 104 /** 105 * Encode the {@link ObjectIdentifier} as TLV format, which is used as the payload of TlvDatum 106 * @param oid the ObjectIdentifier 107 * @return The instance of TlvDatum. 108 */ 109 @NonNull encodeObjectIdentifierAsTlv(@onNull ObjectIdentifier oid)110 public static TlvDatum encodeObjectIdentifierAsTlv(@NonNull ObjectIdentifier oid) { 111 return new TlvDatum(OID_TAG, oid.value); 112 } 113 114 /** 115 * Construct the TLV payload for {@link getDoCommand} top Tag (not nested) 116 * defined in ISO7816-4. 117 */ 118 @NonNull constructGetDoTlv(@onNull Tag tag)119 public static TlvDatum constructGetDoTlv(@NonNull Tag tag) { 120 return new TlvDatum(EXTENDED_HEAD_LIST_TAG, constructDeepestTagOfGetDoAllContent(tag)); 121 } 122 123 /** 124 * Get the TLV for terminate session command. 125 */ 126 @NonNull constructTerminateSessionGetDoTlv()127 public static TlvDatum constructTerminateSessionGetDoTlv() { 128 // TODO: confirm the structure defined in CSML 8.2.2.7.1.4, which is not clear. 129 byte[] value = constructDeepestTagOfGetDoAllContent(TERMINATE_SESSION_DO_TAG); 130 return constructGetOrPutDoTlv( 131 new TlvDatum(TERMINATE_SESSION_TOP_DO_TAG, value)); 132 } 133 134 /** 135 * Get the TLV for session id in the session data. 136 */ 137 @NonNull constructGetSessionIdGetDoTlv()138 public static TlvDatum constructGetSessionIdGetDoTlv() { 139 byte[] value = constructDeepestTagOfGetDoAllContent(SESSION_ID_TAG); 140 return constructGetOrPutDoTlv(new TlvDatum(SESSION_DATA_DO_TAG, value)); 141 } 142 143 /** 144 * Construct the TLV payload for @link getDoCommand} with 145 * EXTENTED HEADER LIST defined in ISO7816-4. 146 */ 147 @NonNull constructGetOrPutDoTlv(TlvDatum tlvDatum)148 public static TlvDatum constructGetOrPutDoTlv(TlvDatum tlvDatum) { 149 return new TlvDatum(EXTENDED_HEAD_LIST_TAG, tlvDatum); 150 } 151 152 /** 153 * Construct the TLV for {@link GetDoCommand} or {@link PutDoCommand}. 154 */ constructGetOrPutDoTlv(byte[] tlvData)155 public static TlvDatum constructGetOrPutDoTlv(byte[] tlvData) { 156 return new TlvDatum(EXTENDED_HEAD_LIST_TAG, tlvData); 157 } 158 159 /** 160 * Get all content for a specific/deepest Tag in the DO tree with Extented Header List. 161 */ 162 @NonNull constructDeepestTagOfGetDoAllContent(Tag tag)163 public static byte[] constructDeepestTagOfGetDoAllContent(Tag tag) { 164 return Bytes.concat(tag.literalValue, new byte[] {(byte) 0x00}); 165 } 166 167 /** 168 * Get part of content for a specific/deepest Tag with Extenteed Header List. 169 */ 170 @NonNull constructDeepestTagOfGetDoPartContent(Tag tag, int len)171 public static byte[] constructDeepestTagOfGetDoPartContent(Tag tag, int len) { 172 if (len > 256) { 173 throw new IllegalArgumentException("The content length can not be over 256 bytes"); 174 } 175 176 return Bytes.concat(tag.literalValue, new byte[] { (byte) len}); 177 } 178 179 /** 180 * Generates a session id, it is an integer random value and greater than 0. 181 */ generateRandomSessionId()182 public static int generateRandomSessionId() { 183 Random random = new Random(); 184 int sessionId = random.nextInt(); 185 while (sessionId <= 0) { 186 sessionId = random.nextInt(); 187 } 188 189 return sessionId; 190 } 191 192 /** 193 * Generates the session data used by both controller and controlee by combining 194 * all information from the controller and the controlee. 195 * 196 * @param localCap The UwbCapability of local device. 197 * @param controleeInfo The {@link ControleeInfo} which includes UwbCapability of 198 * the controlee and other information. 199 200 * @param shareSessionId The main session Id shared amid sessions for multicast case 201 * @param sharedSessionKeyInfo The session key info shared amid sessions for multicast case 202 * @param uniqueSessionId The session Id/sub session Id(multicast), which is unique 203 * per session 204 * @param needSecureRangingInfo If the session Id and key are no derived in 205 * applet (AKA default sessionId/Key), the session 206 * secure info should be provided in {@link SessionData}. 207 * @return The Session Data used by both controller and controlee. 208 * @throws IllegalStateException The {@link SessionData} is not agreed by both devices. 209 */ 210 @NonNull generateSessionData( @onNull UwbCapability localCap, @NonNull ControleeInfo controleeInfo, @NonNull Optional<Integer> shareSessionId, @NonNull Optional<byte[]> sharedSessionKeyInfo, int uniqueSessionId, boolean needSecureRangingInfo)211 public static SessionData generateSessionData( 212 @NonNull UwbCapability localCap, 213 @NonNull ControleeInfo controleeInfo, 214 @NonNull Optional<Integer> shareSessionId, 215 @NonNull Optional<byte[]> sharedSessionKeyInfo, 216 int uniqueSessionId, 217 boolean needSecureRangingInfo) throws IllegalStateException { 218 SessionData.Builder builder = new SessionData.Builder(); 219 SecureRangingInfo.Builder secureRangingInfoBuilder = new SecureRangingInfo.Builder(); 220 if (shareSessionId.isPresent()) { 221 // multicast case 222 builder.setSessionId(shareSessionId.get()); 223 builder.setSubSessionId(uniqueSessionId); 224 secureRangingInfoBuilder.setUwbSessionKeyInfo(sharedSessionKeyInfo.get()); 225 if (needSecureRangingInfo) { 226 secureRangingInfoBuilder.setUwbSubSessionKeyInfo(generate256BitRandomKeyInfo()); 227 } 228 } else { 229 builder.setSessionId(uniqueSessionId); 230 if (needSecureRangingInfo) { 231 secureRangingInfoBuilder.setUwbSessionKeyInfo(generate256BitRandomKeyInfo()); 232 } 233 } 234 if (shareSessionId.isPresent() || needSecureRangingInfo) { 235 builder.setSecureRangingInfo(secureRangingInfoBuilder.build()); 236 } 237 if (controleeInfo.mUwbCapability.isEmpty()) { 238 // use default session data 239 return builder.build(); 240 } 241 UwbCapability remoteCap = controleeInfo.mUwbCapability.get(); 242 if (!localCap.isCompatibleTo(remoteCap)) { 243 throw new IllegalStateException("devices are not compatible."); 244 } 245 246 ConfigurationParams.Builder paramsBuilder = new ConfigurationParams.Builder(); 247 paramsBuilder.setPhyVersion( 248 localCap.getPreferredPhyVersion(remoteCap.mMinPhyVersionSupported)); 249 paramsBuilder.setMacVersion( 250 localCap.getPreferredMacVersion(remoteCap.mMinMacVersionSupported)); 251 paramsBuilder.setStsConfig( 252 localCap.getPreferredStsConfig(remoteCap.mStsConfig, shareSessionId.isPresent())); 253 Optional<Integer> commonChannel = localCap.getPreferredChannel(remoteCap.mChannels); 254 if (commonChannel.isEmpty()) { 255 throw new IllegalStateException("no common channel supported by both devices."); 256 } 257 paramsBuilder.setChannel(commonChannel.get()); 258 paramsBuilder.setCcConstraintLength( 259 localCap.getPreferredConstrainLengthOfConvolutionalCode( 260 remoteCap.mCcConstraintLength)); 261 paramsBuilder.setHoppingMode(localCap.getPreferredHoppingMode(remoteCap.mHoppingMode)); 262 paramsBuilder.setRframeConfig(localCap.getPreferredRframeConfig(remoteCap.mRframeConfig)); 263 paramsBuilder.setMultiNodeMode( 264 shareSessionId.isEmpty() ? MULTI_NODE_MODE_UNICAST : MULTI_NODE_MODE_ONE_TO_MANY); 265 paramsBuilder.setScheduleMode(localCap.getPreferredScheduleMode(remoteCap.mScheduledMode)); 266 paramsBuilder.setBlockStriding( 267 localCap.getPreferredBlockStriding(remoteCap.mBlockStriding)); 268 paramsBuilder.setRangingMethod( 269 localCap.getPreferredRangingMethod(remoteCap.mRangingMethod)); 270 paramsBuilder.setMacAddressMode( 271 localCap.getPreferredMacAddressMode(remoteCap.mExtendedMacSupport)); 272 273 builder.setConfigParams(paramsBuilder.build()); 274 275 return builder.build(); 276 } 277 278 /** Generates the 256bit key info */ 279 @NonNull generate256BitRandomKeyInfo()280 public static byte[] generate256BitRandomKeyInfo() { 281 SecureRandom secureRandom = new SecureRandom(); 282 byte[] keyBits256 = new byte[8]; 283 secureRandom.nextBytes(keyBits256); 284 return keyBits256; 285 } 286 } 287