wip: key handles encoding

This commit is contained in:
Karol Sójko
2023-04-11 12:41:11 +02:00
parent 0ee8ae6a40
commit 76869e40b8
3 changed files with 73 additions and 19 deletions

View File

@@ -4,8 +4,8 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.util.Log;
import android.util.Base64;
import android.util.Log;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ActivityEventListener;
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
@@ -22,6 +23,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import com.google.android.gms.fido.fido2.Fido2ApiClient;
import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse;
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialType;
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor;
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRequestOptions;
@@ -36,6 +38,11 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
private static final int SIGN_REQUEST_CODE = 111;
private static final String LOGS_TAG = "Fido2ApiModule";
private static final String RP_ID = "app.standardnotes.com";
private Promise signInPromise;
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
@@ -43,12 +50,26 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
if (requestCode == SIGN_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
byte[] response = intent.getByteArrayExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA);
String responseString = Base64.encodeToString(response, Base64.DEFAULT);
Log.i("Fido2ApiModule", "FIDO response: " + responseString);
if (intent.hasExtra(Fido.FIDO2_KEY_ERROR_EXTRA)) {
AuthenticatorErrorResponse authenticatorErrorResponse =
AuthenticatorErrorResponse.deserializeFromBytes(intent.getByteArrayExtra(Fido.FIDO2_KEY_ERROR_EXTRA));
Log.e(LOGS_TAG, "FIDO2_KEY_ERROR_EXTRA Security Key: " + authenticatorErrorResponse.getErrorMessage());
signInPromise.reject("FIDO2_KEY_ERROR_EXTRA", authenticatorErrorResponse.getErrorMessage());
signInPromise = null;
return;
}
Log.i(LOGS_TAG, "FIDO response OK");
signInPromise.resolve("OK");
} else {
Log.e("Fido2ApiModule", "FIDO sign in failed");
Log.e(LOGS_TAG, "FIDO sign in failed");
signInPromise.reject("FIDO_SIGN_IN_FAILED", "FIDO sign in failed");
}
signInPromise = null;
}
}
};
@@ -68,31 +89,42 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void promptForU2FAuthentication(String authenticationOptionsJSONString) throws JSONException {
Log.i("Fido2ApiModule", "Prompt for U2F sign in with options: " + authenticationOptionsJSONString);
public void promptForU2FAuthentication(String authenticationOptionsJSONString, Promise promise) throws JSONException {
Log.i(LOGS_TAG, "Prompt for U2F sign in with options: " + authenticationOptionsJSONString);
signInPromise = promise;
JSONObject authenticationOptions = new JSONObject(authenticationOptionsJSONString);
ArrayList<PublicKeyCredentialDescriptor> allowedKeys = new ArrayList<PublicKeyCredentialDescriptor>();
JSONArray keyHandles = authenticationOptions.getJSONArray("allowCredentials");
for (int i = 0, size = keyHandles.length(); i < size; i++) {
JSONObject keyHandle = keyHandles.getJSONObject(i);
JSONArray allowedCredentials = authenticationOptions.getJSONArray("allowCredentials");
for (int i = 0, size = allowedCredentials.length(); i < size; i++) {
JSONObject allowedCredential = allowedCredentials.getJSONObject(i);
Log.i(LOGS_TAG, "Adding credential id: " + allowedCredential.getString("id"));
allowedKeys.add(
new PublicKeyCredentialDescriptor(
PublicKeyCredentialType.PUBLIC_KEY.toString(),
keyHandle.getString("id").getBytes(),
this.convertBase64URLStringToBytes(allowedCredential.getString("id")),
null
)
);
}
String challenge = authenticationOptions.getString("challenge");
Double timeout = authenticationOptions.getDouble("timeout");
Log.i(LOGS_TAG, "Challenge: " + challenge);
Log.i(LOGS_TAG, "Timeout: " + timeout);
Log.i(LOGS_TAG, "RP ID: " + RP_ID);
Log.i(LOGS_TAG, "Allowed keys size: " + allowedKeys.size());
PublicKeyCredentialRequestOptions.Builder optionsBuilder = new PublicKeyCredentialRequestOptions
.Builder()
.setRpId("app.standardnotes.com")
.setRpId(RP_ID)
.setAllowList(allowedKeys)
.setChallenge(authenticationOptions.getString("challenge").getBytes())
.setTimeoutSeconds(authenticationOptions.getDouble("timeout"));
.setChallenge(this.convertBase64URLStringToBytes(challenge))
.setTimeoutSeconds(timeout);
PublicKeyCredentialRequestOptions options = optionsBuilder.build();
@@ -105,7 +137,7 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
@Override
public void onSuccess(PendingIntent fido2PendingIntent) {
if (fido2PendingIntent == null) {
Log.e("Fido2ApiModule", "No pending FIDO intent returned");
Log.e(LOGS_TAG, "No pending FIDO intent returned");
return;
}
@@ -119,7 +151,7 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
0
);
} catch (IntentSender.SendIntentException exception) {
Log.e("Fido2ApiModule", "Error starting FIDO intent: " + exception.getMessage());
Log.e(LOGS_TAG, "Error starting FIDO intent: " + exception.getMessage());
}
}
}
@@ -129,9 +161,22 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e("Fido2ApiModule", "Error getting FIDO intent: " + e.getMessage());
Log.e(LOGS_TAG, "Error getting FIDO intent: " + e.getMessage());
signInPromise.reject("unknown", e.getMessage());
}
}
);
}
private byte[] convertBase64URLStringToBytes(String base64URLString) {
String base64String = base64URLString.replace('-', '+').replace('_', '/');
int padding = (4 - (base64String.length() % 4)) % 4;
for (int i = 0; i < padding; i++) {
base64String += '=';
}
byte[] decoded = Base64.decode(base64String, Base64.DEFAULT);
return decoded;
}
}

View File

@@ -43,4 +43,7 @@ newArchEnabled=false
hermesEnabled=true
# Set AsyncStorage limit
AsyncStorage_db_size_in_MB=50
AsyncStorage_db_size_in_MB=50
# The URL of the server
host=https://app.standardnotes.com

View File

@@ -75,7 +75,13 @@ export class MobileDevice implements MobileDeviceInterface {
async promptForU2FAuthentication(authenticationOptionsJSONString: string): Promise<void> {
const { Fido2ApiModule } = NativeModules
await Fido2ApiModule.promptForU2FAuthentication(authenticationOptionsJSONString)
try {
const response = await Fido2ApiModule.promptForU2FAuthentication(authenticationOptionsJSONString)
this.consoleLog('Fido2ApiModule.promptForU2FAuthentication response', response)
} catch (error) {
this.consoleLog('Fido2ApiModule.promptForU2FAuthentication error', (error as Error).message)
}
}
purchaseSubscriptionIAP(plan: AppleIAPProductId): Promise<AppleIAPReceipt | undefined> {