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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user