· 4 years ago · Jun 22, 2021, 04:46 PM
1package com.blectap.authentificator;
2
3import android.app.Activity;
4import android.bluetooth.BluetoothDevice;
5import android.util.Log;
6
7import com.blectap.Config;
8import com.blectap.LogList;
9import com.blectap.MainActivity;
10import com.blectap.authentificator.credentials.CredentialDB;
11import com.blectap.authentificator.crypto.AuthGetAssertion;
12import com.blectap.authentificator.crypto.AuthMakeCredential;
13import com.blectap.authentificator.credentials.CredentialStruct;
14import com.blectap.authentificator.messages.GetAssertionRequest;
15import com.blectap.authentificator.messages.GetInfoResponse;
16import com.blectap.authentificator.messages.MakeCredentialRequest;
17import com.blectap.keyless.KeylessWrapper;
18import com.blectap.keyless.utils.Constants;
19import com.blectap.keyless.utils.Storage;
20
21import java.io.IOException;
22import java.util.Arrays;
23import java.util.concurrent.atomic.AtomicInteger;
24
25import co.nstant.in.cbor.CborException;
26
27import static com.blectap.authentificator.FidoError.*;
28
29/**
30 * Command Name Command Value Has parameters?
31 * authenticatorMakeCredential 0x01 yes
32 * authenticatorGetAssertion 0x02 yes
33 * authenticatorGetInfo 0x04 no
34 * authenticatorClientPIN 0x06 yes
35 * authenticatorReset 0x07 no
36 * authenticatorGetNextAssertion 0x08 no
37 * authenticatorVendorFirst 0x40 NA
38 * authenticatorVendorLast 0xBF NA
39 */
40public class AuthCommands {
41 public static final String TAG = AuthCommands.class.getSimpleName();
42
43 public static final byte Authenticator_NONE = 0x0;
44 public static final byte Authenticator_Make_Credential = 0x1;
45 public static final byte Authenticator_Get_Assertion = 0x2;
46 public static final byte Authenticator_Get_Info = 0x4;
47 public static final byte Authenticator_ClientPIN = 0x6;
48 public static final byte Authenticator_Reset = 0x7;
49 public static final byte Authenticator_Get_NextAssertion = 0x8;
50 //public static final byte Authenticator_Vendor_First = 0x40;
51 //public static final byte Authenticator_Vendor_Last = 0xBF;
52
53 /** #RECEIVED COMMANDS
54 * Received new command request for API message from FIDO web service
55 * @param request request data, starting 83, size, size, point
56 * @param device device to send answer
57 */
58 public static void receivedCommand(byte[] request, BluetoothDevice device){
59 FidoKeepalive.getInstance().processing(device); // notice about processing
60
61 byte[] data = Arrays.copyOfRange(request, 4, request.length);
62
63 switch (request[3]){
64 case Authenticator_Make_Credential:
65 authenticatorMakeCredential(data, device);
66 break;
67 case Authenticator_Get_Assertion:
68 authenticatorGetAssertion(data, device);
69 break;
70 case Authenticator_Get_Info:
71 authenticatorGetInfo(device);
72 break;
73 case Authenticator_ClientPIN:
74 authenticatorClientPIN(device);
75 break;
76 case Authenticator_Reset:
77 authenticatorReset(device);
78 break;
79 case Authenticator_Get_NextAssertion:
80 authenticatorGetNextAssertion(device);
81 break;
82 default:
83 LogList.log("Wrong command "); // send error
84 FidoError.error(FidoError.ERR_INVALID_COMMAND, device);
85 return;
86 }
87 }
88
89 /**
90 * AuthenticatorMakeCredentials
91 * @param requestData request data to parse
92 * @param device device to reponse
93 */
94 private static void authenticatorMakeCredential(byte[] requestData, BluetoothDevice device){
95 LogList.getInstance().log("authenticatorMakeCredential request");
96
97 // callback for response
98 FidoCallback callback = new FidoCallback() {
99 @Override
100 public void onFidoSucceeded(byte[] data) {
101 try {
102 FidoCommands.sendMessageResponse(data, device);
103 } catch (IOException e) {
104 e.printStackTrace();
105 onFidoFailed("Error formatting response", ERR_OTHER);
106 }
107 }
108
109 @Override
110 public void onFidoFailed(String error, byte errorCode) {
111 Log.e(TAG, error);
112 FidoError.error(errorCode, device);
113 }
114 };
115
116 try {
117 MakeCredentialRequest request = MakeCredentialRequest.decode(requestData); //parse request
118 if(! request.pubKeyCredParams.contains(-7)){ //check supported algorithm
119 callback.onFidoFailed("CTAP2_ERR_UNSUPPORTED_ALGORITHM", ERR_UNSUPPORTED_ALGORITHM);
120 return;
121 }
122 if(!Config.EnableUserVerification && request.userVerification){ //not supported
123 callback.onFidoFailed("ERR_INVALID_OPTION", ERR_INVALID_OPTION);
124 return;
125 }
126 if(!Config.EnableResidentalKey && request.residentKey){
127 callback.onFidoFailed("ERR_INVALID_OPTION", ERR_INVALID_OPTION);
128 return;
129 }
130 if(Config.EnableUserPresence && request.userPresence) {
131 // this _REALLY_ is expected behavior by FIDO2.0 conformance tool:
132 // if "user presence" is supported and "user presence" is requested, thrown an error
133 callback.onFidoFailed("ERR_INVALID_OPTION", ERR_INVALID_OPTION);
134 return;
135 }
136 if(request.error != 0){
137 callback.onFidoFailed("ERROR Parsing MakeCredential", (byte)request.error);
138 return;
139 }
140 showConfirmationAlert(request.rpEntity.id);
141
142 MakeCredentialRequest.decodeExcludeList(requestData, request);
143 if(request.error != 0){
144 callback.onFidoFailed("ERROR Parsing MakeCredential", (byte)request.error);
145 return;
146 }
147
148 AuthMakeCredential makeCredential = new AuthMakeCredential(request); //process request
149 CredentialStruct credential = makeCredential.process(callback); // start creating credentials, then call callback
150 CredentialDB.getInstance().saveCredential(credential); //save credentials for later
151 Storage.remove(BleCtapApplication.getContext(), Constants.SECRET_KEY);
152 }catch (CborException e) {
153 Log.e("CBOR parsing", e.getMessage());
154 callback.onFidoFailed("ERR_INVALID_PARAMETER", ERR_INVALID_PARAMETER);
155 e.printStackTrace();
156 }
157 catch (Exception e) {
158 Log.e("makeCredential", e.getMessage());
159 callback.onFidoFailed("Error makeCredential", ERR_INVALID_PARAMETER);
160 e.printStackTrace();
161 }
162 }
163
164 public static byte[] authenticatorGetAssertion(byte[] request, BluetoothDevice device){
165 LogList.getInstance().log("authenticatorGetAssertion request");
166
167 // callback for response
168 FidoCallback callback = new FidoCallback() {
169 @Override
170 public void onFidoSucceeded(byte[] data) {
171 try {
172 FidoCommands.sendMessageResponse(data, device);
173 } catch (IOException e) {
174 e.printStackTrace();
175 onFidoFailed("Error formatting response", ERR_OTHER);
176 }
177 }
178
179 @Override
180 public void onFidoFailed(String error, byte errCode) {
181 Log.e(TAG, error);
182 FidoError.error(errCode, device);
183 }
184 };
185
186 try{
187 GetAssertionRequest requestAssertion = new GetAssertionRequest().decode(request);
188 if (Storage.get(BleCtapApplication.getContext(), Constants.SECRET_KEY) == null)
189 showConfirmationAlert(requestAssertion.rpId);
190 AuthGetAssertion getAssertion = new AuthGetAssertion(requestAssertion);
191 getAssertion.process(callback);
192 Storage.remove(BleCtapApplication.getContext(), Constants.SECRET_KEY);
193 } catch (CborException e) {
194 Log.e("CBOR parsing", e.getMessage());
195 callback.onFidoFailed("ERR_INVALID_PARAMETER", ERR_INVALID_PARAMETER);
196 e.printStackTrace();
197 }
198 catch (Exception e) {
199 Log.e("makeCredential", e.getMessage());
200 callback.onFidoFailed("Error makeCredential", ERR_INVALID_PARAMETER);
201 e.printStackTrace();
202 }
203 return new byte[0];
204 }
205
206 /**
207 * AuthenticatorGetInfo message
208 * First message to call, retreive authenticator information
209 * @param device Device to response
210 */
211 public static void authenticatorGetInfo(BluetoothDevice device){
212 LogList.getInstance().log("authenticatorGetInfo request");
213
214 try {
215 byte[] getInfo = new GetInfoResponse().encode(); // create response
216 FidoCommands.sendMessageResponse(getInfo, device);
217 } catch (CborException | IOException e) {
218 LogList.getInstance().log("Error parsing getInfo");
219 Log.e("getInfo", e.getMessage());
220 FidoError.error(FidoError.ERR_OTHER, device);
221 e.printStackTrace();
222 }
223 }
224
225 /**
226 * AuthenticatorClientPin
227 * Set pin to verify user
228 */
229 public static void authenticatorClientPIN(BluetoothDevice device){
230 LogList.getInstance().log("authenticatorClientPIN request");
231
232 FidoKeepalive.stop();
233 FidoError.error(FidoError.ERR_OTHER, device);
234 }
235
236 /**
237 * AuthenticatorReset message
238 * reset saved credentials to default settings
239 */
240 public static void authenticatorReset(BluetoothDevice device){
241 LogList.getInstance().log("authenticatorReset request");
242
243 showConfirmationAlert("");
244
245 //reset saved credentials
246 CredentialDB.getInstance().clearCredentials();
247 FidoKeepalive.stop();
248
249 try {
250 FidoCommands.sendMessageResponse(new byte[0], device);
251 } catch (IOException e) {
252 LogList.getInstance().log("Error parsing getInfo");
253 Log.e("getInfo", e.getMessage());
254 FidoError.error(FidoError.ERR_OTHER, device);
255 e.printStackTrace();
256 }
257
258 Storage.remove(BleCtapApplication.getContext(), Constants.SECRET_KEY);
259 }
260
261 public static void authenticatorGetNextAssertion(BluetoothDevice device){
262 LogList.getInstance().log("authenticatorGetNextAssertion request");
263
264 FidoKeepalive.stop();
265 FidoError.error(FidoError.ERR_NOT_ALLOWED, device);
266 }
267 /**
268 * Call the SDK to authenticate user via biometrics and retrieve the key from SDK.
269 * BEWARE: this is a blocking call!!
270 * and it is supposed to be called from the ble thread. Take care not to invoke this on main thread,
271 * as it may make the app crash (or wait undefinitely). When the authentication is completed,
272 * either successfully or not, the [KeylessWrapper.keylessOperationFinished] atomic boolean is
273 * properly updated in [KeylessDelegateCustom] class in order to "unlock" this thread.
274 */
275 public static void authenticateWithKeyless() {
276 //FIXME: this should be investigated
277 try {
278 Thread.sleep(600);
279 } catch (InterruptedException e) {
280 e.printStackTrace();
281 }
282
283 //Authenticate with Keyless in order to retrieve the key to decrypt fido state
284 if (KeylessWrapper.keylessOperationFinished.get()) {
285 KeylessWrapper.authenticate(BleCtapApplication.getContext());
286 }
287 //Wait until the authentication is finished
288 while (!KeylessWrapper.keylessOperationFinished.get());
289
290 //TODO: what if authentication went wrong? Implement error case!
291 }
292
293 private static void showConfirmationAlert(String rpId) {
294 Activity currentActivity = BleCtapApplication.getInstance().getCurrentActivity();
295 if (currentActivity instanceof MainActivity) {
296
297 MainActivity mainActivity = (MainActivity) currentActivity;
298 AtomicInteger confirmationResult = new AtomicInteger(Constants.CONFIRMATION_UNDEFINED);
299 mainActivity.showConfirmationAlert(confirmationResult, rpId);
300
301 while (confirmationResult.get() == Constants.CONFIRMATION_UNDEFINED);
302 if (confirmationResult.get() == Constants.CONFIRMATION_OK)
303 authenticateWithKeyless();
304
305 }
306 }
307}
308