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