1
0
mirror of https://github.com/bitwarden/mobile synced 2026-03-02 19:31:10 +00:00

[PM-7257] android add support for web authn resident key credential property in our net mobile app 2 (#3170)

* [PM-7257] feat: add ability to override `clientDataHash`

* [PM-7257] feat: add support for clientDataHash and extensions

* PM-7257 Updated the origin to be the correct one and not the android one to be passed to the Fido2Client

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
Andreas Coroiu
2024-04-19 15:52:19 +02:00
committed by GitHub
parent 76e0f7e1a4
commit c1522e249d
12 changed files with 217 additions and 82 deletions

View File

@@ -7,6 +7,7 @@ using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2.Extensions;
using Bit.Droid;
using Org.Json;
using Activity = Android.App.Activity;
@@ -84,7 +85,7 @@ namespace Bit.App.Platforms.Android.Autofill
var excludeCredentials = new List<Core.Utilities.Fido2.PublicKeyCredentialDescriptor>();
foreach (var excludeCred in credentialCreationOptions.ExcludeCredentials)
{
excludeCredentials.Add(new Core.Utilities.Fido2.PublicKeyCredentialDescriptor(){ Id = excludeCred.GetId(), Type = excludeCred.Type, Transports = excludeCred.Transports.ToArray() });
excludeCredentials.Add(new Core.Utilities.Fido2.PublicKeyCredentialDescriptor() { Id = excludeCred.GetId(), Type = excludeCred.Type, Transports = excludeCred.Transports.ToArray() });
}
var authenticatorSelection = new Core.Utilities.Fido2.AuthenticatorSelectionCriteria()
@@ -95,7 +96,7 @@ namespace Bit.App.Platforms.Android.Autofill
};
var timeout = Convert.ToInt32(credentialCreationOptions.Timeout);
var credentialCreateParams = new Bit.Core.Utilities.Fido2.Fido2ClientCreateCredentialParams()
{
Challenge = credentialCreationOptions.GetChallenge(),
@@ -107,7 +108,7 @@ namespace Bit.App.Platforms.Android.Autofill
Attestation = credentialCreationOptions.Attestation,
AuthenticatorSelection = authenticatorSelection,
ExcludeCredentials = excludeCredentials.ToArray(),
//Extensions = // Can be improved later to add support for 'credProps'
Extensions = MapExtensionsFromJson(credentialCreationOptions),
SameOriginWithAncestors = true
};
@@ -121,7 +122,7 @@ namespace Bit.App.Platforms.Android.Autofill
activity.Finish();
return;
}
var transportsArray = new JSONArray();
if (clientCreateCredentialResult.Transports != null)
{
@@ -130,7 +131,7 @@ namespace Bit.App.Platforms.Android.Autofill
transportsArray.Put(transport);
}
}
var responseInnerAndroidJson = new JSONObject();
responseInnerAndroidJson.Put("clientDataJSON", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.ClientDataJSON));
responseInnerAndroidJson.Put("authenticatorData", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.AuthData));
@@ -144,7 +145,7 @@ namespace Bit.App.Platforms.Android.Autofill
rootAndroidJson.Put("rawId", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.CredentialId));
rootAndroidJson.Put("authenticatorAttachment", "platform");
rootAndroidJson.Put("type", "public-key");
rootAndroidJson.Put("clientExtensionResults", new JSONObject());
rootAndroidJson.Put("clientExtensionResults", MapExtensionsToJson(clientCreateCredentialResult.Extensions));
rootAndroidJson.Put("response", responseInnerAndroidJson);
var responseAndroidJson = rootAndroidJson.ToString();
@@ -158,5 +159,37 @@ namespace Bit.App.Platforms.Android.Autofill
activity.SetResult(Result.Ok, result);
activity.Finish();
}
private static Fido2CreateCredentialExtensionsParams MapExtensionsFromJson(PublicKeyCredentialCreationOptions options)
{
if (options == null || !options.Json.Has("extensions"))
{
return null;
}
var extensions = options.Json.GetJSONObject("extensions");
return new Fido2CreateCredentialExtensionsParams
{
CredProps = extensions.Has("credProps") && extensions.GetBoolean("credProps")
};
}
private static JSONObject MapExtensionsToJson(Fido2CreateCredentialExtensionsResult extensions)
{
if (extensions == null)
{
return null;
}
var extensionsJson = new JSONObject();
if (extensions.CredProps != null)
{
var credPropsJson = new JSONObject();
credPropsJson.Put("rk", extensions.CredProps.Rk);
extensionsJson.Put("credProps", credPropsJson);
}
return extensionsJson;
}
}
}

View File

@@ -68,6 +68,7 @@ namespace Bit.Droid.Autofill
var androidOrigin = AppInfoToOrigin(getRequest?.CallingAppInfo);
var packageName = getRequest?.CallingAppInfo.PackageName;
var appInfoOrigin = getRequest?.CallingAppInfo.Origin;
var userInterface = new Fido2GetAssertionUserInterface(
cipherId: cipherId,
@@ -76,17 +77,17 @@ namespace Bit.Droid.Autofill
hasVaultBeenUnlockedInThisTransaction: () => hasVaultBeenUnlockedInThisTransaction,
verifyUserCallback: (cipherId, uvPreference) => VerifyUserAsync(cipherId, uvPreference, RpId, hasVaultBeenUnlockedInThisTransaction));
var assertParams = new Fido2AuthenticatorGetAssertionParams
var clientAssertParams = new Fido2ClientAssertCredentialParams
{
Challenge = requestOptions.GetChallenge(),
RpId = RpId,
UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.ToFido2UserVerificationPreference(requestOptions.UserVerification),
Hash = credentialPublic.GetClientDataHash(),
AllowCredentialDescriptorList = new Core.Utilities.Fido2.PublicKeyCredentialDescriptor[] { new Core.Utilities.Fido2.PublicKeyCredentialDescriptor { Id = credentialId } },
Extensions = new object()
AllowCredentials = new Core.Utilities.Fido2.PublicKeyCredentialDescriptor[] { new Core.Utilities.Fido2.PublicKeyCredentialDescriptor { Id = credentialId } },
Origin = appInfoOrigin,
SameOriginWithAncestors = true,
UserVerification = requestOptions.UserVerification
};
var assertResult = await _fido2MediatorService.Value.GetAssertionAsync(assertParams, userInterface);
var assertResult = await _fido2MediatorService.Value.AssertCredentialAsync(clientAssertParams, credentialPublic.GetClientDataHash());
var response = new AuthenticatorAssertionResponse(
requestOptions,
@@ -98,7 +99,7 @@ namespace Bit.Droid.Autofill
false,
assertResult.SelectedCredential.UserHandle,
packageName,
credentialPublic.GetClientDataHash() //clientDataHash
assertResult.ClientDataHash
);
response.SetAuthenticatorData(assertResult.AuthenticatorData);
response.SetSignature(assertResult.Signature);
@@ -117,7 +118,7 @@ namespace Bit.Droid.Autofill
}
catch (NotAllowedError)
{
await MainThread.InvokeOnMainThreadAsync(async() =>
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
Finish();
@@ -126,7 +127,7 @@ namespace Bit.Droid.Autofill
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await MainThread.InvokeOnMainThreadAsync(async() =>
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
Finish();