Compare commits

...

3 Commits

Author SHA1 Message Date
Federico Maccaroni ce239499f8
Merge 2cdb5a9c74 into a333e72448 2024-05-17 01:29:04 -04:00
Vince Grassia a333e72448
Update F-Droid release process (#3249) 2024-05-17 00:15:17 -03:00
Federico Maccaroni 2cdb5a9c74
PM-7963 Fix vault timeout immediately on Android Fido2 autofill 2024-05-10 17:54:56 -03:00
7 changed files with 140 additions and 59 deletions

View File

@ -65,7 +65,6 @@ jobs:
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release
- name: Download all artifacts
if: ${{ inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
@ -152,9 +151,7 @@ jobs:
node-version: '16.x'
- name: Set up F-Droid server
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
run: pip install git+https://gitlab.com/fdroid/fdroidserver.git
- name: Set up Git credentials
env:
@ -167,9 +164,10 @@ jobs:
- name: Print environment
run: |
node --version
npm --version
git --version
echo "Node Version: $(node --version)"
echo "NPM Version: $(npm --version)"
echo "Git Version: $(git --version)"
echo "F-Droid Server Version: $(fdroid --version)"
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
@ -194,27 +192,30 @@ jobs:
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
run: |
cd $GITHUB_WORKSPACE
# Create required directories.
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
mkdir -p store/temp/fdroid
mkdir -p store/fdroid/repo
# Configure F-Droid server.
cp CNAME dist/
chmod 600 store/fdroid/config.yml store/fdroid/keystore.jks
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk ./repo/
echo "keypass: $FDROID_STORE_KEYSTORE_PASSWORD" >> store/fdroid/config.yml
echo "keystorepass: $FDROID_STORE_KEYSTORE_PASSWORD" >> store/fdroid/config.yml
echo "local_copy_dir: $TEMP_DIR" >> store/fdroid/config.yml
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk store/fdroid/repo/
# Run update and deploy.
cd store/fdroid
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE
fdroid deploy
cd ../..
# Move files for distribution.
rm -rf store/temp/fdroid/archive
mv -v store/temp/fdroid dist
cp store/fdroid/index.html store/fdroid/btn.png store/fdroid/qr.png dist/fdroid
- name: Deploy to gh-pages
if: ${{ inputs.release_type != 'Dry Run' }}

View File

@ -1,26 +1,31 @@
using Android.App;
using System.Diagnostics;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using AndroidX.Activity.Result;
using AndroidX.Activity.Result.Contract;
using AndroidX.Credentials;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.App.Droid.Utilities;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Services;
using Bit.App.Droid.Utilities;
using Bit.App.Platforms.Android.Autofill;
using AndroidX.Credentials.Exceptions;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
using Org.Json;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
NoHistory = false,
LaunchMode = LaunchMode.SingleInstance)]
[Register("com.x8bit.bitwarden.CredentialProviderSelectionActivity")]
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
{
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
@ -31,6 +36,8 @@ namespace Bit.Droid.Autofill
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
private ActivityResultLauncher _activityResultLauncher;
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
@ -100,8 +107,14 @@ namespace Bit.Droid.Autofill
cipherId,
false,
() => hasVaultBeenUnlockedInThisTransaction,
RpId
);
RpId
);
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback(result =>
{
_fido2GetAssertionUserInterface.Value.ConfirmVaultUnlocked(result.ResultCode == (int)Result.Ok);
}));
var clientAssertParams = new Fido2ClientAssertCredentialParams
{
@ -171,6 +184,19 @@ namespace Bit.Droid.Autofill
}
}
public void LaunchToUnlock()
{
if (_activityResultLauncher is null)
{
throw new InvalidOperationException("There is no activity result launcher available");
}
var intent = new Intent(this, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout);
_activityResultLauncher.Launch(intent);
}
private void FailAndFinish()
{
var result = new Intent();
@ -180,4 +206,12 @@ namespace Bit.Droid.Autofill
Finish();
}
}
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
readonly Action<ActivityResult> _callback;
public ActivityResultCallback(Action<ActivityResult> callback) => _callback = callback;
public ActivityResultCallback(TaskCompletionSource<ActivityResult> tcs) => _callback = tcs.SetResult;
public void OnActivityResult(Java.Lang.Object p0) => _callback((ActivityResult)p0);
}
}

View File

@ -1,6 +1,7 @@
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
using Bit.Droid.Autofill;
namespace Bit.App.Platforms.Android.Autofill
{
@ -10,6 +11,11 @@ namespace Bit.App.Platforms.Android.Autofill
bool userVerified,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
string rpId);
/// <summary>
/// Call this after the vault was unlocked so that Fido2 credential autofill can proceed.
/// </summary>
void ConfirmVaultUnlocked(bool unlocked);
}
public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface
@ -19,6 +25,8 @@ namespace Bit.App.Platforms.Android.Autofill
private readonly ICipherService _cipherService;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
private TaskCompletionSource<bool> _unlockVaultTcs;
public Fido2GetAssertionUserInterface(IStateService stateService,
IVaultTimeoutService vaultTimeoutService,
ICipherService cipherService,
@ -46,11 +54,38 @@ namespace Bit.App.Platforms.Android.Autofill
{
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
{
// this should never happen but just in case.
throw new InvalidOperationException("Not authed or vault locked");
if (await _stateService.GetVaultTimeoutAsync() != 0)
{
// this should never happen but just in case.
throw new InvalidOperationException("Not authed or vault locked");
}
// if vault timeout is immediate, then we need to unlock the vault
if (!await NavigateAndWaitForUnlockAsync())
{
throw new InvalidOperationException("Couldn't unlock with immediate timeout");
}
}
}
public void ConfirmVaultUnlocked(bool unlocked) => _unlockVaultTcs?.TrySetResult(unlocked);
private async Task<bool> NavigateAndWaitForUnlockAsync()
{
var credentialProviderSelectionActivity = Platform.CurrentActivity as CredentialProviderSelectionActivity;
if (credentialProviderSelectionActivity == null)
{
throw new InvalidOperationException("Can't get current activity");
}
_unlockVaultTcs?.TrySetCanceled();
_unlockVaultTcs = new TaskCompletionSource<bool>();
credentialProviderSelectionActivity.LaunchToUnlock();
return await _unlockVaultTcs.Task;
}
private async Task<bool> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction)
{
try

View File

@ -23,11 +23,14 @@ using Resource = Bit.Core.Resource;
using Application = Android.App.Application;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities;
namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
public const int DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS = 15000;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private AlertDialog _progressDialog;
@ -578,6 +581,15 @@ namespace Bit.Droid.Services
{
await ExecuteFido2GetCredentialAsync(appOptions);
}
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout
&&
ServiceContainer.TryResolve<IVaultTimeoutService>(out var vaultTimeoutService))
{
vaultTimeoutService.DelayLockAndLogoutMs = DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS;
activity.SetResult(Result.Ok);
activity.Finish();
}
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialCreate)
{
await ExecuteFido2CreateCredentialAsync();

View File

@ -5,6 +5,7 @@
public const string Fido2CredentialCreate = "fido2CredentialCreate";
public const string Fido2CredentialGet = "fido2CredentialGet";
public const string Fido2CredentialAction = "fido2CredentialAction";
public const string Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout = "fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout";
public const string CredentialProviderCipherId = "credentialProviderCipherId";
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
public const string CredentialIdIntentExtra = "credId";

View File

@ -1,20 +0,0 @@
#!/usr/bin/env python3
repo_url = "https://mobileapp.bitwarden.com/fdroid/repo"
repo_name = "Bitwarden F-Droid Repo"
repo_icon = "fdroid-icon.png"
repo_description = """
F-Droid repo for Bitwarden.
"""
archive_older = 2
archive_url = "https://does.not.exist"
archive_name = "Bitwarden Archive Repo"
archive_icon = "fdroid-icon.png"
archive_description = """
F-Droid archive repo for Bitwarden.
"""
repo_keyalias = "bitwarden-Virtual-Machine"
keystore = "keystore.jks"
keydname = "CN=bitwarden-Virtual-Machine, OU=F-Droid"

18
store/fdroid/config.yml Normal file
View File

@ -0,0 +1,18 @@
---
repo_url: https://mobileapp.bitwarden.com/fdroid/repo
repo_name: Bitwarden F-Droid Repo
repo_icon: fdroid-icon.png
repo_description: >-
F-Droid repo for Bitwarden.
archive_older: 2
archive_url: https://does.not.exist/archive
archive_name: Bitwarden Archive Repo
archive_icon: fdroid-icon.png
archive_description: >-
F-Droid archive repo for Bitwarden.
repo_keyalias: bitwarden-Virtual-Machine
keystore: keystore.jks
keydname: CN=bitwarden-Virtual-Machine, OU=F-Droid