mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
283 Commits
v2024.3.1
...
vault/pm-7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f312e8c4d2 | ||
|
|
1b3d5e5eb2 | ||
|
|
81fbb91c76 | ||
|
|
45641aadfe | ||
|
|
27380abd89 | ||
|
|
9db32ca019 | ||
|
|
1fd7dd462e | ||
|
|
f04ff7777a | ||
|
|
64775694e0 | ||
|
|
3c0007a21a | ||
|
|
ff49d041be | ||
|
|
b931263662 | ||
|
|
3a10e09469 | ||
|
|
ebc068d820 | ||
|
|
6bec0ede05 | ||
|
|
35ff235010 | ||
|
|
01bd5a7b8d | ||
|
|
3fce8c76bc | ||
|
|
3b64d7b979 | ||
|
|
f343a2cdbb | ||
|
|
39da2a82c6 | ||
|
|
970d3c2621 | ||
|
|
faa515b415 | ||
|
|
74085689d3 | ||
|
|
9a9fb85ad8 | ||
|
|
e7f9d64edb | ||
|
|
144fc7c727 | ||
|
|
53aedea93a | ||
|
|
459d20c019 | ||
|
|
dd997aaa47 | ||
|
|
a8529fa4b7 | ||
|
|
d1e82c9f1d | ||
|
|
46c1d72b3c | ||
|
|
9bc2901255 | ||
|
|
01fe329f3b | ||
|
|
67f7b3156e | ||
|
|
39187732c0 | ||
|
|
4292542155 | ||
|
|
e41abf5003 | ||
|
|
4c2932f4d0 | ||
|
|
a10481603d | ||
|
|
b8ff0e0244 | ||
|
|
85755902e1 | ||
|
|
38d3a7ed41 | ||
|
|
18fae7ddd8 | ||
|
|
b83473ce3a | ||
|
|
e34a58e875 | ||
|
|
9f92fdeb29 | ||
|
|
c31444dc8b | ||
|
|
16e1b60a4d | ||
|
|
71de3bedf4 | ||
|
|
d339514d9a | ||
|
|
75ec96f282 | ||
|
|
8f8a5795d3 | ||
|
|
4631a9e62c | ||
|
|
51ee6a84b5 | ||
|
|
8d5006c0bd | ||
|
|
37208571fe | ||
|
|
759627b3c7 | ||
|
|
08fac4752f | ||
|
|
9307e7e0d8 | ||
|
|
b1a0801f9b | ||
|
|
04cc53b934 | ||
|
|
c138658a31 | ||
|
|
f1854f2c04 | ||
|
|
e4056d9ee6 | ||
|
|
eb95a54db2 | ||
|
|
7ddea4c70b | ||
|
|
3804e86995 | ||
|
|
b23bed182f | ||
|
|
f8e421871b | ||
|
|
d0103496b9 | ||
|
|
cd8952221e | ||
|
|
155c7539bd | ||
|
|
5f43681fb1 | ||
|
|
d2965e6e10 | ||
|
|
ec1ade7761 | ||
|
|
f35bef0d7b | ||
|
|
138d37cf5e | ||
|
|
fc2fed079f | ||
|
|
9c441a98f4 | ||
|
|
1491872b62 | ||
|
|
c74636ffa5 | ||
|
|
05677f93c5 | ||
|
|
0aef241df6 | ||
|
|
e0b58461b5 | ||
|
|
cd33c7f608 | ||
|
|
9d29af36e5 | ||
|
|
4472d7f9a8 | ||
|
|
999579915c | ||
|
|
63904fd303 | ||
|
|
2cb6872e4e | ||
|
|
f539bf051d | ||
|
|
14f845d623 | ||
|
|
133a80acef | ||
|
|
b43790de9a | ||
|
|
0bdd63df06 | ||
|
|
c6544b49e9 | ||
|
|
8e1a8b5f0e | ||
|
|
4717f5e230 | ||
|
|
01ee1ff845 | ||
|
|
75b4655f38 | ||
|
|
9b2f596d15 | ||
|
|
55fb71744d | ||
|
|
ee252be634 | ||
|
|
66f0471f2e | ||
|
|
6b9eeba88d | ||
|
|
0a1fbfafb5 | ||
|
|
0a5d772886 | ||
|
|
70c8a264d2 | ||
|
|
b5fbb2cade | ||
|
|
9027755b71 | ||
|
|
6d625f285b | ||
|
|
822ad7564e | ||
|
|
1949a450fd | ||
|
|
27fa79e0bd | ||
|
|
1e29eacc61 | ||
|
|
b81d26d589 | ||
|
|
cd107b6161 | ||
|
|
7ac3646fb0 | ||
|
|
d1e4e8645a | ||
|
|
36a648e53e | ||
|
|
6c04ac67b1 | ||
|
|
dfb7a0621f | ||
|
|
1eb9e5f8ea | ||
|
|
b149e7549c | ||
|
|
e3877cc589 | ||
|
|
275ae76761 | ||
|
|
a1e4f0aaa2 | ||
|
|
adaef0d15b | ||
|
|
fa4a2247e3 | ||
|
|
5d2fc4530f | ||
|
|
9b64af3423 | ||
|
|
b6ff6e34f6 | ||
|
|
6d4c706026 | ||
|
|
14fd026ea0 | ||
|
|
a4392a8730 | ||
|
|
1b885ea438 | ||
|
|
fa022a1a4f | ||
|
|
6011b63958 | ||
|
|
7d79b98bf2 | ||
|
|
d4e75e9de8 | ||
|
|
c3370b58ec | ||
|
|
3de13325c9 | ||
|
|
c253c110c1 | ||
|
|
bf35d1f2dc | ||
|
|
05b6aa90b6 | ||
|
|
e39898bba6 | ||
|
|
da0866cc85 | ||
|
|
b3140381ab | ||
|
|
c01a8f8d93 | ||
|
|
8484b4af30 | ||
|
|
6b9faed45f | ||
|
|
770a1c5dfe | ||
|
|
3c87d4db1c | ||
|
|
8b3c6ab35f | ||
|
|
90912977c4 | ||
|
|
740b368b8c | ||
|
|
3a40a4cda8 | ||
|
|
9bcd2e51f7 | ||
|
|
741214a1cc | ||
|
|
aad87dfdce | ||
|
|
8fc1e9a3b9 | ||
|
|
2a8e15146e | ||
|
|
8559d5908e | ||
|
|
f60c4d94fe | ||
|
|
05858bea48 | ||
|
|
5cbef47fd4 | ||
|
|
4bf695d18c | ||
|
|
c24e0dfa28 | ||
|
|
9ccd0834ff | ||
|
|
a806f17d3b | ||
|
|
436a162df2 | ||
|
|
f2c298607e | ||
|
|
b5dbb9ae5e | ||
|
|
7a5f7c0274 | ||
|
|
17acb57732 | ||
|
|
5803635f44 | ||
|
|
19c393842f | ||
|
|
15a306490d | ||
|
|
a4a3d31c19 | ||
|
|
922dc683af | ||
|
|
bae1b3e891 | ||
|
|
a5888827c9 | ||
|
|
0348940a12 | ||
|
|
4c2998337d | ||
|
|
7ea86380f4 | ||
|
|
406f4425c8 | ||
|
|
95ca911444 | ||
|
|
fa62510e09 | ||
|
|
65dc73495d | ||
|
|
02a2e41118 | ||
|
|
bd6f8295e7 | ||
|
|
0a0cb7093b | ||
|
|
465e5eff76 | ||
|
|
5b756aaf7a | ||
|
|
d168a7b750 | ||
|
|
7f4bbafe3c | ||
|
|
a5804df6a3 | ||
|
|
bfa2a51608 | ||
|
|
32be08daae | ||
|
|
0a628cc8a8 | ||
|
|
80c424ed03 | ||
|
|
99fb5463cf | ||
|
|
c5d941e1df | ||
|
|
3edfef6169 | ||
|
|
1c8742511a | ||
|
|
8e424d6c05 | ||
|
|
390c303b90 | ||
|
|
443f7282b8 | ||
|
|
50109ee70b | ||
|
|
9ffdfd51cc | ||
|
|
04e409f3c6 | ||
|
|
6c143bad57 | ||
|
|
286e18059a | ||
|
|
553bf9ed0a | ||
|
|
ddb27b52d3 | ||
|
|
6c504aa710 | ||
|
|
62254aef8d | ||
|
|
06a0195a6d | ||
|
|
df2b0b21d5 | ||
|
|
e6b1bab860 | ||
|
|
ce41eb0578 | ||
|
|
1a0b52d644 | ||
|
|
16ada4993c | ||
|
|
3795f3aa17 | ||
|
|
eceb506c77 | ||
|
|
2c7870d660 | ||
|
|
f02b3415a3 | ||
|
|
beda4e9ff8 | ||
|
|
df4d89cd52 | ||
|
|
5f12bb9747 | ||
|
|
5712639492 | ||
|
|
e0a3c301fb | ||
|
|
27306fe353 | ||
|
|
a31f15559f | ||
|
|
0e75f3f5c8 | ||
|
|
363da063fa | ||
|
|
974a571455 | ||
|
|
e0c721098c | ||
|
|
a86f6e3034 | ||
|
|
fe17288b99 | ||
|
|
7324da9d47 | ||
|
|
69aa6fc044 | ||
|
|
e840dc2e30 | ||
|
|
eb25ee5d1b | ||
|
|
840f24dbe5 | ||
|
|
c6309173ba | ||
|
|
946c465f0c | ||
|
|
e90409d842 | ||
|
|
484b5a5160 | ||
|
|
2688209752 | ||
|
|
53e0e55915 | ||
|
|
ca57948d9f | ||
|
|
aaf082faba | ||
|
|
e7aeb08cae | ||
|
|
f177968958 | ||
|
|
f1d59210f9 | ||
|
|
62213c0aaf | ||
|
|
8be8abb8fe | ||
|
|
174acbc558 | ||
|
|
4bcc7c0d71 | ||
|
|
14b2960f30 | ||
|
|
455c3a257c | ||
|
|
8c623a2067 | ||
|
|
3cdf1c2f0e | ||
|
|
ce9503fa0c | ||
|
|
2e4da1b87d | ||
|
|
d63a219272 | ||
|
|
c92cd90a97 | ||
|
|
1dcd3a3daa | ||
|
|
efb8763d3c | ||
|
|
90649d1c8b | ||
|
|
828055791f | ||
|
|
87eebda55f | ||
|
|
7542d1ae1c | ||
|
|
990de4ea4e | ||
|
|
0dbc23f734 | ||
|
|
9f6c8601d3 | ||
|
|
8b7f9b9fb3 | ||
|
|
d17789d5ee | ||
|
|
b8f0747dd4 | ||
|
|
8ef9443b1e |
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -146,10 +146,7 @@ jobs:
|
||||
- name: Increment version
|
||||
run: |
|
||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
||||
|
||||
echo "########################################"
|
||||
echo "##### Setting Version Code $BUILD_NUMBER"
|
||||
echo "########################################"
|
||||
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
||||
./${{ env.android_folder_path_bash }}/AndroidManifest.xml
|
||||
@@ -358,7 +355,7 @@ jobs:
|
||||
- name: Increment version
|
||||
run: |
|
||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
||||
echo "##### Setting Version Code $BUILD_NUMBER"
|
||||
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
||||
./${{ env.android_manifest_path }}
|
||||
@@ -366,15 +363,14 @@ jobs:
|
||||
|
||||
- name: Clean for F-Droid
|
||||
run: |
|
||||
$appPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
|
||||
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
|
||||
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
|
||||
|
||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
|
||||
|
||||
Write-Output "##### Back up project files"
|
||||
|
||||
Copy-Item $androidManifest $($androidManifest + ".original");
|
||||
Copy-Item $appPath $($appPath + ".original");
|
||||
Copy-Item $directoryBuildProps $($directoryBuildProps + ".original");
|
||||
|
||||
Write-Output "##### Cleanup Android Manifest"
|
||||
|
||||
@@ -386,6 +382,10 @@ jobs:
|
||||
|
||||
$xml.Save($androidManifest);
|
||||
|
||||
Write-Output "##### Enabling FDROID constant"
|
||||
|
||||
(Get-Content $directoryBuildProps).Replace('<!-- <CustomConstants>FDROID</CustomConstants> -->', '<CustomConstants>FDROID</CustomConstants>') | Set-Content $directoryBuildProps
|
||||
|
||||
- name: Restore packages
|
||||
run: dotnet restore
|
||||
|
||||
@@ -399,17 +399,16 @@ jobs:
|
||||
Write-Output "##### Sign FDroid"
|
||||
|
||||
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks"
|
||||
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
|
||||
/p:AndroidKeyStore=true `
|
||||
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
|
||||
/p:AndroidSigningKeyAlias=bitwarden `
|
||||
/p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" `
|
||||
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" `
|
||||
/p:CustomConstants="FDROID" --no-restore
|
||||
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" ` --no-restore
|
||||
|
||||
Write-Output "##### Copy FDroid apk to project root"
|
||||
|
||||
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.apk";
|
||||
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\$($packageName)-Signed.apk";
|
||||
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden-fdroid.apk";
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
@@ -521,9 +520,7 @@ jobs:
|
||||
- name: Increment version
|
||||
run: |
|
||||
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
|
||||
|
||||
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
|
||||
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
|
||||
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
||||
|
||||
53
.github/workflows/cleanup-rc-branch.yml
vendored
Normal file
53
.github/workflows/cleanup-rc-branch.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: Cleanup RC Branch
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v**
|
||||
|
||||
jobs:
|
||||
delete-rc:
|
||||
name: Delete RC Branch
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve bot secrets
|
||||
id: retrieve-bot-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: bitwarden-ci
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
|
||||
- name: Check if a RC branch exists
|
||||
id: branch-check
|
||||
run: |
|
||||
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
|
||||
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||
|
||||
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
|
||||
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
|
||||
elif [[ "${rc_branch_check}" -gt 0 ]]; then
|
||||
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "name=rc" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Delete RC branch
|
||||
env:
|
||||
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
|
||||
run: |
|
||||
if ! [[ -z "$BRANCH_NAME" ]]; then
|
||||
git push --quiet origin --delete $BRANCH_NAME
|
||||
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
24
.github/workflows/version-auto-bump.yml
vendored
24
.github/workflows/version-auto-bump.yml
vendored
@@ -11,24 +11,6 @@ jobs:
|
||||
name: Bump Mobile Version
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Calculate bumped version
|
||||
id: version
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.ref }}
|
||||
run: |
|
||||
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
|
||||
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
|
||||
echo "Current Major: $CURR_MAJOR"
|
||||
echo "Current Patch: $CURR_PATCH"
|
||||
|
||||
NEW_PATCH=$((CURR_PATCH+1))
|
||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
||||
echo "New Version: $NEW_VER"
|
||||
echo "new_version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
@@ -41,9 +23,9 @@ jobs:
|
||||
keyvault: bitwarden-ci
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: "Bump version to ${{ steps.version.outputs.new_version }}"
|
||||
- name: Trigger Version Bump workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
run: |
|
||||
echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}"}' | \
|
||||
gh workflow run version-bump.yml --json --repo bitwarden/mobile
|
||||
echo '{"cut_rc_branch": "false"}' | \
|
||||
gh workflow run version-bump.yml --json --repo bitwarden/mobile
|
||||
|
||||
168
.github/workflows/version-bump.yml
vendored
168
.github/workflows/version-bump.yml
vendored
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: Version Bump
|
||||
run-name: Version Bump - v${{ inputs.version_number }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: "New version (example: '2024.1.0')"
|
||||
required: true
|
||||
version_number_override:
|
||||
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
|
||||
required: false
|
||||
type: string
|
||||
cut_rc_branch:
|
||||
description: "Cut RC branch?"
|
||||
default: true
|
||||
@@ -15,22 +15,16 @@ on:
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
name: "Bump Version to v${{ inputs.version_number }}"
|
||||
name: Bump Version
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
version: ${{ steps.set-final-version-output.outputs.version }}
|
||||
steps:
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
- name: Validate version input
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-check@main
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-gpg-private-key,
|
||||
github-gpg-private-key-passphrase,
|
||||
github-pat-bitwarden-devops-bot-repo-scope"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
@@ -47,6 +41,20 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-gpg-private-key,
|
||||
github-gpg-private-key-passphrase,
|
||||
github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
|
||||
with:
|
||||
@@ -55,25 +63,38 @@ jobs:
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||
git config --local user.name "bitwarden-devops-bot"
|
||||
|
||||
- name: Create Version Branch
|
||||
id: create-branch
|
||||
run: |
|
||||
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
|
||||
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
|
||||
git switch -c $NAME
|
||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install xmllint
|
||||
run: sudo apt install -y libxml2-utils
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-utils
|
||||
|
||||
- name: Verify input version
|
||||
env:
|
||||
NEW_VERSION: ${{ inputs.version_number }}
|
||||
- name: Get current version
|
||||
id: current-version
|
||||
run: |
|
||||
CURRENT_VERSION=$(xmllint --xpath '
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Verify input version
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
env:
|
||||
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
|
||||
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||
run: |
|
||||
# Error if version has not changed.
|
||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||
echo "Version has not changed."
|
||||
@@ -89,40 +110,93 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Bump Version - Android XML
|
||||
- name: Calculate next release version
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
id: calculate-next-version
|
||||
uses: bitwarden/gh-actions/version-next@main
|
||||
with:
|
||||
version: ${{ steps.current-version.outputs.version }}
|
||||
|
||||
- name: Bump Version - Android XML - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
id: bump-version-override
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - iOS.Autofill
|
||||
- name: Bump Version - Android XML - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
id: bump-version-automatic
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Bump Version - iOS.Autofill - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.Autofill/Info.plist"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - iOS.Extension
|
||||
- name: Bump Version - iOS.Autofill - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "src/iOS.Autofill/Info.plist"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Bump Version - iOS.Extension - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.Extension/Info.plist"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - iOS.ShareExtension
|
||||
- name: Bump Version - iOS.Extension - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "src/iOS.Extension/Info.plist"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Bump Version - iOS.ShareExtension - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.ShareExtension/Info.plist"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - iOS
|
||||
- name: Bump Version - iOS.ShareExtension - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||
file_path: "src/iOS.ShareExtension/Info.plist"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Setup git
|
||||
- name: Bump Version - iOS - Version Override
|
||||
if: ${{ inputs.version_number_override != '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Bump Version - iOS - Automatic Calculation
|
||||
if: ${{ inputs.version_number_override == '' }}
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||
|
||||
- name: Set Job output
|
||||
id: set-final-version-output
|
||||
run: |
|
||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||
git config --local user.name "bitwarden-devops-bot"
|
||||
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then
|
||||
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
|
||||
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check if version changed
|
||||
id: version-changed
|
||||
@@ -136,7 +210,7 @@ jobs:
|
||||
|
||||
- name: Commit files
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
|
||||
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a
|
||||
|
||||
- name: Push changes
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
@@ -150,7 +224,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||
TITLE: "Bump version to ${{ inputs.version_number }}"
|
||||
TITLE: "Bump version to ${{ steps.set-final-version-output.outputs.version }}"
|
||||
run: |
|
||||
PR_URL=$(gh pr create --title "$TITLE" \
|
||||
--base "main" \
|
||||
@@ -166,16 +240,18 @@ jobs:
|
||||
- [X] Other
|
||||
|
||||
## Objective
|
||||
Automated version bump to ${{ inputs.version_number }}")
|
||||
Automated version bump to ${{ steps.set-final-version-output.outputs.version }}")
|
||||
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Approve PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
run: gh pr review $PR_NUMBER --approve
|
||||
|
||||
- name: Merge PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
@@ -183,28 +259,30 @@ jobs:
|
||||
|
||||
cut_rc:
|
||||
name: Cut RC branch
|
||||
needs: bump_version
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
needs: bump_version
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
|
||||
- name: Install xmllint
|
||||
run: sudo apt install -y libxml2-utils
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-utils
|
||||
|
||||
- name: Verify version has been updated
|
||||
env:
|
||||
NEW_VERSION: ${{ inputs.version_number }}
|
||||
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
while : ; do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(xmllint --xpath '
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -148,6 +148,7 @@ publish/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
!**/Xamarin.AndroidX.Credentials.1.0.0.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
|
||||
@@ -9,5 +9,8 @@
|
||||
|
||||
<!-- Uncomment this when Unit Testing-->
|
||||
<!-- <CustomConstants>UT</CustomConstants> -->
|
||||
|
||||
<!-- Uncomment this when building FDROID-->
|
||||
<!-- <CustomConstants>FDROID</CustomConstants> -->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Xamarin.AndroidX.Credentials</name>
|
||||
</assembly>
|
||||
<members>
|
||||
</members>
|
||||
</doc>
|
||||
Binary file not shown.
@@ -2,5 +2,6 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
|
||||
<add key="Local AndroidX Credentials" value="lib/android/Xamarin.AndroidX.Credentials" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -121,6 +121,7 @@
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.2.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.3.3" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
@@ -43,6 +43,9 @@
|
||||
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
|
||||
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
|
||||
@@ -347,7 +347,7 @@ namespace Bit.Droid.Autofill
|
||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
||||
// "my vault" presentation) so we're including an empty one here
|
||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, false));
|
||||
}
|
||||
var slice = CreateInlinePresentationSlice(
|
||||
inlinePresentationSpec,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class CredentialProviderConstants
|
||||
{
|
||||
public const string CredentialProviderCipherId = "credentialProviderCipherId";
|
||||
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
|
||||
public const string CredentialIdIntentExtra = "credId";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using AndroidX.Credentials.Provider;
|
||||
using AndroidX.Credentials.WebAuthn;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.App.Droid.Utilities;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Activity(
|
||||
NoHistory = true,
|
||||
LaunchMode = LaunchMode.SingleTop)]
|
||||
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
||||
{
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
Intent?.Validate();
|
||||
base.OnCreate(bundle);
|
||||
|
||||
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
|
||||
if (string.IsNullOrEmpty(cipherId))
|
||||
{
|
||||
SetResult(Result.Canceled);
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
GetCipherAndPerformPasskeyAuthAsync(cipherId).FireAndForget();
|
||||
}
|
||||
|
||||
private async Task GetCipherAndPerformPasskeyAuthAsync(string cipherId)
|
||||
{
|
||||
// TODO this is a work in progress
|
||||
// https://developer.android.com/training/sign-in/credential-provider#passkeys-implement
|
||||
|
||||
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
|
||||
// var publicKeyRequest = getRequest?.CredentialOptions as PublicKeyCredentialRequestOptions;
|
||||
|
||||
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
|
||||
var credIdEnc = requestInfo?.GetString(CredentialProviderConstants.CredentialIdIntentExtra);
|
||||
|
||||
var cipherService = ServiceContainer.Resolve<ICipherService>();
|
||||
var cipher = await cipherService.GetAsync(cipherId);
|
||||
var decCipher = await cipher.DecryptAsync();
|
||||
|
||||
var passkey = decCipher.Login.Fido2Credentials.Find(f => f.CredentialId == credIdEnc);
|
||||
|
||||
var credId = Convert.FromBase64String(credIdEnc);
|
||||
// var privateKey = Convert.FromBase64String(passkey.PrivateKey);
|
||||
// var uid = Convert.FromBase64String(passkey.uid);
|
||||
|
||||
var origin = getRequest?.CallingAppInfo.Origin;
|
||||
var packageName = getRequest?.CallingAppInfo.PackageName;
|
||||
|
||||
// --- continue WIP here (save TOTP copy as last step) ---
|
||||
|
||||
// Copy TOTP if needed
|
||||
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||
autofillHandler.Autofill(decCipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/App/Platforms/Android/Autofill/CredentialProviderService.cs
Normal file
147
src/App/Platforms/Android/Autofill/CredentialProviderService.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using AndroidX.Credentials.Provider;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using AndroidX.Credentials.Exceptions;
|
||||
using AndroidX.Credentials.WebAuthn;
|
||||
using Bit.Core.Models.View;
|
||||
using Resource = Microsoft.Maui.Resource;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Service(Permission = Manifest.Permission.BindCredentialProviderService, Label = "Bitwarden", Exported = true)]
|
||||
[IntentFilter(new string[] { "android.service.credentials.CredentialProviderService" })]
|
||||
[MetaData("android.credentials.provider", Resource = "@xml/provider")]
|
||||
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
|
||||
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
|
||||
{
|
||||
private const string GetPasskeyIntentAction = "PACKAGE_NAME.GET_PASSKEY";
|
||||
private const int UniqueRequestCode = 94556023;
|
||||
|
||||
private ICipherService _cipherService;
|
||||
private IUserVerificationService _userVerificationService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
|
||||
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
|
||||
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
|
||||
|
||||
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
|
||||
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
_vaultTimeoutService ??= ServiceContainer.Resolve<IVaultTimeoutService>();
|
||||
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
var locked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!locked)
|
||||
{
|
||||
var response = await ProcessGetCredentialsRequestAsync(request);
|
||||
callback.OnResult(response);
|
||||
}
|
||||
// TODO handle auth/unlock account flow
|
||||
}
|
||||
catch (GetCredentialException e)
|
||||
{
|
||||
_logger.Value.Exception(e);
|
||||
callback.OnError(e.ErrorMessage ?? "Error getting credentials");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Value.Exception(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
|
||||
BeginGetCredentialRequest request)
|
||||
{
|
||||
IList<CredentialEntry> credentialEntries = null;
|
||||
|
||||
foreach (var option in request.BeginGetCredentialOptions)
|
||||
{
|
||||
var credentialOption = option as BeginGetPublicKeyCredentialOption;
|
||||
if (credentialOption != null)
|
||||
{
|
||||
credentialEntries ??= new List<CredentialEntry>();
|
||||
((List<CredentialEntry>)credentialEntries).AddRange(
|
||||
await PopulatePasskeyDataAsync(request.CallingAppInfo, credentialOption));
|
||||
}
|
||||
}
|
||||
|
||||
if (credentialEntries == null)
|
||||
{
|
||||
return new BeginGetCredentialResponse();
|
||||
}
|
||||
|
||||
return new BeginGetCredentialResponse.Builder()
|
||||
.SetCredentialEntries(credentialEntries)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
|
||||
BeginGetPublicKeyCredentialOption option)
|
||||
{
|
||||
var packageName = callingAppInfo.PackageName;
|
||||
var origin = callingAppInfo.Origin;
|
||||
var signingInfo = callingAppInfo.SigningInfo;
|
||||
|
||||
var request = new PublicKeyCredentialRequestOptions(option.RequestJson);
|
||||
|
||||
var passkeyEntries = new List<CredentialEntry>();
|
||||
|
||||
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
|
||||
var ciphers = await _cipherService.GetAllDecryptedForUrlAsync(origin);
|
||||
if (ciphers == null)
|
||||
{
|
||||
return passkeyEntries;
|
||||
}
|
||||
|
||||
var passkeyCiphers = ciphers.Where(cipher => cipher.HasFido2Credential).ToList();
|
||||
if (!passkeyCiphers.Any())
|
||||
{
|
||||
return passkeyEntries;
|
||||
}
|
||||
|
||||
foreach (var cipher in passkeyCiphers)
|
||||
{
|
||||
var passkeyEntry = GetPasskey(cipher, option);
|
||||
passkeyEntries.Add(passkeyEntry);
|
||||
}
|
||||
|
||||
return passkeyEntries;
|
||||
}
|
||||
|
||||
private PublicKeyCredentialEntry GetPasskey(CipherView cipher, BeginGetPublicKeyCredentialOption option)
|
||||
{
|
||||
var credDataBundle = new Bundle();
|
||||
credDataBundle.PutString(CredentialProviderConstants.CredentialIdIntentExtra,
|
||||
cipher.Login.MainFido2Credential.CredentialId);
|
||||
|
||||
var intent = new Intent(ApplicationContext, typeof(CredentialProviderSelectionActivity))
|
||||
.SetAction(GetPasskeyIntentAction).SetPackage(Constants.PACKAGE_NAME);
|
||||
intent.PutExtra(CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
|
||||
intent.PutExtra(CredentialProviderConstants.CredentialProviderCipherId, cipher.Id);
|
||||
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueRequestCode, intent,
|
||||
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
|
||||
|
||||
return new PublicKeyCredentialEntry.Builder(
|
||||
ApplicationContext,
|
||||
cipher.Login.Username ?? "No username",
|
||||
pendingIntent,
|
||||
option)
|
||||
.SetDisplayName(cipher.Name)
|
||||
.SetIcon(Icon.CreateWithResource(ApplicationContext, Resource.Drawable.icon))
|
||||
.Build();
|
||||
}
|
||||
|
||||
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
|
||||
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,12 @@ namespace Bit.Droid
|
||||
ServiceContainer.Resolve<IWatchDeviceService>(),
|
||||
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
|
||||
var userPinService = new UserPinService(
|
||||
ServiceContainer.Resolve<IStateService>(),
|
||||
ServiceContainer.Resolve<ICryptoService>(),
|
||||
ServiceContainer.Resolve<IVaultTimeoutService>());
|
||||
ServiceContainer.Register<IUserPinService>(userPinService);
|
||||
}
|
||||
#if !FDROID
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
@@ -160,7 +166,6 @@ namespace Bit.Droid
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
|
||||
var biometricService = new BiometricService(stateService, cryptoService);
|
||||
var userPinService = new UserPinService(stateService, cryptoService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
||||
|
||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||
@@ -184,7 +189,6 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||
ServiceContainer.Register<IUserPinService>(userPinService);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
|
||||
6
src/App/Platforms/Android/Resources/xml/provider.xml
Normal file
6
src/App/Platforms/Android/Resources/xml/provider.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<capabilities>
|
||||
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
|
||||
</capabilities>
|
||||
</credential-provider>
|
||||
@@ -37,6 +37,23 @@ namespace Bit.Droid.Services
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public bool CredentialProviderServiceEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
// TODO - find a way to programmatically check if the credential provider service is enabled
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutofillServiceEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
@@ -163,7 +180,14 @@ namespace Bit.Droid.Services
|
||||
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
||||
}
|
||||
|
||||
|
||||
public void DisableCredentialProviderService()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO - find a way to programmatically disable the provider service, or take the user to the settings page where they can do it
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void DisableAutofillService()
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using Android.Text.Method;
|
||||
using Android.Views;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
using AndroidX.Credentials;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.App.Utilities;
|
||||
@@ -72,17 +73,28 @@ namespace Bit.Droid.Services
|
||||
|
||||
public bool LaunchApp(string appName)
|
||||
{
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
try
|
||||
{
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
{
|
||||
// API 33 required to avoid using wildcard app visibility or dangerous permissions
|
||||
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
|
||||
return false;
|
||||
}
|
||||
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||
appName = appName.Replace("androidapp://", string.Empty);
|
||||
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
||||
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
||||
return launchIntentSender != null;
|
||||
}
|
||||
catch (IntentSender.SendIntentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Android.Util.AndroidException)
|
||||
{
|
||||
// API 33 required to avoid using wildcard app visibility or dangerous permissions
|
||||
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
|
||||
return false;
|
||||
}
|
||||
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||
appName = appName.Replace("androidapp://", string.Empty);
|
||||
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
||||
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
||||
return launchIntentSender != null;
|
||||
}
|
||||
|
||||
public async Task ShowLoadingAsync(string text)
|
||||
@@ -490,6 +502,27 @@ namespace Bit.Droid.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenCredentialProviderSettings()
|
||||
{
|
||||
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||
try
|
||||
{
|
||||
var pendingIntent = CredentialManager.Create(activity).CreateSettingsPendingIntent();
|
||||
pendingIntent.Send();
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
var alertBuilder = new AlertDialog.Builder(activity);
|
||||
alertBuilder.SetMessage(AppResources.BitwardenCredentialProviderGoToSettings);
|
||||
alertBuilder.SetCancelable(true);
|
||||
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
|
||||
{
|
||||
(sender as AlertDialog)?.Cancel();
|
||||
});
|
||||
alertBuilder.Create().Show();
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAccessibilitySettings()
|
||||
{
|
||||
try
|
||||
@@ -548,6 +581,8 @@ namespace Bit.Droid.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsCredentialProviderService() => Build.VERSION.SdkInt >= BuildVersionCodes.UpsideDownCake;
|
||||
|
||||
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
|
||||
|
||||
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Accessibility;
|
||||
using Java.Lang;
|
||||
using Bit.App.Droid.Utilities;
|
||||
|
||||
namespace Bit.Droid.Tile
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace Bit.Droid.Tile
|
||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
intent.PutExtra("autofillTileClicked", true);
|
||||
StartActivityAndCollapse(intent);
|
||||
this.StartActivityAndCollapseWithIntent(intent, isMutable: true);
|
||||
}
|
||||
|
||||
private void ShowConfigErrorDialog()
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Service.QuickSettings;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Java.Lang;
|
||||
|
||||
namespace Bit.Droid.Tile
|
||||
@@ -62,7 +55,7 @@ namespace Bit.Droid.Tile
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
intent.PutExtra("generatorTile", true);
|
||||
StartActivityAndCollapse(intent);
|
||||
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Service.QuickSettings;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Java.Lang;
|
||||
|
||||
namespace Bit.Droid.Tile
|
||||
@@ -63,7 +56,7 @@ namespace Bit.Droid.Tile
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
intent.PutExtra("myVaultTile", true);
|
||||
StartActivityAndCollapse(intent);
|
||||
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Service.QuickSettings;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Droid.Utilities
|
||||
@@ -64,5 +65,26 @@ namespace Bit.App.Droid.Utilities
|
||||
|
||||
return pendingIntentFlags;
|
||||
}
|
||||
|
||||
public static void StartActivityAndCollapseWithIntent(this TileService service, Intent intent, bool isMutable)
|
||||
{
|
||||
//For Android 14+ We need to use PendingIntent instead of Intent directly. Older versions still need to use Intent.
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
|
||||
{
|
||||
service.StartActivityAndCollapse(intent);
|
||||
return;
|
||||
}
|
||||
var pendingIntent = PendingIntent.GetActivity(
|
||||
service.ApplicationContext,
|
||||
0,
|
||||
intent,
|
||||
AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, isMutable)
|
||||
);
|
||||
if (pendingIntent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
service.StartActivityAndCollapse(pendingIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Bit.iOS
|
||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||
if (needsAutofillReplacement.GetValueOrDefault())
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
}
|
||||
else if (message.Command == "showAppExtension")
|
||||
@@ -102,7 +102,7 @@ namespace Bit.iOS
|
||||
var success = value as bool?;
|
||||
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,22 +114,21 @@ namespace Bit.iOS
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ASHelpers.IdentitiesCanIncremental())
|
||||
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
|
||||
{
|
||||
var cipherId = message.Data as string;
|
||||
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
|
||||
{
|
||||
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
|
||||
var identity = await ASHelpers.GetCipherPasswordIdentityAsync(cipherId);
|
||||
if (identity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
|
||||
new ASPasswordCredentialIdentity[] { identity });
|
||||
await ASCredentialIdentityStoreExtensions.SaveCredentialIdentitiesAsync(identity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
||||
{
|
||||
@@ -138,28 +137,27 @@ namespace Bit.iOS
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ASHelpers.IdentitiesCanIncremental())
|
||||
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
|
||||
{
|
||||
var identity = ASHelpers.ToCredentialIdentity(
|
||||
var identity = ASHelpers.ToPasswordCredentialIdentity(
|
||||
message.Data as Bit.Core.Models.View.CipherView);
|
||||
if (identity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
|
||||
new ASPasswordCredentialIdentity[] { identity });
|
||||
await ASCredentialIdentityStoreExtensions.RemoveCredentialIdentitiesAsync(identity);
|
||||
return;
|
||||
}
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
|
||||
}
|
||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
|
||||
{
|
||||
@@ -168,12 +166,12 @@ namespace Bit.iOS
|
||||
{
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2024.2.2</string>
|
||||
<string>2024.3.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleIconName</key>
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IAutofillHandler
|
||||
{
|
||||
bool CredentialProviderServiceEnabled();
|
||||
bool AutofillServicesEnabled();
|
||||
bool SupportsAutofillService();
|
||||
void Autofill(CipherView cipher);
|
||||
@@ -11,6 +12,7 @@ namespace Bit.Core.Abstractions
|
||||
bool AutofillAccessibilityServiceRunning();
|
||||
bool AutofillAccessibilityOverlayPermitted();
|
||||
bool AutofillServiceEnabled();
|
||||
void DisableCredentialProviderService();
|
||||
void DisableAutofillService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.View;
|
||||
@@ -37,5 +34,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
|
||||
Task SoftDeleteWithServerAsync(string id);
|
||||
Task RestoreWithServerAsync(string id);
|
||||
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
|
||||
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public enum AwaiterPrecondition
|
||||
{
|
||||
EnvironmentUrlsInited,
|
||||
AndroidWindowCreated
|
||||
AndroidWindowCreated,
|
||||
AutofillIOSExtensionViewDidAppear
|
||||
}
|
||||
|
||||
public interface IConditionedAwaiterManager
|
||||
@@ -14,5 +12,6 @@ namespace Bit.Core.Abstractions
|
||||
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
|
||||
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
|
||||
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
|
||||
void Recreate(AwaiterPrecondition awaiterPrecondition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Bit.App.Abstractions
|
||||
bool SupportsNfc();
|
||||
bool SupportsCamera();
|
||||
bool SupportsFido2();
|
||||
bool SupportsCredentialProviderService();
|
||||
bool SupportsAutofillServices();
|
||||
bool SupportsInlineAutofill();
|
||||
bool SupportsDrawOver();
|
||||
@@ -36,6 +37,7 @@ namespace Bit.App.Abstractions
|
||||
void RateApp();
|
||||
void OpenAccessibilitySettings();
|
||||
void OpenAccessibilityOverlayPermissionSettings();
|
||||
void OpenCredentialProviderSettings();
|
||||
void OpenAutofillSettings();
|
||||
long GetActiveTime();
|
||||
void CloseMainApp();
|
||||
|
||||
12
src/Core/Abstractions/IFido2AuthenticatorService.cs
Normal file
12
src/Core/Abstractions/IFido2AuthenticatorService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IFido2AuthenticatorService
|
||||
{
|
||||
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
|
||||
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
|
||||
// TODO: Should this return a List? Or maybe IEnumerable?
|
||||
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
|
||||
}
|
||||
}
|
||||
35
src/Core/Abstractions/IFido2ClientService.cs
Normal file
35
src/Core/Abstractions/IFido2ClientService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents an abstraction of the WebAuthn Client as described by W3C:
|
||||
/// https://www.w3.org/TR/webauthn-3/#webauthn-client
|
||||
///
|
||||
/// The WebAuthn Client is an intermediary entity typically implemented in the user agent
|
||||
/// (in whole, or in part). Conceptually, it underlies the Web Authentication API and embodies
|
||||
/// the implementation of the Web Authentication API's operations.
|
||||
///
|
||||
/// It is responsible for both marshalling the inputs for the underlying authenticator operations,
|
||||
/// and for returning the results of the latter operations to the Web Authentication API's callers.
|
||||
/// </summary>
|
||||
public interface IFido2ClientService
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows WebAuthn Relying Party scripts to request the creation of a new public key credential source.
|
||||
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential
|
||||
/// </summary>
|
||||
/// <param name="createCredentialParams">The parameters for the credential creation operation</param>
|
||||
/// <returns>The new credential</returns>
|
||||
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
|
||||
|
||||
/// <summary>
|
||||
/// Allows WebAuthn Relying Party scripts to discover and use an existing public key credential, with the user’s consent.
|
||||
/// Relying Party script can optionally specify some criteria to indicate what credential sources are acceptable to it.
|
||||
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-getAssertion
|
||||
/// </summary>
|
||||
/// <param name="assertCredentialParams">The parameters for the credential assertion operation</param>
|
||||
/// <returns>The asserted credential</returns>
|
||||
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
|
||||
}
|
||||
}
|
||||
20
src/Core/Abstractions/IFido2GetAssertionUserInterface.cs
Normal file
20
src/Core/Abstractions/IFido2GetAssertionUserInterface.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public struct Fido2GetAssertionUserInterfaceCredential
|
||||
{
|
||||
public string CipherId { get; set; }
|
||||
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||
}
|
||||
|
||||
public interface IFido2GetAssertionUserInterface : IFido2UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Ask the user to pick a credential from a list of existing credentials.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials that the user can pick from, and if the user must be verified before completing the operation</param>
|
||||
/// <returns>The ID of the cipher that contains the credentials the user picked, and if the user was verified before completing the operation</returns>
|
||||
Task<(string CipherId, bool UserVerified)> PickCredentialAsync(Fido2GetAssertionUserInterfaceCredential[] credentials);
|
||||
}
|
||||
}
|
||||
44
src/Core/Abstractions/IFido2MakeCredentialUserInterface.cs
Normal file
44
src/Core/Abstractions/IFido2MakeCredentialUserInterface.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public struct Fido2ConfirmNewCredentialParams
|
||||
{
|
||||
///<summary>
|
||||
/// The name of the credential.
|
||||
///</summary>
|
||||
public string CredentialName { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// The name of the user.
|
||||
///</summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The preference to whether or not the user must be verified before completing the operation.
|
||||
/// </summary>
|
||||
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The relying party identifier
|
||||
/// </summary>
|
||||
public string RpId { get; set; }
|
||||
}
|
||||
|
||||
public interface IFido2MakeCredentialUserInterface : IFido2UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Inform the user that the operation was cancelled because their vault contains excluded credentials.
|
||||
/// </summary>
|
||||
/// <param name="existingCipherIds">The IDs of the excluded credentials.</param>
|
||||
/// <returns>When user has confirmed the message</returns>
|
||||
Task InformExcludedCredentialAsync(string[] existingCipherIds);
|
||||
|
||||
/// <summary>
|
||||
/// Ask the user to confirm the creation of a new credential.
|
||||
/// </summary>
|
||||
/// <param name="confirmNewCredentialParams">The parameters to use when asking the user to confirm the creation of a new credential.</param>
|
||||
/// <returns>The ID of the cipher where the new credential should be saved, and if the user was verified before completing the operation</returns>
|
||||
Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams);
|
||||
}
|
||||
}
|
||||
14
src/Core/Abstractions/IFido2MediatorService.cs
Normal file
14
src/Core/Abstractions/IFido2MediatorService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IFido2MediatorService
|
||||
{
|
||||
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
|
||||
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
|
||||
|
||||
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
|
||||
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
|
||||
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
|
||||
}
|
||||
}
|
||||
17
src/Core/Abstractions/IFido2UserInterface.cs
Normal file
17
src/Core/Abstractions/IFido2UserInterface.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IFido2UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the vault has been unlocked during this transaction
|
||||
/// </summary>
|
||||
bool HasVaultBeenUnlockedInThisTransaction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that the vault is unlocked.
|
||||
/// This should open a window and ask the user to login or unlock the vault if necessary.
|
||||
/// </summary>
|
||||
/// <returns>When vault has been unlocked.</returns>
|
||||
Task EnsureUnlockedVaultAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
@@ -10,5 +9,7 @@ namespace Bit.App.Abstractions
|
||||
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
||||
|
||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||
|
||||
Task<bool> ShouldByPassMasterPasswordRepromptAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
@@ -29,7 +26,7 @@ namespace Bit.Core.Abstractions
|
||||
bool SupportsDuo();
|
||||
Task<bool> SupportsBiometricAsync();
|
||||
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false);
|
||||
Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false);
|
||||
long GetActiveTime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<BwRegion?> GetActiveUserRegionAsync();
|
||||
Task<BwRegion?> GetPreAuthRegionAsync();
|
||||
Task SetPreAuthRegionAsync(BwRegion value);
|
||||
Task ReloadStateAsync();
|
||||
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
Task<string> GetPinProtectedAsync(string userId = null);
|
||||
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IUserPinService
|
||||
{
|
||||
Task<bool> IsPinLockEnabledAsync();
|
||||
Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart);
|
||||
Task<bool> VerifyPinAsync(string inputPin);
|
||||
Task<bool> VerifyPinAsync(string inputPin, string email, KdfConfig kdfConfig, PinLockType pinLockType);
|
||||
}
|
||||
}
|
||||
|
||||
28
src/Core/Abstractions/IUserVerificationMediatorService.cs
Normal file
28
src/Core/Abstractions/IUserVerificationMediatorService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IUserVerificationMediatorService
|
||||
{
|
||||
Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
|
||||
Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options);
|
||||
Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options);
|
||||
Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options);
|
||||
Task<CancellableResult<UVResult>> PerformOSUnlockAsync();
|
||||
Task<CancellableResult<UVResult>> VerifyPinCodeAsync();
|
||||
Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt);
|
||||
|
||||
public struct UVResult
|
||||
{
|
||||
public UVResult(bool canPerform, bool isVerified)
|
||||
{
|
||||
CanPerform = canPerform;
|
||||
IsVerified = isVerified;
|
||||
}
|
||||
|
||||
public bool CanPerform { get; set; }
|
||||
public bool IsVerified { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IUserVerificationService
|
||||
{
|
||||
Task<bool> VerifyUser(string secret, VerificationType verificationType);
|
||||
Task<bool> VerifyMasterPasswordAsync(string masterPassword);
|
||||
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Pages;
|
||||
using Bit.Core.Services;
|
||||
@@ -167,132 +168,153 @@ namespace Bit.App
|
||||
|
||||
_accountsManager.Init(() => Options, this);
|
||||
|
||||
Bootstrap();
|
||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.Command == "showDialog")
|
||||
{
|
||||
var details = message.Data as DialogDetails;
|
||||
var confirmed = true;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||
{
|
||||
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||
details.CancelText);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||
}
|
||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||
});
|
||||
}
|
||||
#if IOS
|
||||
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
else if (message.Command == "slept")
|
||||
{
|
||||
await SleptAsync();
|
||||
}
|
||||
#endif
|
||||
else if (message.Command == "migrated")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
|
||||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
Options.OtpData = new OtpData((string)message.Data);
|
||||
}
|
||||
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
Bootstrap();
|
||||
}
|
||||
|
||||
private async void BroadcastServiceMessageCallbackAsync(Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
if (message.Command == "showDialog")
|
||||
{
|
||||
var details = message.Data as DialogDetails;
|
||||
ArgumentNullException.ThrowIfNull(details);
|
||||
ArgumentNullException.ThrowIfNull(MainPage);
|
||||
|
||||
var confirmed = true;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction);
|
||||
async Task ShowDialogAction()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||
{
|
||||
if (MainPage is TabsPage tabsPage)
|
||||
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||
details.CancelText);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||
}
|
||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||
}
|
||||
}
|
||||
#if IOS
|
||||
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
else if (message.Command == "slept")
|
||||
{
|
||||
await SleptAsync();
|
||||
}
|
||||
#endif
|
||||
else if (message.Command == "migrated")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
|
||||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
Options.OtpData = new OtpData((string)message.Data);
|
||||
}
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction);
|
||||
async Task ExecuteNavigationAction()
|
||||
{
|
||||
if (MainPage is TabsPage tabsPage)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
|
||||
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
|
||||
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await tabsPage.Navigation.PopModalAsync(false);
|
||||
}
|
||||
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
|
||||
{
|
||||
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
|
||||
{
|
||||
Options.MyVaultTile = false;
|
||||
tabsPage.ResetToVaultPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
|
||||
{
|
||||
Options.GeneratorTile = false;
|
||||
tabsPage.ResetToGeneratorPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToSendPage();
|
||||
}
|
||||
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToVaultPage();
|
||||
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
|
||||
}
|
||||
await tabsPage.Navigation.PopModalAsync(false);
|
||||
}
|
||||
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
|
||||
{
|
||||
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
|
||||
{
|
||||
Options.MyVaultTile = false;
|
||||
tabsPage.ResetToVaultPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
|
||||
{
|
||||
Options.GeneratorTile = false;
|
||||
tabsPage.ResetToGeneratorPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToSendPage();
|
||||
}
|
||||
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToVaultPage();
|
||||
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
|
||||
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (message.Command == "convertAccountToKeyConnector")
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.ForceUpdatePassword)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new UpdateTempPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.ForceSetPassword)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
await _configService.GetAsync(true);
|
||||
}
|
||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||
|| message.Command == "unlocked"
|
||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||
{
|
||||
lock (_processingLoginRequestLock)
|
||||
{
|
||||
// lock doesn't allow for async execution
|
||||
CheckPasswordlessLoginRequestsAsync().Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (message.Command == "convertAccountToKeyConnector")
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
ArgumentNullException.ThrowIfNull(MainPage);
|
||||
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
|
||||
async Task NavigateToRemoveMasterPasswordPageAction()
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
}
|
||||
}
|
||||
});
|
||||
else if (message.Command == Constants.ForceUpdatePassword)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(MainPage);
|
||||
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
|
||||
async Task NavigateToUpdateTempPasswordPageAction()
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new UpdateTempPasswordPage()));
|
||||
}
|
||||
}
|
||||
else if (message.Command == Constants.ForceSetPassword)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(MainPage);
|
||||
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction);
|
||||
void NavigateToSetPasswordPageAction()
|
||||
{
|
||||
MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
|
||||
}
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
await _configService.GetAsync(true);
|
||||
}
|
||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||
|| message.Command == "unlocked"
|
||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||
{
|
||||
lock (_processingLoginRequestLock)
|
||||
{
|
||||
// lock doesn't allow for async execution
|
||||
CheckPasswordlessLoginRequestsAsync().Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPasswordlessLoginRequestsAsync()
|
||||
@@ -307,7 +329,6 @@ namespace Bit.App
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
|
||||
if (notification == null)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
|
||||
<PackageReference Include="System.Formats.Cbor" Version="8.0.0" />
|
||||
<PackageReference Include="zxcvbn-core" Version="7.0.92" />
|
||||
<PackageReference Include="MessagePack.MSBuild.Tasks" Version="2.5.124">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -52,6 +53,7 @@
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
|
||||
@@ -75,8 +77,10 @@
|
||||
<Folder Include="Utilities\Automation\" />
|
||||
<Folder Include="Utilities\Prompts\" />
|
||||
<Folder Include="Resources\Localization\" />
|
||||
<Folder Include="Utilities\Fido2\" />
|
||||
<Folder Include="Controls\Picker\" />
|
||||
<Folder Include="Controls\Avatar\" />
|
||||
<Folder Include="Services\UserVerification\" />
|
||||
<Folder Include="Utilities\WebAuthenticatorMAUI\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -106,8 +110,10 @@
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Utilities\Fido2\" />
|
||||
<None Remove="Controls\Picker\" />
|
||||
<None Remove="Controls\Avatar\" />
|
||||
<None Remove="Services\UserVerification\" />
|
||||
<None Remove="Utilities\WebAuthenticatorMAUI\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
@@ -21,6 +20,7 @@ namespace Bit.Core.Models.Api
|
||||
RpName = fido2Key.RpName?.EncryptedString;
|
||||
UserHandle = fido2Key.UserHandle?.EncryptedString;
|
||||
UserName = fido2Key.UserName?.EncryptedString;
|
||||
UserDisplayName = fido2Key.UserDisplayName?.EncryptedString;
|
||||
Counter = fido2Key.Counter?.EncryptedString;
|
||||
CreationDate = fido2Key.CreationDate;
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace Bit.Core.Models.Api
|
||||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Bit.Core.Models.Data
|
||||
RpName = apiData.RpName;
|
||||
UserHandle = apiData.UserHandle;
|
||||
UserName = apiData.UserName;
|
||||
UserDisplayName = apiData.UserDisplayName;
|
||||
Counter = apiData.Counter;
|
||||
CreationDate = apiData.CreationDate;
|
||||
}
|
||||
@@ -33,6 +34,7 @@ namespace Bit.Core.Models.Data
|
||||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
@@ -21,6 +17,7 @@ namespace Bit.Core.Models.Domain
|
||||
nameof(RpName),
|
||||
nameof(UserHandle),
|
||||
nameof(UserName),
|
||||
nameof(UserDisplayName),
|
||||
nameof(Counter)
|
||||
};
|
||||
|
||||
@@ -48,6 +45,7 @@ namespace Bit.Core.Models.Domain
|
||||
public EncString RpName { get; set; }
|
||||
public EncString UserHandle { get; set; }
|
||||
public EncString UserName { get; set; }
|
||||
public EncString UserDisplayName { get; set; }
|
||||
public EncString Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
@@ -9,7 +8,7 @@ namespace Bit.Core.Models.Domain
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new Exception("Must provide key.");
|
||||
throw new ArgumentKeyNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (encType == null)
|
||||
@@ -24,7 +23,7 @@ namespace Bit.Core.Models.Domain
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to determine encType.");
|
||||
throw new InvalidKeyOperationException("Unable to determine encType.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +47,7 @@ namespace Bit.Core.Models.Domain
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unsupported encType/key length.");
|
||||
throw new InvalidKeyOperationException("Unsupported encType/key length.");
|
||||
}
|
||||
|
||||
if (Key != null)
|
||||
@@ -72,6 +71,32 @@ namespace Bit.Core.Models.Domain
|
||||
public string KeyB64 { get; set; }
|
||||
public string EncKeyB64 { get; set; }
|
||||
public string MacKeyB64 { get; set; }
|
||||
|
||||
public class ArgumentKeyNullException : ArgumentNullException
|
||||
{
|
||||
public ArgumentKeyNullException(string paramName) : base(paramName)
|
||||
{
|
||||
}
|
||||
|
||||
public ArgumentKeyNullException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public ArgumentKeyNullException(string paramName, string message) : base(paramName, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidKeyOperationException : InvalidOperationException
|
||||
{
|
||||
public InvalidKeyOperationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidKeyOperationException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UserKey : SymmetricCryptoKey
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Models.View
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.View
|
||||
{
|
||||
@@ -26,13 +26,42 @@ namespace Bit.Core.Models.View
|
||||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int CounterValue {
|
||||
get => int.TryParse(Counter, out var counter) ? counter : 0;
|
||||
set => Counter = value.ToString();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public byte[] UserHandleValue {
|
||||
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
|
||||
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public byte[] KeyBytes {
|
||||
get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue);
|
||||
set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool DiscoverableValue {
|
||||
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
|
||||
set => Discoverable = value.ToString().ToLower();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override string SubTitle => UserName;
|
||||
|
||||
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
|
||||
public bool IsDiscoverable => !string.IsNullOrWhiteSpace(Discoverable);
|
||||
|
||||
[JsonIgnore]
|
||||
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
|
||||
[JsonIgnore]
|
||||
public string LaunchUri => $"https://{RpId}";
|
||||
|
||||
public bool IsUniqueAgainst(Fido2CredentialView fido2View) => fido2View?.RpId != RpId || fido2View?.UserName != UserName;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.View
|
||||
{
|
||||
@@ -40,4 +39,15 @@ namespace Bit.Core.Models.View
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginViewExtensions
|
||||
{
|
||||
public static string GetMainFido2CredentialUsername(this LoginView loginView)
|
||||
{
|
||||
return loginView.MainFido2Credential.UserName
|
||||
.FallbackOnNullOrWhiteSpace(loginView.MainFido2Credential.UserDisplayName)
|
||||
.FallbackOnNullOrWhiteSpace(loginView.Username)
|
||||
.FallbackOnNullOrWhiteSpace(AppResources.UnknownAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<ScrollView>
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="MAUI APP"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
|
||||
@@ -515,7 +515,7 @@ namespace Bit.App.Pages
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
|
||||
!PinEnabled && !HasMasterPassword);
|
||||
!PinEnabled && !HasMasterPassword) ?? false;
|
||||
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
|
||||
@@ -68,7 +68,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get
|
||||
{
|
||||
var appInfo = string.Format("{0}: {1} ({2})",
|
||||
// TODO: REMOVE WHEN MERGED INTO MAIN BRANCH
|
||||
var appInfo = string.Format("MAUI {0}: {1} ({2})",
|
||||
AppResources.Version,
|
||||
_platformUtilsService.GetApplicationVersion(),
|
||||
_deviceActionService.GetBuildNumber());
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
x:Class="Bit.App.Pages.AutofillPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
Title="{u:I18n PasswordAutofill}">
|
||||
Title="{u:I18n SetUpAutofill}">
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
@@ -15,26 +15,22 @@
|
||||
<StackLayout Spacing="5"
|
||||
Padding="20, 20, 20, 30"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<Label Text="{u:I18n ExtensionInstantAccess}"
|
||||
<Label Text="{u:I18n GetInstantAccessToYourPasswordsAndPasskeys}"
|
||||
HorizontalOptions="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineBreakMode="WordWrap"
|
||||
StyleClass="text-lg"
|
||||
Margin="0, 0, 0, 15" />
|
||||
<Label Text="{u:I18n AutofillTurnOn}"
|
||||
<Label Text="{u:I18n SetUpAutoFillDescriptionLong}"
|
||||
HorizontalOptions="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineBreakMode="WordWrap"
|
||||
Margin="0, 0, 0, 15" />
|
||||
<Label Text="{u:I18n AutofillTurnOn1}"
|
||||
<Label Text="{u:I18n FirstDotGoToYourDeviceSettingsPasswordsPasswordOptions}"
|
||||
LineBreakMode="WordWrap" />
|
||||
<Label Text="{u:I18n AutofillTurnOn2}"
|
||||
<Label Text="{u:I18n SecondDotTurnOnAutoFill}"
|
||||
LineBreakMode="WordWrap" />
|
||||
<Label Text="{u:I18n AutofillTurnOn3}"
|
||||
LineBreakMode="WordWrap" />
|
||||
<Label Text="{u:I18n AutofillTurnOn4}"
|
||||
LineBreakMode="WordWrap" />
|
||||
<Label Text="{u:I18n AutofillTurnOn5}"
|
||||
<Label Text="{u:I18n ThirdDotSelectBitwardenToUseForPasswordsAndPasskeys}"
|
||||
LineBreakMode="WordWrap" />
|
||||
<Image Source="autofill-kb.png"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
Text="{u:I18n Autofill}"
|
||||
StyleClass="settings-header" />
|
||||
|
||||
<controls:SwitchItemView
|
||||
Title="{u:I18n CredentialProviderService}"
|
||||
Subtitle="{u:I18n CredentialProviderServiceExplanationLong}"
|
||||
IsVisible="{Binding SupportsCredentialProviderService}"
|
||||
IsToggled="{Binding UseCredentialProviderService}"
|
||||
AutomationId="CredentialProviderServiceSwitch"
|
||||
StyleClass="settings-item-view"
|
||||
HorizontalOptions="FillAndExpand" />
|
||||
|
||||
<controls:SwitchItemView
|
||||
Title="{u:I18n AutofillServices}"
|
||||
Subtitle="{u:I18n AutofillServicesExplanationLong}"
|
||||
|
||||
@@ -6,12 +6,27 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public partial class AutofillSettingsPageViewModel
|
||||
{
|
||||
private bool _useCredentialProviderService;
|
||||
private bool _useAutofillServices;
|
||||
private bool _useInlineAutofill;
|
||||
private bool _useAccessibility;
|
||||
private bool _useDrawOver;
|
||||
private bool _askToAddLogin;
|
||||
|
||||
public bool SupportsCredentialProviderService => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsCredentialProviderService();
|
||||
|
||||
public bool UseCredentialProviderService
|
||||
{
|
||||
get => _useCredentialProviderService;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useCredentialProviderService, value))
|
||||
{
|
||||
((ICommand)ToggleUseCredentialProviderServiceCommand).Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices();
|
||||
|
||||
public bool UseAutofillServices
|
||||
@@ -84,6 +99,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncRelayCommand ToggleUseCredentialProviderServiceCommand { get; private set; }
|
||||
public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; }
|
||||
public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; }
|
||||
public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; }
|
||||
@@ -93,6 +109,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void InitAndroidCommands()
|
||||
{
|
||||
ToggleUseCredentialProviderServiceCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseCredentialProviderService()), () => _inited, allowsMultipleExecutions: false);
|
||||
ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false);
|
||||
ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false);
|
||||
ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false);
|
||||
@@ -115,6 +132,9 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task UpdateAndroidAutofillSettingsAsync()
|
||||
{
|
||||
// TODO - uncomment once _autofillHandler.CredentialProviderServiceEnabled() returns a real value
|
||||
// _useCredentialProviderService =
|
||||
// SupportsCredentialProviderService && _autofillHandler.CredentialProviderServiceEnabled();
|
||||
_useAutofillServices =
|
||||
_autofillHandler.SupportsAutofillService() && _autofillHandler.AutofillServiceEnabled();
|
||||
_useAccessibility = _autofillHandler.AutofillAccessibilityServiceRunning();
|
||||
@@ -123,6 +143,7 @@ namespace Bit.App.Pages
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
TriggerPropertyChanged(nameof(UseCredentialProviderService));
|
||||
TriggerPropertyChanged(nameof(UseAutofillServices));
|
||||
TriggerPropertyChanged(nameof(UseAccessibility));
|
||||
TriggerPropertyChanged(nameof(UseDrawOver));
|
||||
@@ -130,6 +151,18 @@ namespace Bit.App.Pages
|
||||
});
|
||||
}
|
||||
|
||||
private void ToggleUseCredentialProviderService()
|
||||
{
|
||||
if (UseCredentialProviderService)
|
||||
{
|
||||
_deviceActionService.OpenCredentialProviderSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
_autofillHandler.DisableCredentialProviderService();
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleUseAutofillServices()
|
||||
{
|
||||
if (UseAutofillServices)
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace Bit.App.Pages
|
||||
|
||||
if (!_supportsBiometric
|
||||
||
|
||||
!await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null))
|
||||
await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null) != true)
|
||||
{
|
||||
_canUnlockWithBiometrics = false;
|
||||
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics)));
|
||||
|
||||
225
src/Core/Resources/Localization/AppResources.Designer.cs
generated
225
src/Core/Resources/Localization/AppResources.Designer.cs
generated
@@ -1318,6 +1318,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to We were unable to automatically open the Android credential provider settings menu for you. You can navigate to the credential provider settings menu manually from Android Settings > System > Passwords & accounts > Passwords, passkeys and data services..
|
||||
/// </summary>
|
||||
public static string BitwardenCredentialProviderGoToSettings {
|
||||
get {
|
||||
return ResourceManager.GetString("BitwardenCredentialProviderGoToSettings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bitwarden Help Center.
|
||||
/// </summary>
|
||||
@@ -1534,6 +1543,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose a login to save this passkey to.
|
||||
/// </summary>
|
||||
public static string ChooseALoginToSaveThisPasskeyTo {
|
||||
get {
|
||||
return ResourceManager.GetString("ChooseALoginToSaveThisPasskeyTo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose file.
|
||||
/// </summary>
|
||||
@@ -1894,6 +1912,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Credential Provider service.
|
||||
/// </summary>
|
||||
public static string CredentialProviderService {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialProviderService", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The Android Credential Provider is used for managing passkeys for use with websites and other apps on your device..
|
||||
/// </summary>
|
||||
public static string CredentialProviderServiceExplanationLong {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialProviderServiceExplanationLong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Credits.
|
||||
/// </summary>
|
||||
@@ -2596,6 +2632,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error creating passkey.
|
||||
/// </summary>
|
||||
public static string ErrorCreatingPasskey {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorCreatingPasskey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error reading passkey.
|
||||
/// </summary>
|
||||
public static string ErrorReadingPasskey {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorReadingPasskey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to EU.
|
||||
/// </summary>
|
||||
@@ -3136,6 +3190,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 1. Go to your device's Settings > Passwords > Password Options.
|
||||
/// </summary>
|
||||
public static string FirstDotGoToYourDeviceSettingsPasswordsPasswordOptions {
|
||||
get {
|
||||
return ResourceManager.GetString("FirstDotGoToYourDeviceSettingsPasswordsPasswordOptions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to First name.
|
||||
/// </summary>
|
||||
@@ -3325,6 +3388,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get instant access to your passwords and passkeys!.
|
||||
/// </summary>
|
||||
public static string GetInstantAccessToYourPasswordsAndPasskeys {
|
||||
get {
|
||||
return ResourceManager.GetString("GetInstantAccessToYourPasswordsAndPasskeys", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Get master password hint.
|
||||
/// </summary>
|
||||
@@ -5128,6 +5200,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Overwrite passkey?.
|
||||
/// </summary>
|
||||
public static string OverwritePasskey {
|
||||
get {
|
||||
return ResourceManager.GetString("OverwritePasskey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ownership.
|
||||
/// </summary>
|
||||
@@ -5155,6 +5236,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkeys for {0}.
|
||||
/// </summary>
|
||||
public static string PasskeysForX {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeysForX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey will not be copied.
|
||||
/// </summary>
|
||||
@@ -5335,6 +5425,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passwords.
|
||||
/// </summary>
|
||||
public static string Passwords {
|
||||
get {
|
||||
return ResourceManager.GetString("Passwords", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This password was not found in any known data breaches. It should be safe to use..
|
||||
/// </summary>
|
||||
@@ -5344,6 +5443,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passwords for {0}.
|
||||
/// </summary>
|
||||
public static string PasswordsForX {
|
||||
get {
|
||||
return ResourceManager.GetString("PasswordsForX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Password type.
|
||||
/// </summary>
|
||||
@@ -5849,6 +5957,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save passkey.
|
||||
/// </summary>
|
||||
public static string SavePasskey {
|
||||
get {
|
||||
return ResourceManager.GetString("SavePasskey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save passkey as new login.
|
||||
/// </summary>
|
||||
public static string SavePasskeyAsNewLogin {
|
||||
get {
|
||||
return ResourceManager.GetString("SavePasskeyAsNewLogin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Saving....
|
||||
/// </summary>
|
||||
@@ -5957,6 +6083,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 2. Turn on AutoFill.
|
||||
/// </summary>
|
||||
public static string SecondDotTurnOnAutoFill {
|
||||
get {
|
||||
return ResourceManager.GetString("SecondDotTurnOnAutoFill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Secure notes.
|
||||
/// </summary>
|
||||
@@ -6281,6 +6416,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Set up auto-fill.
|
||||
/// </summary>
|
||||
public static string SetUpAutofill {
|
||||
get {
|
||||
return ResourceManager.GetString("SetUpAutofill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to To set up password auto-fill and passkey management, set Bitwarden as your preferred provider in the iOS Settings..
|
||||
/// </summary>
|
||||
public static string SetUpAutoFillDescriptionLong {
|
||||
get {
|
||||
return ResourceManager.GetString("SetUpAutoFillDescriptionLong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Set up TOTP.
|
||||
/// </summary>
|
||||
@@ -6686,6 +6839,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There was a problem creating a passkey for {0}. Try again later..
|
||||
/// </summary>
|
||||
public static string ThereWasAProblemCreatingAPasskeyForXTryAgainLater {
|
||||
get {
|
||||
return ResourceManager.GetString("ThereWasAProblemCreatingAPasskeyForXTryAgainLater", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There was a problem reading your passkey for {0}. Try again later..
|
||||
/// </summary>
|
||||
public static string ThereWasAProblemReadingAPasskeyForXTryAgainLater {
|
||||
get {
|
||||
return ResourceManager.GetString("ThereWasAProblemReadingAPasskeyForXTryAgainLater", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The URI {0} is already blocked.
|
||||
/// </summary>
|
||||
@@ -6695,6 +6866,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 3. Select "Bitwarden" to use for passwords and passkeys.
|
||||
/// </summary>
|
||||
public static string ThirdDotSelectBitwardenToUseForPasswordsAndPasskeys {
|
||||
get {
|
||||
return ResourceManager.GetString("ThirdDotSelectBitwardenToUseForPasswordsAndPasskeys", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 30 days.
|
||||
/// </summary>
|
||||
@@ -6722,6 +6902,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This item already contains a passkey. Are you sure you want to overwrite the current passkey?.
|
||||
/// </summary>
|
||||
public static string ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey {
|
||||
get {
|
||||
return ResourceManager.GetString("ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This request is no longer valid.
|
||||
/// </summary>
|
||||
@@ -7019,6 +7208,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unknown account.
|
||||
/// </summary>
|
||||
public static string UnknownAccount {
|
||||
get {
|
||||
return ResourceManager.GetString("UnknownAccount", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unknown {0} error occurred..
|
||||
/// </summary>
|
||||
@@ -7505,6 +7703,24 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verification required by {0}.
|
||||
/// </summary>
|
||||
public static string VerificationRequiredByX {
|
||||
get {
|
||||
return ResourceManager.GetString("VerificationRequiredByX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verification required for this action. Set up an unlock method in Bitwarden to continue..
|
||||
/// </summary>
|
||||
public static string VerificationRequiredForThisActionSetUpAnUnlockMethodInBitwardenToContinue {
|
||||
get {
|
||||
return ResourceManager.GetString("VerificationRequiredForThisActionSetUpAnUnlockMethodInBitwardenToContinue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verify Face ID.
|
||||
/// </summary>
|
||||
@@ -7532,6 +7748,15 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verifying identity....
|
||||
/// </summary>
|
||||
public static string VerifyingIdentityEllipsis {
|
||||
get {
|
||||
return ResourceManager.GetString("VerifyingIdentityEllipsis", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Verify master password.
|
||||
/// </summary>
|
||||
|
||||
@@ -2878,12 +2878,12 @@
|
||||
<value>أعدنّ ميزة إلغاء القُفْل لتغيير إجراء مهلة المخزن الخاص بك.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>تسجيل الدخول لـ Duo من خطوتين مطلوب لحسابك. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
<value>اتبع الخطوات من Duo لإنهاء تسجيل الدخول.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>تشغيل Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2623,22 +2623,22 @@
|
||||
<value>Бягучы асноўны пароль</value>
|
||||
</data>
|
||||
<data name="LoggedIn" xml:space="preserve">
|
||||
<value>Logged in!</value>
|
||||
<value>Вы ўвайшлі!</value>
|
||||
</data>
|
||||
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||
<value>Approve with my other device</value>
|
||||
</data>
|
||||
<data name="RequestAdminApproval" xml:space="preserve">
|
||||
<value>Request admin approval</value>
|
||||
<value>Запытаць ухваленне адміністратара</value>
|
||||
</data>
|
||||
<data name="ApproveWithMasterPassword" xml:space="preserve">
|
||||
<value>Approve with master password</value>
|
||||
<value>Ухваліць з дапамогай асноўнага пароля</value>
|
||||
</data>
|
||||
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
|
||||
<value>Turn off using a public device</value>
|
||||
</data>
|
||||
<data name="RememberThisDevice" xml:space="preserve">
|
||||
<value>Remember this device</value>
|
||||
<value>Запомніць гэту прыладу</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Passkey</value>
|
||||
@@ -2677,19 +2677,19 @@
|
||||
<value>Памылковы токен API</value>
|
||||
</data>
|
||||
<data name="AdminApprovalRequested" xml:space="preserve">
|
||||
<value>Admin approval requested</value>
|
||||
<value>Патрабуецца ўхваленне адміністратара</value>
|
||||
</data>
|
||||
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
|
||||
<value>Your request has been sent to your admin.</value>
|
||||
<value>Ваш запыт адпраўлены адміністратару.</value>
|
||||
</data>
|
||||
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
|
||||
<value>You will be notified once approved. </value>
|
||||
<value>Вы атрымаеце апавяшчэння пасля яго ўхвалення. </value>
|
||||
</data>
|
||||
<data name="TroubleLoggingIn" xml:space="preserve">
|
||||
<value>Trouble logging in?</value>
|
||||
<value>Праблемы з уваходам?</value>
|
||||
</data>
|
||||
<data name="LoggingInAsX" xml:space="preserve">
|
||||
<value>Logging in as {0}</value>
|
||||
<value>Увайсці як {0}</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
|
||||
<value>Vault timeout action changed to log out</value>
|
||||
@@ -2738,7 +2738,7 @@
|
||||
<value>Немагчыма рэдагаваць некалькі URI адначасова</value>
|
||||
</data>
|
||||
<data name="LoginApproved" xml:space="preserve">
|
||||
<value>Login approved</value>
|
||||
<value>Уваход ухвалены</value>
|
||||
</data>
|
||||
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
|
||||
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
|
||||
@@ -2747,28 +2747,28 @@
|
||||
<value>Log in with device</value>
|
||||
</data>
|
||||
<data name="LoggingInOn" xml:space="preserve">
|
||||
<value>Logging in on</value>
|
||||
<value>Увайсці на</value>
|
||||
</data>
|
||||
<data name="Vault" xml:space="preserve">
|
||||
<value>Vault</value>
|
||||
<value>Сховішча</value>
|
||||
</data>
|
||||
<data name="Appearance" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
<value>Знешні выгляд</value>
|
||||
</data>
|
||||
<data name="AccountSecurity" xml:space="preserve">
|
||||
<value>Account security</value>
|
||||
<value>Бяспеке акаўнта</value>
|
||||
</data>
|
||||
<data name="BitwardenHelpCenter" xml:space="preserve">
|
||||
<value>Bitwarden Help Center</value>
|
||||
<value>Даведачны цэнтр Bitwarden</value>
|
||||
</data>
|
||||
<data name="ContactBitwardenSupport" xml:space="preserve">
|
||||
<value>Contact Bitwarden support</value>
|
||||
<value>Звярніцеся ў службу падтрымкі Bitwarden</value>
|
||||
</data>
|
||||
<data name="CopyAppInformation" xml:space="preserve">
|
||||
<value>Copy app information</value>
|
||||
</data>
|
||||
<data name="SyncNow" xml:space="preserve">
|
||||
<value>Sync now</value>
|
||||
<value>Сінхранізаваць зараз</value>
|
||||
</data>
|
||||
<data name="UnlockOptions" xml:space="preserve">
|
||||
<value>Unlock options</value>
|
||||
@@ -2805,7 +2805,7 @@
|
||||
<value>Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay.</value>
|
||||
</data>
|
||||
<data name="AdditionalOptions" xml:space="preserve">
|
||||
<value>Additional options</value>
|
||||
<value>Дадатковы параметры</value>
|
||||
</data>
|
||||
<data name="ContinueToWebApp" xml:space="preserve">
|
||||
<value>Continue to web app?</value>
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
<comment>Label for notes.</comment>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>Ok</value>
|
||||
<value>Iawn</value>
|
||||
<comment>Acknowledgement.</comment>
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
@@ -346,7 +346,7 @@
|
||||
<comment>Confirmation message after successfully deleting a login.</comment>
|
||||
</data>
|
||||
<data name="Submit" xml:space="preserve">
|
||||
<value>Submit</value>
|
||||
<value>Cyflwyno</value>
|
||||
</data>
|
||||
<data name="Sync" xml:space="preserve">
|
||||
<value>Cysoni</value>
|
||||
@@ -371,7 +371,7 @@
|
||||
<comment>Label for a username.</comment>
|
||||
</data>
|
||||
<data name="ValidationFieldRequired" xml:space="preserve">
|
||||
<value>The {0} field is required.</value>
|
||||
<value>Mae'r maes {0} yn ofynnol.</value>
|
||||
<comment>Validation message for when a form field is left blank and is required to be entered.</comment>
|
||||
</data>
|
||||
<data name="ValueHasBeenCopied" xml:space="preserve">
|
||||
@@ -382,7 +382,7 @@
|
||||
<value>Verify fingerprint</value>
|
||||
</data>
|
||||
<data name="VerifyMasterPassword" xml:space="preserve">
|
||||
<value>Verify master password</value>
|
||||
<value>Gwirio'r prif gyfrinair</value>
|
||||
</data>
|
||||
<data name="VerifyPIN" xml:space="preserve">
|
||||
<value>Verify PIN</value>
|
||||
@@ -407,7 +407,7 @@
|
||||
<value>Cyfrif</value>
|
||||
</data>
|
||||
<data name="AccountCreated" xml:space="preserve">
|
||||
<value>Your new account has been created! You may now log in.</value>
|
||||
<value>Mae eich cyfrif newydd wedi cael ei greu! Gallwch bellach fewngofnodi.</value>
|
||||
</data>
|
||||
<data name="AddAnItem" xml:space="preserve">
|
||||
<value>Ychwanegu eitem</value>
|
||||
@@ -784,10 +784,10 @@
|
||||
<value>Do you want to auto-fill or view this item?</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceMatchConfirm" xml:space="preserve">
|
||||
<value>Are you sure you want to auto-fill this item? It is not a complete match for "{0}".</value>
|
||||
<value>Ydych chi'n siŵr eich bod am lenwi'r eitem hon? Dyw hi ddim yn cyfateb yn llwyr i "{0}".</value>
|
||||
</data>
|
||||
<data name="MatchingItems" xml:space="preserve">
|
||||
<value>Matching items</value>
|
||||
<value>Eitemau sy'n cyfateb</value>
|
||||
</data>
|
||||
<data name="PossibleMatchingItems" xml:space="preserve">
|
||||
<value>Possible matching items</value>
|
||||
@@ -799,22 +799,22 @@
|
||||
<value>You are searching for an auto-fill item for "{0}".</value>
|
||||
</data>
|
||||
<data name="LearnOrg" xml:space="preserve">
|
||||
<value>Learn about organizations</value>
|
||||
<value>Dysgu am sefydliadau</value>
|
||||
</data>
|
||||
<data name="CannotOpenApp" xml:space="preserve">
|
||||
<value>Cannot open the app "{0}".</value>
|
||||
<value>Methu agor yr ap "{0}".</value>
|
||||
<comment>Message shown when trying to launch an app that does not exist on the user's device.</comment>
|
||||
</data>
|
||||
<data name="AuthenticatorAppTitle" xml:space="preserve">
|
||||
<value>Authenticator app</value>
|
||||
<value>Ap dilysu</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="EnterVerificationCodeApp" xml:space="preserve">
|
||||
<value>Enter the 6 digit verification code from your authenticator app.</value>
|
||||
<value>Rhowch y cod dilysu 6 nod o'ch ap dilysu.</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="EnterVerificationCodeEmail" xml:space="preserve">
|
||||
<value>Enter the 6 digit verification code that was emailed to {0}.</value>
|
||||
<value>Rhowch y cod dilysu 6 nod anfonwyd i {0}.</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="LoginUnavailable" xml:space="preserve">
|
||||
@@ -895,7 +895,7 @@
|
||||
Scanning will happen automatically.</value>
|
||||
</data>
|
||||
<data name="ScanQrTitle" xml:space="preserve">
|
||||
<value>Scan QR Code</value>
|
||||
<value>Sganio cod QR</value>
|
||||
</data>
|
||||
<data name="Camera" xml:space="preserve">
|
||||
<value>Camera</value>
|
||||
@@ -1143,7 +1143,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Icons server URL</value>
|
||||
</data>
|
||||
<data name="AutofillWithBitwarden" xml:space="preserve">
|
||||
<value>Auto-fill with Bitwarden</value>
|
||||
<value>Llenwi'n awtomatig â Bitwarden</value>
|
||||
</data>
|
||||
<data name="VaultIsLocked" xml:space="preserve">
|
||||
<value>Vault is locked</value>
|
||||
@@ -1173,7 +1173,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Use the Bitwarden auto-fill service to fill login information into other apps.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceOpenAutofillSettings" xml:space="preserve">
|
||||
<value>Open Autofill Settings</value>
|
||||
<value>Agor gosodiadau llenwi awtomatig</value>
|
||||
</data>
|
||||
<data name="FaceID" xml:space="preserve">
|
||||
<value>Face ID</value>
|
||||
@@ -1256,7 +1256,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Yes, and save</value>
|
||||
</data>
|
||||
<data name="AutofillAndSave" xml:space="preserve">
|
||||
<value>Auto-fill and save</value>
|
||||
<value>Llenwi'n awtomatig a chadw</value>
|
||||
</data>
|
||||
<data name="Organization" xml:space="preserve">
|
||||
<value>Sefydliad</value>
|
||||
@@ -1486,7 +1486,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.</value>
|
||||
</data>
|
||||
<data name="LoggedInAsOn" xml:space="preserve">
|
||||
<value>Logged in as {0} on {1}.</value>
|
||||
<value>Rydych wedi mewngofnodi fel {0} ar {1}.</value>
|
||||
<comment>ex: Logged in as user@example.com on bitwarden.com.</comment>
|
||||
</data>
|
||||
<data name="VaultLockedMasterPassword" xml:space="preserve">
|
||||
@@ -1541,7 +1541,7 @@ Scanning will happen automatically.</value>
|
||||
<comment>Color theme</comment>
|
||||
</data>
|
||||
<data name="ThemeDescription" xml:space="preserve">
|
||||
<value>Newid thema liwiau'r ap.</value>
|
||||
<value>Newid thema liwiau'r ap</value>
|
||||
</data>
|
||||
<data name="ThemeDefault" xml:space="preserve">
|
||||
<value>Rhagosodiad (system)</value>
|
||||
@@ -1577,7 +1577,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Auto-fill blocked URIs</value>
|
||||
</data>
|
||||
<data name="AskToAddLogin" xml:space="preserve">
|
||||
<value>Ask to add login</value>
|
||||
<value>Gofyn i ychwanegu manylion mewngofnodi</value>
|
||||
</data>
|
||||
<data name="AskToAddLoginDescription" xml:space="preserve">
|
||||
<value>Ask to add an item if one isn't found in your vault.</value>
|
||||
@@ -1669,7 +1669,7 @@ Scanning will happen automatically.</value>
|
||||
<comment>Title for the alert to confirm vault exports.</comment>
|
||||
</data>
|
||||
<data name="Warning" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
<value>Rhybudd</value>
|
||||
</data>
|
||||
<data name="ExportVaultFailure" xml:space="preserve">
|
||||
<value>There was a problem exporting your vault. If the problem persists, you'll need to export from the web vault.</value>
|
||||
@@ -1945,7 +1945,7 @@ Scanning will happen automatically.</value>
|
||||
<value>Current access count</value>
|
||||
</data>
|
||||
<data name="NewPassword" xml:space="preserve">
|
||||
<value>New password</value>
|
||||
<value>Cyfrinair newydd</value>
|
||||
</data>
|
||||
<data name="PasswordInfo" xml:space="preserve">
|
||||
<value>Optionally require a password for users to access this Send.</value>
|
||||
@@ -2310,7 +2310,7 @@ select Add TOTP to store the key safely</value>
|
||||
<value>Caniatáu sgrinluniau</value>
|
||||
</data>
|
||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||
<value>Are you sure you want to turn on screen capture?</value>
|
||||
<value>Ydych chi'n siŵr eich bod am ganiatáu sgrinluniau?</value>
|
||||
</data>
|
||||
<data name="LogInRequested" xml:space="preserve">
|
||||
<value>Login requested</value>
|
||||
@@ -2394,16 +2394,16 @@ select Add TOTP to store the key safely</value>
|
||||
<value>Gair ar hap</value>
|
||||
</data>
|
||||
<data name="EmailRequiredParenthesis" xml:space="preserve">
|
||||
<value>Email (required)</value>
|
||||
<value>Ebost (gofynnol)</value>
|
||||
</data>
|
||||
<data name="DomainNameRequiredParenthesis" xml:space="preserve">
|
||||
<value>Enw parth (gofynnol)</value>
|
||||
</data>
|
||||
<data name="APIKeyRequiredParenthesis" xml:space="preserve">
|
||||
<value>API key (required)</value>
|
||||
<value>Allwedd API (gofynnol)</value>
|
||||
</data>
|
||||
<data name="Service" xml:space="preserve">
|
||||
<value>Service</value>
|
||||
<value>Gwasanaeth</value>
|
||||
</data>
|
||||
<data name="AddyIo" xml:space="preserve">
|
||||
<value>addy.io</value>
|
||||
@@ -2549,7 +2549,7 @@ Do you want to switch to this account?</value>
|
||||
<value>Bydd angen ailgychwyn yr ap</value>
|
||||
</data>
|
||||
<data name="DefaultSystem" xml:space="preserve">
|
||||
<value>Default (System)</value>
|
||||
<value>Rhagosodiad (system)</value>
|
||||
</data>
|
||||
<data name="Important" xml:space="preserve">
|
||||
<value>Pwysig</value>
|
||||
@@ -2624,7 +2624,7 @@ Do you want to switch to this account?</value>
|
||||
<value>Prif gyfrinair presennol</value>
|
||||
</data>
|
||||
<data name="LoggedIn" xml:space="preserve">
|
||||
<value>Logged in!</value>
|
||||
<value>Wedi mewngofnodi!</value>
|
||||
</data>
|
||||
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||
<value>Approve with my other device</value>
|
||||
@@ -2812,17 +2812,17 @@ Do you want to switch to this account?</value>
|
||||
<value>Parhau i'r ap gwe?</value>
|
||||
</data>
|
||||
<data name="ContinueToX" xml:space="preserve">
|
||||
<value>Continue to {0}?</value>
|
||||
<value>Parhau i {0}?</value>
|
||||
<comment>The parameter is an URL, like bitwarden.com.</comment>
|
||||
</data>
|
||||
<data name="ContinueToHelpCenter" xml:space="preserve">
|
||||
<value>Continue to Help center?</value>
|
||||
<value>Parhau i'r ganolfan gymorth?</value>
|
||||
</data>
|
||||
<data name="ContinueToContactSupport" xml:space="preserve">
|
||||
<value>Continue to contact support?</value>
|
||||
</data>
|
||||
<data name="ContinueToPrivacyPolicy" xml:space="preserve">
|
||||
<value>Continue to privacy policy?</value>
|
||||
<value>Parhau i'r polisi preifatrwydd?</value>
|
||||
</data>
|
||||
<data name="ContinueToAppStore" xml:space="preserve">
|
||||
<value>Continue to app store?</value>
|
||||
@@ -2859,11 +2859,11 @@ Do you want to switch to this account?</value>
|
||||
<value>Choose the dark theme to use when your device’s dark mode is in use</value>
|
||||
</data>
|
||||
<data name="CreatedXY" xml:space="preserve">
|
||||
<value>Created {0}, {1}</value>
|
||||
<value>Crëwyd ar {0} am {1}</value>
|
||||
<comment>To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time.</comment>
|
||||
</data>
|
||||
<data name="TooManyAttempts" xml:space="preserve">
|
||||
<value>Too many attempts</value>
|
||||
<value>Gormod o geisiadau</value>
|
||||
</data>
|
||||
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
|
||||
<value>Account logged out.</value>
|
||||
@@ -2884,6 +2884,6 @@ Do you want to switch to this account?</value>
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Lansio Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -152,11 +152,11 @@
|
||||
<comment>Copy some value to your clipboard.</comment>
|
||||
</data>
|
||||
<data name="CopyPassword" xml:space="preserve">
|
||||
<value>Αντιγραφή Κωδικού</value>
|
||||
<value>Αντιγραφή κωδικού</value>
|
||||
<comment>The button text that allows a user to copy the login's password to their clipboard.</comment>
|
||||
</data>
|
||||
<data name="CopyUsername" xml:space="preserve">
|
||||
<value>Αντιγραφή Ονόματος Χρήστη</value>
|
||||
<value>Αντιγραφή ονόματος χρήστη</value>
|
||||
<comment>The button text that allows a user to copy the login's username to their clipboard.</comment>
|
||||
</data>
|
||||
<data name="Credits" xml:space="preserve">
|
||||
@@ -179,14 +179,14 @@
|
||||
<value>Επεξεργασία</value>
|
||||
</data>
|
||||
<data name="EditFolder" xml:space="preserve">
|
||||
<value>Επεξεργασία Φακέλου</value>
|
||||
<value>Επεξεργασία φακέλου</value>
|
||||
</data>
|
||||
<data name="Email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
<comment>Short label for an email address.</comment>
|
||||
</data>
|
||||
<data name="EmailAddress" xml:space="preserve">
|
||||
<value>Διεύθυνση Email</value>
|
||||
<value>Διεύθυνση email</value>
|
||||
<comment>Full label for a email address.</comment>
|
||||
</data>
|
||||
<data name="EmailUs" xml:space="preserve">
|
||||
@@ -203,7 +203,7 @@
|
||||
<comment>Title for your favorite items in the vault.</comment>
|
||||
</data>
|
||||
<data name="FileBugReport" xml:space="preserve">
|
||||
<value>Υποβολή Αναφοράς Σφάλματος</value>
|
||||
<value>Υποβολή αναφοράς σφάλματος</value>
|
||||
</data>
|
||||
<data name="FileBugReportDescription" xml:space="preserve">
|
||||
<value>Αναφέρετε το προβλήμα στο GitHub.</value>
|
||||
@@ -229,14 +229,14 @@
|
||||
<value>Φάκελοι</value>
|
||||
</data>
|
||||
<data name="FolderUpdated" xml:space="preserve">
|
||||
<value>Ο φάκελος ενημερώθηκε.</value>
|
||||
<value>Ο φάκελος ενημερώθηκε</value>
|
||||
</data>
|
||||
<data name="GoToWebsite" xml:space="preserve">
|
||||
<value>Μετάβαση στην ιστοσελίδα</value>
|
||||
<comment>The button text that allows user to launch the website to their web browser.</comment>
|
||||
</data>
|
||||
<data name="HelpAndFeedback" xml:space="preserve">
|
||||
<value>Βοήθεια και Σχόλια</value>
|
||||
<value>Βοήθεια και σχόλια</value>
|
||||
</data>
|
||||
<data name="Hide" xml:space="preserve">
|
||||
<value>Απόκρυψη</value>
|
||||
@@ -251,7 +251,7 @@
|
||||
<comment>Title for the alert when internet connection is required to continue.</comment>
|
||||
</data>
|
||||
<data name="InvalidMasterPassword" xml:space="preserve">
|
||||
<value>Μη έγκυρος κύριος κωδικός, δοκιμάστε ξανά.</value>
|
||||
<value>Μη έγκυρος κύριος κωδικός. Δοκιμάστε ξανά.</value>
|
||||
</data>
|
||||
<data name="InvalidPIN" xml:space="preserve">
|
||||
<value>Μη έγκυρο PIN. Προσπάθηστε ξανά.</value>
|
||||
@@ -282,7 +282,7 @@
|
||||
<value>Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτόν τον λογαριασμό?</value>
|
||||
</data>
|
||||
<data name="AccountAlreadyAdded" xml:space="preserve">
|
||||
<value>Ο Λογαριασμός Προστέθηκε Ήδη</value>
|
||||
<value>Ο λογαριασμός έχει ήδη προστεθεί</value>
|
||||
</data>
|
||||
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
|
||||
<value>Θα θέλατε να το αλλάξετε τώρα?</value>
|
||||
@@ -296,11 +296,11 @@
|
||||
<comment>Text to define that there are more options things to see.</comment>
|
||||
</data>
|
||||
<data name="MyVault" xml:space="preserve">
|
||||
<value>Το Vault μου</value>
|
||||
<value>Το vault μου</value>
|
||||
<comment>The title for the vault page.</comment>
|
||||
</data>
|
||||
<data name="Authenticator" xml:space="preserve">
|
||||
<value>Εφαρμογή Επαλήθευσης</value>
|
||||
<value>Αυθεντικοποιητής</value>
|
||||
<comment>Authenticator TOTP feature</comment>
|
||||
</data>
|
||||
<data name="Name" xml:space="preserve">
|
||||
@@ -342,7 +342,7 @@
|
||||
<comment>Reveal a hidden value (password).</comment>
|
||||
</data>
|
||||
<data name="ItemDeleted" xml:space="preserve">
|
||||
<value>Το στοιχείο έχει διαγραφεί.</value>
|
||||
<value>Το στοιχείο διαγράφτηκε</value>
|
||||
<comment>Confirmation message after successfully deleting a login.</comment>
|
||||
</data>
|
||||
<data name="Submit" xml:space="preserve">
|
||||
@@ -375,14 +375,14 @@
|
||||
<comment>Validation message for when a form field is left blank and is required to be entered.</comment>
|
||||
</data>
|
||||
<data name="ValueHasBeenCopied" xml:space="preserve">
|
||||
<value>{0} έχει αντιγραφεί.</value>
|
||||
<value>{0} έχει αντιγραφεί</value>
|
||||
<comment>Confirmation message after successfully copying a value to the clipboard.</comment>
|
||||
</data>
|
||||
<data name="VerifyFingerprint" xml:space="preserve">
|
||||
<value>Επαλήθευση Δακτυλικού Αποτυπώματος</value>
|
||||
<value>Επαλήθευση δακτυλικού αποτυπώματος</value>
|
||||
</data>
|
||||
<data name="VerifyMasterPassword" xml:space="preserve">
|
||||
<value>Επαλήθευση Κύριου Κωδικού</value>
|
||||
<value>Επαλήθευση κύριου κωδικού</value>
|
||||
</data>
|
||||
<data name="VerifyPIN" xml:space="preserve">
|
||||
<value>Επαλήθευση PIN</value>
|
||||
@@ -413,22 +413,22 @@
|
||||
<value>Προσθήκη στοιχείου</value>
|
||||
</data>
|
||||
<data name="AppExtension" xml:space="preserve">
|
||||
<value>Επέκταση Εφαρμογής</value>
|
||||
<value>Επέκταση εφαρμογής</value>
|
||||
</data>
|
||||
<data name="AutofillAccessibilityDescription" xml:space="preserve">
|
||||
<value>Χρησιμοποιείστε την υπηρεσία προσβασιμότητας Bitwarden, για την αυτόματη συμπλήρωση συνδέσεων σε εφαρμογές και στο διαδίκτυο.</value>
|
||||
</data>
|
||||
<data name="AutofillService" xml:space="preserve">
|
||||
<value>Υπηρεσία Αυτόματης Συμπλήρωσης</value>
|
||||
<value>Υπηρεσία αυτόματης συμπλήρωσης</value>
|
||||
</data>
|
||||
<data name="AvoidAmbiguousCharacters" xml:space="preserve">
|
||||
<value>Αποφυγή Αμφιλεγόμενων Χαρακτήρων</value>
|
||||
<value>Αποφυγή αμφιλεγόμενων χαρακτήρων</value>
|
||||
</data>
|
||||
<data name="BitwardenAppExtension" xml:space="preserve">
|
||||
<value>Επέκταση Εφαρμογής Bitwarden</value>
|
||||
<value>Επέκταση εφαρμογής Bitwarden</value>
|
||||
</data>
|
||||
<data name="BitwardenAppExtensionAlert2" xml:space="preserve">
|
||||
<value>Ο ευκολότερος τρόπος για να προσθέστε νέες συνδέσεις στο vault σας, είναι μέσω της επέκτασης εφαρμογής Bitwarden. Μάθετε περισσότερα σχετικά με τη χρήση της επέκτασης, μεταβαίνοντας στις "Ρυθμίσεις".</value>
|
||||
<value>Ο ευκολότερος τρόπος για να προσθέστε νέες συνδέσεις στο vault σας, είναι μέσω της επέκτασης εφαρμογής Bitwarden. Μάθετε περισσότερα σχετικά με τη χρήση της επέκτασης εφαρμογής Bitwarden, μεταβαίνοντας στις "Ρυθμίσεις".</value>
|
||||
</data>
|
||||
<data name="BitwardenAppExtensionDescription" xml:space="preserve">
|
||||
<value>Χρήση του Bitwarden στο Safari και σε άλλες εφαρμογές για αυτόματη συμπλήρωση των συνδέσεων σας.</value>
|
||||
@@ -440,13 +440,13 @@
|
||||
<value>Χρησιμοποιείστε την υπηρεσία προσβασιμότητας Bitwarden, για την αυτόματη συμπλήρωση συνδέσεων.</value>
|
||||
</data>
|
||||
<data name="ChangeEmail" xml:space="preserve">
|
||||
<value>Αλλαγή Email</value>
|
||||
<value>Αλλαγή email</value>
|
||||
</data>
|
||||
<data name="ChangeEmailConfirmation" xml:space="preserve">
|
||||
<value>Μπορείτε να αλλάξετε τη διεύθυνση του email σας στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;</value>
|
||||
</data>
|
||||
<data name="ChangeMasterPassword" xml:space="preserve">
|
||||
<value>Αλλαγή Κύριου Κωδικού</value>
|
||||
<value>Αλλαγή κύριου κωδικού</value>
|
||||
</data>
|
||||
<data name="Close" xml:space="preserve">
|
||||
<value>Κλείσιμο</value>
|
||||
@@ -455,29 +455,29 @@
|
||||
<value>Συνέχεια</value>
|
||||
</data>
|
||||
<data name="CreateAccount" xml:space="preserve">
|
||||
<value>Δημιουργία Λογαριασμού</value>
|
||||
<value>Δημιουργία λογαριασμού</value>
|
||||
</data>
|
||||
<data name="CreatingAccount" xml:space="preserve">
|
||||
<value>Δημιουργία λογαριασμού...</value>
|
||||
<comment>Message shown when interacting with the server</comment>
|
||||
</data>
|
||||
<data name="EditItem" xml:space="preserve">
|
||||
<value>Επεξεργασία Στοιχείου</value>
|
||||
<value>Επεξεργασία στοιχείου</value>
|
||||
</data>
|
||||
<data name="EnableAutomaticSyncing" xml:space="preserve">
|
||||
<value>Ενεργοποίηση Αυτόματου Συγχρονισμού</value>
|
||||
<value>Επιτρέψτε τον αυτόματο συγχρονισμό</value>
|
||||
</data>
|
||||
<data name="EnterEmailForHint" xml:space="preserve">
|
||||
<value>Εισάγετε την διεύθυνση email του λογαριασμού σας, προκειμένου για να λάβετε υπόδειξη του κύριου κωδικού.</value>
|
||||
</data>
|
||||
<data name="ExntesionReenable" xml:space="preserve">
|
||||
<value>Ενεργοποιείστε εκ νέου την Επέκτασης Εφαρμογής</value>
|
||||
<value>Εκ νέου ενεργοποίηση επέκτασης εφαρμογής</value>
|
||||
</data>
|
||||
<data name="ExtensionAlmostDone" xml:space="preserve">
|
||||
<value>Σχεδόν ολοκληρώθηκε!</value>
|
||||
</data>
|
||||
<data name="ExtensionEnable" xml:space="preserve">
|
||||
<value>Ενεργοποίηση Επέκτασης Εφαρμογής</value>
|
||||
<value>Ενεργοποίηση επέκτασης εφαρμογής</value>
|
||||
</data>
|
||||
<data name="ExtensionInSafari" xml:space="preserve">
|
||||
<value>Στο Safari, βρείτε το Bitwarden χρησιμοποιώντας το εικονίδιο κοινής χρήσης (μετακινηθείτε προς τα δεξιά στην κάτω σειρά του μενού).</value>
|
||||
@@ -508,13 +508,13 @@
|
||||
<value>Δακτυλικό αποτύπωμα</value>
|
||||
</data>
|
||||
<data name="GeneratePassword" xml:space="preserve">
|
||||
<value>Δημιουργία Κωδικού</value>
|
||||
<value>Παραγωγή κωδικού</value>
|
||||
</data>
|
||||
<data name="GetPasswordHint" xml:space="preserve">
|
||||
<value>Λήψη υπόδειξης κύριου κωδικού</value>
|
||||
</data>
|
||||
<data name="ImportItems" xml:space="preserve">
|
||||
<value>Εισαγωγή Στοιχείων</value>
|
||||
<value>Εισαγωγή στοιχείων</value>
|
||||
</data>
|
||||
<data name="ImportItemsConfirmation" xml:space="preserve">
|
||||
<value>Μπορείτε να εισαγάγετε μαζικά στοιχεία διαδικτυακά στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;</value>
|
||||
@@ -523,7 +523,7 @@
|
||||
<value>Γρήγορη μαζική εισαγωγή των στοιχείων σας από άλλες εφαρμογές διαχείρισης κωδικών.</value>
|
||||
</data>
|
||||
<data name="LastSync" xml:space="preserve">
|
||||
<value>Τελευταίος Συγχρονισμός:</value>
|
||||
<value>Τελευταίος συγχρονισμός:</value>
|
||||
</data>
|
||||
<data name="Length" xml:space="preserve">
|
||||
<value>Μήκος</value>
|
||||
@@ -547,10 +547,10 @@
|
||||
<value>Άμεσα</value>
|
||||
</data>
|
||||
<data name="VaultTimeout" xml:space="preserve">
|
||||
<value>Χρόνος Λήξης Vault</value>
|
||||
<value>Χρόνος λήξης vault</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutAction" xml:space="preserve">
|
||||
<value>Ενέργεια Χρόνου Λήξης Vault</value>
|
||||
<value>Ενέργεια χρόνου λήξης vault</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
|
||||
<value>Η αποσύνδεση θα καταργήσει όλη την πρόσβαση στο vault σας και απαιτεί online έλεγχο ταυτότητας μετά το χρονικό όριο λήξης. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτήν τη ρύθμιση;</value>
|
||||
@@ -572,7 +572,7 @@
|
||||
<value>Ο κύριος κωδικός χρησιμοποιείται για πρόσβαση στο vault σας. Είναι πολύ σημαντικό να μην τον ξεχάσετε. Δεν υπάρχει τρόπος να τον ανακτήσετε σε αυτή την περίπτωση.</value>
|
||||
</data>
|
||||
<data name="MasterPasswordHint" xml:space="preserve">
|
||||
<value>Υπόδειξη Κύριου Κωδικού (προαιρετικό)</value>
|
||||
<value>Υπόδειξη κύριου κωδικού (προαιρετικό)</value>
|
||||
</data>
|
||||
<data name="MasterPasswordHintDescription" xml:space="preserve">
|
||||
<value>Η υπόδειξη κύριου κωδικού μπορεί να σας βοηθήσει να θυμηθείτε τον κωδικό σας αν τον ξεχάσετε.</value>
|
||||
@@ -581,15 +581,15 @@
|
||||
<value>Ο κύριος κωδικός πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες.</value>
|
||||
</data>
|
||||
<data name="MinNumbers" xml:space="preserve">
|
||||
<value>Ελάχιστα Αριθμητικά Ψηφία</value>
|
||||
<value>Ελάχιστα αριθμητικά ψηφία</value>
|
||||
<comment>Minimum numeric characters for password generator settings</comment>
|
||||
</data>
|
||||
<data name="MinSpecial" xml:space="preserve">
|
||||
<value>Ελάχιστο Ειδικών Χαρακτήρων</value>
|
||||
<value>Ελάχιστοι ειδικοί χαρακτήρες</value>
|
||||
<comment>Minimum special characters for password generator settings</comment>
|
||||
</data>
|
||||
<data name="MoreSettings" xml:space="preserve">
|
||||
<value>Περισσότερες Ρυθμίσεις</value>
|
||||
<value>Περισσότερες ρυθμίσεις</value>
|
||||
</data>
|
||||
<data name="MustLogInMainApp" xml:space="preserve">
|
||||
<value>Πρέπει να συνδεθείτε στην κύρια εφαρμογή Bitwarden για να μπορέσετε να χρησιμοποιήσετε την επέκταση.</value>
|
||||
@@ -598,7 +598,7 @@
|
||||
<value>Ποτέ</value>
|
||||
</data>
|
||||
<data name="NewItemCreated" xml:space="preserve">
|
||||
<value>Δημιουργήθηκε νέο στοιχείο.</value>
|
||||
<value>Το στοιχείο προστέθηκε</value>
|
||||
</data>
|
||||
<data name="NoFavorites" xml:space="preserve">
|
||||
<value>Δεν υπάρχουν αγαπημένα στο vault σας.</value>
|
||||
@@ -626,13 +626,13 @@
|
||||
<value>Άλλες</value>
|
||||
</data>
|
||||
<data name="PasswordGenerated" xml:space="preserve">
|
||||
<value>Ο κωδικός δημιουργήθηκε.</value>
|
||||
<value>Ο κωδικός δημιουργήθηκε</value>
|
||||
</data>
|
||||
<data name="PasswordGenerator" xml:space="preserve">
|
||||
<value>Γεννήτρια Κωδικού</value>
|
||||
<value>Γεννήτρια κωδικού</value>
|
||||
</data>
|
||||
<data name="PasswordHint" xml:space="preserve">
|
||||
<value>Υπόδειξη Κωδικού</value>
|
||||
<value>Υπόδειξη κωδικού</value>
|
||||
</data>
|
||||
<data name="PasswordHintAlert" xml:space="preserve">
|
||||
<value>Σας στείλαμε ένα email με υπόδειξη του κύριου κωδικού.</value>
|
||||
@@ -641,20 +641,20 @@
|
||||
<value>Είστε βέβαιοι ότι θέλετε να αντικαταστήσετε τον τρέχον κωδικό;</value>
|
||||
</data>
|
||||
<data name="PushNotificationAlert" xml:space="preserve">
|
||||
<value>Το Bitwarden κρατάει αυτόματα το vault σας, με τη χρήση ειδοποιήσεων push. Για την καλύτερη δυνατή εμπειρία, επιλέξτε "Να επιτρέπεται" στην παρακάτω ερώτηση, όταν σας ζητηθεί να ενεργοποιήσετε τις ειδοποιήσεις push.</value>
|
||||
<value>Το Bitwarden κρατάει συγχρονισμένο το vault σας αυτόματα, με τη χρήση ειδοποιήσεων push. Για την καλύτερη δυνατή εμπειρία, επιλέξτε "Να επιτρέπεται" στην παρακάτω προτροπή όταν σας ζητηθεί να ενεργοποιήσετε τις ειδοποιήσεις push.</value>
|
||||
<comment>Push notifications for apple products</comment>
|
||||
</data>
|
||||
<data name="RateTheApp" xml:space="preserve">
|
||||
<value>Αξιολογήστε την Εφαρμογή</value>
|
||||
<value>Αξιολογήστε την εφαρμογή</value>
|
||||
</data>
|
||||
<data name="RateTheAppDescription" xml:space="preserve">
|
||||
<value>Παρακαλούμε σκεφτείτε να μας βοηθήσετε με μια καλή κριτική!</value>
|
||||
</data>
|
||||
<data name="RegeneratePassword" xml:space="preserve">
|
||||
<value>Επαναδημιουργία Κωδικού</value>
|
||||
<value>Επαναδημιουργία κωδικού</value>
|
||||
</data>
|
||||
<data name="RetypeMasterPassword" xml:space="preserve">
|
||||
<value>Εισάγετε Ξανά τον Κύριο Κωδικό</value>
|
||||
<value>Εισάγετε ξανά τον κύριο κωδικό</value>
|
||||
</data>
|
||||
<data name="SearchVault" xml:space="preserve">
|
||||
<value>Αναζήτηση στο vault</value>
|
||||
@@ -672,10 +672,10 @@
|
||||
<value>Εισαγωγή τετραψήφιου αριθμού PIN για ξεκλείδωμα εφαρμογής.</value>
|
||||
</data>
|
||||
<data name="ItemInformation" xml:space="preserve">
|
||||
<value>Πληροφορίες Στοιχείου</value>
|
||||
<value>Πληροφορίες στοιχείου</value>
|
||||
</data>
|
||||
<data name="ItemUpdated" xml:space="preserve">
|
||||
<value>Το στοιχείο ενημερώθηκε.</value>
|
||||
<value>Το στοιχείο αποθηκεύτηκε</value>
|
||||
</data>
|
||||
<data name="Submitting" xml:space="preserve">
|
||||
<value>Υποβολή...</value>
|
||||
@@ -686,39 +686,39 @@
|
||||
<comment>Message shown when interacting with the server</comment>
|
||||
</data>
|
||||
<data name="SyncingComplete" xml:space="preserve">
|
||||
<value>Ο συγχρονισμός ολοκληρώθηκε.</value>
|
||||
<value>Ο συγχρονισμός ολοκληρώθηκε</value>
|
||||
</data>
|
||||
<data name="SyncingFailed" xml:space="preserve">
|
||||
<value>Ο συγχρονισμός απέτυχε.</value>
|
||||
<value>Ο συγχρονισμός απέτυχε</value>
|
||||
</data>
|
||||
<data name="SyncVaultNow" xml:space="preserve">
|
||||
<value>Συγχρονισμός Vault Τώρα</value>
|
||||
<value>Συγχρονισμός του vault τώρα</value>
|
||||
</data>
|
||||
<data name="TouchID" xml:space="preserve">
|
||||
<value>Touch ID</value>
|
||||
<comment>What Apple calls their fingerprint reader.</comment>
|
||||
</data>
|
||||
<data name="TwoStepLogin" xml:space="preserve">
|
||||
<value>Σύνδεση σε δύο βήματα</value>
|
||||
<value>Σύνδεση δύο βημάτων</value>
|
||||
</data>
|
||||
<data name="UnlockWith" xml:space="preserve">
|
||||
<value>Ξεκλείδωμα με {0}</value>
|
||||
</data>
|
||||
<data name="UnlockWithPIN" xml:space="preserve">
|
||||
<value>Ξεκλείδωμα με PIN</value>
|
||||
<value>Ξεκλείδωμα με κωδικό PIN</value>
|
||||
</data>
|
||||
<data name="Validating" xml:space="preserve">
|
||||
<value>Επαλήθευση</value>
|
||||
<comment>Message shown when interacting with the server</comment>
|
||||
</data>
|
||||
<data name="VerificationCode" xml:space="preserve">
|
||||
<value>Κωδικός Επαλήθευσης</value>
|
||||
<value>Κωδικός επαλήθευσης</value>
|
||||
</data>
|
||||
<data name="ViewItem" xml:space="preserve">
|
||||
<value>Προβολή Στοιχείου</value>
|
||||
<value>Προβολή στοιχείου</value>
|
||||
</data>
|
||||
<data name="WebVault" xml:space="preserve">
|
||||
<value>Bitwarden Web Vault</value>
|
||||
<value>Διαδικτυακό vault Bitwarden</value>
|
||||
</data>
|
||||
<data name="Lost2FAApp" xml:space="preserve">
|
||||
<value>Χάσατε την εφαρμογή επαλήθευσης;</value>
|
||||
@@ -728,7 +728,7 @@
|
||||
<comment>Screen title</comment>
|
||||
</data>
|
||||
<data name="ExtensionActivated" xml:space="preserve">
|
||||
<value>Η Επέκταση Ενεργοποιήθηκε!</value>
|
||||
<value>Η επέκταση ενεργοποιήθηκε!</value>
|
||||
</data>
|
||||
<data name="Icons" xml:space="preserve">
|
||||
<value>Εικονίδια</value>
|
||||
@@ -799,14 +799,14 @@
|
||||
<value>Αναζήτηση στοιχείου αυτόματης συμπλήρωσης για "{0}".</value>
|
||||
</data>
|
||||
<data name="LearnOrg" xml:space="preserve">
|
||||
<value>Μάθετε για τους Οργανισμούς</value>
|
||||
<value>Μάθετε για τους οργανισμούς</value>
|
||||
</data>
|
||||
<data name="CannotOpenApp" xml:space="preserve">
|
||||
<value>Δεν είναι δυνατή η πρόσβαση στην εφαρμογή "{0}".</value>
|
||||
<comment>Message shown when trying to launch an app that does not exist on the user's device.</comment>
|
||||
</data>
|
||||
<data name="AuthenticatorAppTitle" xml:space="preserve">
|
||||
<value>Εφαρμογή Επαλήθευσης</value>
|
||||
<value>Εφαρμογή αυθεντικοποίησης</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="EnterVerificationCodeApp" xml:space="preserve">
|
||||
@@ -818,14 +818,14 @@
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="LoginUnavailable" xml:space="preserve">
|
||||
<value>Μη Διαθέσιμη Σύνδεση</value>
|
||||
<value>Μη διαθέσιμη σύνδεση</value>
|
||||
<comment>For 2FA whenever there are no available providers on this device.</comment>
|
||||
</data>
|
||||
<data name="NoTwoStepAvailable" xml:space="preserve">
|
||||
<value>Αυτός ο λογαριασμός έχει ενεργοποιημένη τη σύνδεση σε δύο βήματα, ωστόσο, σε αυτήν τη συσκευή δεν υποστηρίζεται κανένας από τους διαμορφωμένους παροχείς δύο βημάτων. Χρησιμοποιήστε μια υποστηριζόμενη συσκευή και / ή προσθέστε επιπλέον παρόχους που υποστηρίζονται καλύτερα σε όλες τις συσκευές (όπως μια εφαρμογή επαλήθευσης).</value>
|
||||
<value>Αυτός ο λογαριασμός έχει ενεργοποιημένη τη σύνδεση δύο βημάτων, ωστόσο, σε αυτήν τη συσκευή δεν υποστηρίζεται κανένας από τους διαμορφωμένους παρόχους δύο βημάτων. Παρακαλώ χρησιμοποιήστε μια υποστηριζόμενη συσκευή και/ή προσθέστε επιπλέον παρόχους που υποστηρίζονται καλύτερα σε όλες τις συσκευές (όπως μια εφαρμογή αυθεντικοποίησης).</value>
|
||||
</data>
|
||||
<data name="RecoveryCodeTitle" xml:space="preserve">
|
||||
<value>Κωδικός Ανάκτησης</value>
|
||||
<value>Κωδικός ανάκτησης</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="RememberMe" xml:space="preserve">
|
||||
@@ -837,7 +837,7 @@
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="TwoStepLoginOptions" xml:space="preserve">
|
||||
<value>Επιλογές σύνδεσης δύο παραγόντων</value>
|
||||
<value>Επιλογές σύνδεσης δύο βημάτων</value>
|
||||
</data>
|
||||
<data name="UseAnotherTwoStepMethod" xml:space="preserve">
|
||||
<value>Χρήση άλλης μεθόδου δύο παραγόντων</value>
|
||||
@@ -847,18 +847,18 @@
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="VerificationEmailSent" xml:space="preserve">
|
||||
<value>Το email επιβεβαίωσης στάλθηκε.</value>
|
||||
<value>Το email επιβεβαίωσης στάλθηκε</value>
|
||||
<comment>For 2FA</comment>
|
||||
</data>
|
||||
<data name="YubiKeyInstruction" xml:space="preserve">
|
||||
<value>Για να συνεχίσετε, κρατήστε το YubiKey NEO στο πίσω μέρος της συσκευής ή τοποθετήστε το YubiKey στη θύρα USB της συσκευής σας, και στη συνέχεια πατήστε το κουμπί του.</value>
|
||||
</data>
|
||||
<data name="YubiKeyTitle" xml:space="preserve">
|
||||
<value>Κλειδί Ασφαλείας YubiKey</value>
|
||||
<value>Κλειδί ασφαλείας YubiKey</value>
|
||||
<comment>"YubiKey" is the product name and should not be translated.</comment>
|
||||
</data>
|
||||
<data name="AddNewAttachment" xml:space="preserve">
|
||||
<value>Προσθήκη Νέου Συνημμένου</value>
|
||||
<value>Προσθήκη συνημμένου</value>
|
||||
</data>
|
||||
<data name="Attachments" xml:space="preserve">
|
||||
<value>Συνημμένα</value>
|
||||
@@ -878,10 +878,10 @@
|
||||
<comment>The placeholder will show the file size of the attachment. Ex "25 MB"</comment>
|
||||
</data>
|
||||
<data name="AuthenticatorKey" xml:space="preserve">
|
||||
<value>Κλειδί επαλήθευσης (TOTP)</value>
|
||||
<value>Κλειδί αυθεντικοποίησης (TOTP)</value>
|
||||
</data>
|
||||
<data name="VerificationCodeTotp" xml:space="preserve">
|
||||
<value>Κωδικός Επαλήθευσης (TOTP)</value>
|
||||
<value>Κωδικός επαλήθευσης (TOTP)</value>
|
||||
<comment>Totp code label</comment>
|
||||
</data>
|
||||
<data name="AuthenticatorKeyAdded" xml:space="preserve">
|
||||
@@ -891,7 +891,8 @@
|
||||
<value>Αδυναμία ανάγνωσης κλειδιού επαλήθευσης.</value>
|
||||
</data>
|
||||
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
|
||||
<value>Σημαδέψτε τον κωδικό QR με τη κάμερα. Η σάρωση θα γίνει αυτόματα.</value>
|
||||
<value>Σημαδέψτε με την κάμερα σας τον κωδικό QR.
|
||||
Η σάρωση θα γίνει αυτόματα.</value>
|
||||
</data>
|
||||
<data name="ScanQrTitle" xml:space="preserve">
|
||||
<value>Σάρωση κώδικα QR</value>
|
||||
@@ -906,7 +907,7 @@
|
||||
<value>Αντιγραφή TOTP</value>
|
||||
</data>
|
||||
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
|
||||
<value>Εάν η σύνδεση έχει κάποιο κλειδί επαλήθευσης, αντιγράψτε τον κωδικό επαλήθευσης TOTP στο πρόχειρο κάθε φορά που κάνετε αυτόματη συμπλήρωση τα στοιχεία σύνδεσης.</value>
|
||||
<value>Εάν η σύνδεση έχει κάποιο κλειδί αυθεντικοποίησης, αντιγράψτε τον κωδικό επαλήθευσης TOTP στο πρόχειρο σας όταν κάνετε με αυτόματη συμπλήρωση τη συνδεση.</value>
|
||||
</data>
|
||||
<data name="CopyTotpAutomatically" xml:space="preserve">
|
||||
<value>Αυτόματη αντιγραφή TOTP</value>
|
||||
@@ -921,7 +922,7 @@
|
||||
<value>Το συνημμένο διαγράφηκε</value>
|
||||
</data>
|
||||
<data name="ChooseFile" xml:space="preserve">
|
||||
<value>Επιλογή Αρχείου</value>
|
||||
<value>Επιλογή αρχείου</value>
|
||||
</data>
|
||||
<data name="File" xml:space="preserve">
|
||||
<value>Αρχείο</value>
|
||||
@@ -945,16 +946,16 @@
|
||||
<value>Δεν μπορείτε να χρησιμοποιήσετε αυτήν τη δυνατότητα μέχρι να ενημερώσετε το κλειδί κρυπτογράφησης.</value>
|
||||
</data>
|
||||
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
|
||||
<value>Απαιτείται μεταφορά του κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του web vault για να ενημερώσετε το κλειδί κρυπτογράφησης.</value>
|
||||
<value>Απαιτείται μεταφορά του κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού vault για να ενημερώσετε το κλειδί κρυπτογράφησης.</value>
|
||||
</data>
|
||||
<data name="LearnMore" xml:space="preserve">
|
||||
<value>Μάθετε Περισσότερα</value>
|
||||
<value>Μάθετε περισσότερα</value>
|
||||
</data>
|
||||
<data name="ApiUrl" xml:space="preserve">
|
||||
<value>URL Διακομιστή API</value>
|
||||
<value>URL διακομιστή API</value>
|
||||
</data>
|
||||
<data name="CustomEnvironment" xml:space="preserve">
|
||||
<value>Προσαρμοσμένο Περιβάλλον</value>
|
||||
<value>Προσαρμοσμένο περιβάλλον</value>
|
||||
</data>
|
||||
<data name="CustomEnvironmentFooter" xml:space="preserve">
|
||||
<value>Για προχωρημένους χρήστες. Μπορείτε να ορίσετε τo URL κάθε υπηρεσίας ανεξάρτητα.</value>
|
||||
@@ -967,7 +968,7 @@
|
||||
<comment>Validation error when something is not formatted correctly, such as a URL or email address.</comment>
|
||||
</data>
|
||||
<data name="IdentityUrl" xml:space="preserve">
|
||||
<value>URL Ταυτότητας Διακομιστή</value>
|
||||
<value>URL διακομιστή ταυτότητας</value>
|
||||
<comment>"Identity" refers to an identity server. See more context here https://en.wikipedia.org/wiki/Identity_management</comment>
|
||||
</data>
|
||||
<data name="SelfHostedEnvironment" xml:space="preserve">
|
||||
@@ -980,25 +981,25 @@
|
||||
<value>URL Διακομιστή</value>
|
||||
</data>
|
||||
<data name="WebVaultUrl" xml:space="preserve">
|
||||
<value>URL Διακομιστή Web Vault</value>
|
||||
<value>URL διακομιστή διαδικτυακού vault</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceNotificationContentOld" xml:space="preserve">
|
||||
<value>Πατήστε στην ειδοποίηση για να συμπληρώσετε αυτόματα ένα στοιχείο από τη λίστα σας.</value>
|
||||
</data>
|
||||
<data name="CustomFields" xml:space="preserve">
|
||||
<value>Προσαρμοσμένα Πεδία</value>
|
||||
<value>Προσαρμοσμένα πεδία</value>
|
||||
</data>
|
||||
<data name="CopyNumber" xml:space="preserve">
|
||||
<value>Αντιγραφή Αριθμού</value>
|
||||
<value>Αντιγραφή αριθμού</value>
|
||||
</data>
|
||||
<data name="CopySecurityCode" xml:space="preserve">
|
||||
<value>Αντιγραφή Κωδικού Ασφαλείας</value>
|
||||
<value>Αντιγραφή κωδικού ασφαλείας</value>
|
||||
</data>
|
||||
<data name="Number" xml:space="preserve">
|
||||
<value>Αριθμός</value>
|
||||
</data>
|
||||
<data name="SecurityCode" xml:space="preserve">
|
||||
<value>Κωδικός Ασφαλείας</value>
|
||||
<value>Κωδικός ασφαλείας</value>
|
||||
</data>
|
||||
<data name="TypeCard" xml:space="preserve">
|
||||
<value>Κάρτα</value>
|
||||
@@ -1010,7 +1011,7 @@
|
||||
<value>Σύνδεση</value>
|
||||
</data>
|
||||
<data name="TypeSecureNote" xml:space="preserve">
|
||||
<value>Ασφαλής Σημείωση</value>
|
||||
<value>Ασφαλής σημείωση</value>
|
||||
</data>
|
||||
<data name="Address1" xml:space="preserve">
|
||||
<value>Διεύθυνση 1</value>
|
||||
@@ -1049,10 +1050,10 @@
|
||||
<value>Dr</value>
|
||||
</data>
|
||||
<data name="ExpirationMonth" xml:space="preserve">
|
||||
<value>Μήνας Λήξης</value>
|
||||
<value>Μήνας λήξης</value>
|
||||
</data>
|
||||
<data name="ExpirationYear" xml:space="preserve">
|
||||
<value>Έτος Λήξης</value>
|
||||
<value>Έτος λήξης</value>
|
||||
</data>
|
||||
<data name="February" xml:space="preserve">
|
||||
<value>Φεβρουάριος</value>
|
||||
@@ -1076,7 +1077,7 @@
|
||||
<value>Ονοματεπώνυμο</value>
|
||||
</data>
|
||||
<data name="LicenseNumber" xml:space="preserve">
|
||||
<value>Αριθμός Άδειας</value>
|
||||
<value>Αριθμός άδειας</value>
|
||||
</data>
|
||||
<data name="March" xml:space="preserve">
|
||||
<value>Μάρτιος</value>
|
||||
@@ -1085,7 +1086,7 @@
|
||||
<value>Μάιος</value>
|
||||
</data>
|
||||
<data name="MiddleName" xml:space="preserve">
|
||||
<value>Μεσαίο Όνομα</value>
|
||||
<value>Μεσαίο όνομα</value>
|
||||
</data>
|
||||
<data name="Mr" xml:space="preserve">
|
||||
<value>Κος</value>
|
||||
@@ -1106,7 +1107,7 @@
|
||||
<value>Οκτώβριος</value>
|
||||
</data>
|
||||
<data name="PassportNumber" xml:space="preserve">
|
||||
<value>Αριθμός Διαβατηρίου</value>
|
||||
<value>Αριθμός διαβατηρίου</value>
|
||||
</data>
|
||||
<data name="Phone" xml:space="preserve">
|
||||
<value>Τηλέφωνο</value>
|
||||
@@ -1124,7 +1125,7 @@
|
||||
<value>Τίτλος</value>
|
||||
</data>
|
||||
<data name="ZipPostalCode" xml:space="preserve">
|
||||
<value>Ταχυδρομικός Κώδικας</value>
|
||||
<value>Ταχυδρομικός κώδικας</value>
|
||||
</data>
|
||||
<data name="Address" xml:space="preserve">
|
||||
<value>Διεύθυνση</value>
|
||||
@@ -1139,7 +1140,7 @@
|
||||
<value>Εμφάνιση μιας αναγνωρίσιμης εικόνας δίπλα σε κάθε σύνδεση.</value>
|
||||
</data>
|
||||
<data name="IconsUrl" xml:space="preserve">
|
||||
<value>Εικονίδια Διακομιστή URL</value>
|
||||
<value>URL διακομιστή εικονιδίων</value>
|
||||
</data>
|
||||
<data name="AutofillWithBitwarden" xml:space="preserve">
|
||||
<value>Αυτόματη συμπλήρωση με Bitwarden</value>
|
||||
@@ -1194,7 +1195,7 @@
|
||||
<value>Δεν ήταν δυνατό να ανοίξουμε αυτόματα το μενού ρυθμίσεων αυτόματης συμπλήρωσης Android για εσάς. Μπορείτε να πλοηγηθείτε στο μενού ρυθμίσεων αυτόματης συμπλήρωσης με μη αυτόματο τρόπο από τις Ρυθμίσεις Android > Σύστημα > Γλώσσες και εισαγωγή > Σύνθετες > Υπηρεσία αυτόματης συμπλήρωσης.</value>
|
||||
</data>
|
||||
<data name="CustomFieldName" xml:space="preserve">
|
||||
<value>Όνομα Προσαρμοσμένου Πεδίου</value>
|
||||
<value>Όνομα προσαρμοσμένου πεδίου</value>
|
||||
</data>
|
||||
<data name="FieldTypeBoolean" xml:space="preserve">
|
||||
<value>Δυαδικό</value>
|
||||
@@ -1209,7 +1210,7 @@
|
||||
<value>Κείμενο</value>
|
||||
</data>
|
||||
<data name="NewCustomField" xml:space="preserve">
|
||||
<value>Νέο Προσαρμοσμένο Πεδίο</value>
|
||||
<value>Νέο προσαρμοσμένο πεδίο</value>
|
||||
</data>
|
||||
<data name="SelectTypeField" xml:space="preserve">
|
||||
<value>Τι τύπος προσαρμοσμένου πεδίου είναι αυτό που θέλετε να προσθέσετε;</value>
|
||||
@@ -1248,11 +1249,11 @@
|
||||
<value>Εντοπισμός Αντιστοίχισης URI</value>
|
||||
</data>
|
||||
<data name="MatchDetection" xml:space="preserve">
|
||||
<value>Εντοπισμός Αντιστοίχισης</value>
|
||||
<value>Εντοπισμός αντιστοίχισης</value>
|
||||
<comment>URI match detection for auto-fill.</comment>
|
||||
</data>
|
||||
<data name="YesAndSave" xml:space="preserve">
|
||||
<value>Αποδοχή και Αποθήκευση</value>
|
||||
<value>Ναι, και αποθήκευση</value>
|
||||
</data>
|
||||
<data name="AutofillAndSave" xml:space="preserve">
|
||||
<value>Αυτόματη συμπλήρωση και αποθήκευση</value>
|
||||
@@ -1274,7 +1275,7 @@
|
||||
<value>Η υπηρεσία προσβασιμότητας μπορεί να είναι χρήσιμη όταν οι εφαρμογές δεν υποστηρίζουν την τυπική υπηρεσία αυτόματης συμπλήρωσης.</value>
|
||||
</data>
|
||||
<data name="DatePasswordUpdated" xml:space="preserve">
|
||||
<value>Ο Κωδικός Ενημερώθηκε</value>
|
||||
<value>Ο κωδικός ενημερώθηκε</value>
|
||||
<comment>ex. Date this password was updated</comment>
|
||||
</data>
|
||||
<data name="DateUpdated" xml:space="preserve">
|
||||
@@ -1297,7 +1298,7 @@
|
||||
<value>Αποκτήστε πρόσβαση στη λίστα σας απευθείας από το πληκτρολόγιό σας για γρήγορη αυτόματη συμπλήρωση κωδικών.</value>
|
||||
</data>
|
||||
<data name="AutofillTurnOn" xml:space="preserve">
|
||||
<value>Για να ενεργοποιήσετε την αυτόματη συμπλήρωση του κωδικού στη συσκευή σας, ακολουθήστε αυτές τις οδηγίες:</value>
|
||||
<value>Για να ενεργοποιήσετε την αυτόματη συμπλήρωση κωδικού στη συσκευή σας, ακολουθήστε αυτές τις οδηγίες:</value>
|
||||
</data>
|
||||
<data name="AutofillTurnOn1" xml:space="preserve">
|
||||
<value>1. Μεταβείτε στις "Ρυθμίσεις" του iOS</value>
|
||||
@@ -1315,7 +1316,7 @@
|
||||
<value>5. Επιλέξτε "Bitwarden"</value>
|
||||
</data>
|
||||
<data name="PasswordAutofill" xml:space="preserve">
|
||||
<value>Αυτόματη Συμπλήρωση Κωδικού</value>
|
||||
<value>Αυτόματη συμπλήρωση κωδικού</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillAlert2" xml:space="preserve">
|
||||
<value>Ο ευκολότερος τρόπος για να προσθέσετε νέες συνδέσεις στο vault σας, είναι με την επέκταση αυτόματης συμπλήρωσης κωδικών Bitwarden. Μάθετε περισσότερα σχετικά με τη χρήση της επέκτασης αυτής, μεταβαίνοντας στις "Ρυθμίσεις".</value>
|
||||
@@ -1333,10 +1334,10 @@
|
||||
<value>Συνδέσεις</value>
|
||||
</data>
|
||||
<data name="SecureNotes" xml:space="preserve">
|
||||
<value>Ασφαλείς Σημειώσεις</value>
|
||||
<value>Ασφαλείς σημειώσεις</value>
|
||||
</data>
|
||||
<data name="AllItems" xml:space="preserve">
|
||||
<value>Όλα τα Στοιχεία</value>
|
||||
<value>Όλα τα στοιχεία</value>
|
||||
</data>
|
||||
<data name="URIs" xml:space="preserve">
|
||||
<value>URIs</value>
|
||||
@@ -1356,13 +1357,13 @@
|
||||
<value>Ο κωδικός αυτός δεν βρέθηκε σε γνωστές διαρροές δεδομένων. Είναι ασφαλής για χρήση.</value>
|
||||
</data>
|
||||
<data name="IdentityName" xml:space="preserve">
|
||||
<value>Όνομα Ταυτότητας</value>
|
||||
<value>Όνομα ταυτότητας</value>
|
||||
</data>
|
||||
<data name="Value" xml:space="preserve">
|
||||
<value>Τιμή</value>
|
||||
</data>
|
||||
<data name="PasswordHistory" xml:space="preserve">
|
||||
<value>Ιστορικό Κωδικού</value>
|
||||
<value>Ιστορικό κωδικού</value>
|
||||
</data>
|
||||
<data name="Types" xml:space="preserve">
|
||||
<value>Τύποι</value>
|
||||
@@ -1433,13 +1434,13 @@
|
||||
<value>Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτό το στοιχείο. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε.</value>
|
||||
</data>
|
||||
<data name="NumberOfWords" xml:space="preserve">
|
||||
<value>Αριθμός Λέξεων</value>
|
||||
<value>Αριθμός λέξεων</value>
|
||||
</data>
|
||||
<data name="Passphrase" xml:space="preserve">
|
||||
<value>Συνθηματικό</value>
|
||||
</data>
|
||||
<data name="WordSeparator" xml:space="preserve">
|
||||
<value>Διαχωριστής Λέξεων</value>
|
||||
<value>Διαχωριστής λέξεων</value>
|
||||
</data>
|
||||
<data name="Clear" xml:space="preserve">
|
||||
<value>Εκκαθάριση</value>
|
||||
@@ -1453,7 +1454,7 @@
|
||||
<value>Δεν υπάρχουν φάκελοι προς εμφάνιση.</value>
|
||||
</data>
|
||||
<data name="FingerprintPhrase" xml:space="preserve">
|
||||
<value>Φράση Δακτυλικών Αποτυπωμάτων</value>
|
||||
<value>Φράση δακτυλικών αποτυπωμάτων</value>
|
||||
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
|
||||
</data>
|
||||
<data name="YourAccountsFingerprint" xml:space="preserve">
|
||||
@@ -1464,10 +1465,10 @@
|
||||
<value>Το Bitwarden επιτρέπει να μοιράζεστε τα στοιχεία του vault σας με άλλους χρησιμοποιώντας ένα λογαριασμό οργανισμού. Θέλετε να επισκεφθείτε την ιστοσελίδα bitwarden.com για να μάθετε περισσότερα;</value>
|
||||
</data>
|
||||
<data name="ExportVault" xml:space="preserve">
|
||||
<value>Εξαγωγή Vault</value>
|
||||
<value>Εξαγωγή του vault</value>
|
||||
</data>
|
||||
<data name="LockNow" xml:space="preserve">
|
||||
<value>Κλείδωμα Τώρα</value>
|
||||
<value>Κλείδωμα τώρα</value>
|
||||
</data>
|
||||
<data name="PIN" xml:space="preserve">
|
||||
<value>PIN</value>
|
||||
@@ -1476,7 +1477,7 @@
|
||||
<value>Ξεκλείδωμα</value>
|
||||
</data>
|
||||
<data name="UnlockVault" xml:space="preserve">
|
||||
<value>Ξεκλείδωμα Vault</value>
|
||||
<value>Ξεκλείδωμα του vault</value>
|
||||
</data>
|
||||
<data name="ThirtyMinutes" xml:space="preserve">
|
||||
<value>30 λεπτά</value>
|
||||
@@ -1521,7 +1522,7 @@
|
||||
<value>2 λεπτά</value>
|
||||
</data>
|
||||
<data name="ClearClipboard" xml:space="preserve">
|
||||
<value>Εκκαθάριση Πρόχειρου</value>
|
||||
<value>Εκκαθάριση πρόχειρου</value>
|
||||
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
||||
</data>
|
||||
<data name="ClearClipboardDescription" xml:space="preserve">
|
||||
@@ -1549,7 +1550,7 @@
|
||||
<value>Προεπιλεγμένο σκοτεινό θέμα</value>
|
||||
</data>
|
||||
<data name="CopyNotes" xml:space="preserve">
|
||||
<value>Αντιγραφή Σημειώσεων</value>
|
||||
<value>Αντιγραφή σημείωσης</value>
|
||||
</data>
|
||||
<data name="Exit" xml:space="preserve">
|
||||
<value>Έξοδος</value>
|
||||
@@ -1573,19 +1574,19 @@
|
||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||
<value>Αυτόματη συμπλήρωση μπλοκαρισμένων URIs</value>
|
||||
<value>Αυτόματη συμπλήρωση μπλοκαρισμένων URI</value>
|
||||
</data>
|
||||
<data name="AskToAddLogin" xml:space="preserve">
|
||||
<value>Ζητήστε να προσθέστε σύνδεση</value>
|
||||
<value>Ρωτήστε για να προσθέστε σύνδεση</value>
|
||||
</data>
|
||||
<data name="AskToAddLoginDescription" xml:space="preserve">
|
||||
<value>Ζητήστε να προσθέσετε ένα αντικείμενο αν δε βρεθεί στην κρύπτη σας.</value>
|
||||
<value>Ρωτήστε για να προσθέσετε ένα αντικείμενο αν δε βρεθεί στο vault σας.</value>
|
||||
</data>
|
||||
<data name="OnRestart" xml:space="preserve">
|
||||
<value>Κατά την Επανεκκίνηση Εφαρμογής</value>
|
||||
<value>Κατά την επανεκκίνηση της εφαρμογής</value>
|
||||
</data>
|
||||
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
||||
<value>Η αυτόματη συμπλήρωση διευκολύνει την ασφαλή πρόσβαση στη λίστα από άλλες ιστοσελίδες και εφαρμογές. Φαίνεται ότι δεν έχετε ενεργοποιήσει την υπηρεσία αυτόματης συμπλήρωσης για το Bitwarden. Ενεργοποιήστε την από τις "Ρυθμίσεις".</value>
|
||||
<value>Η αυτόματη συμπλήρωση διευκολύνει την ασφαλή πρόσβαση στο vault του Bitwarden από άλλες ιστοσελίδες και εφαρμογές. Φαίνεται ότι δεν έχετε ενεργοποιήσει την υπηρεσία αυτόματης συμπλήρωσης για το Bitwarden. Ενεργοποιήστε την αυτόματη συμπλήρωση από τις "Ρυθμίσεις".</value>
|
||||
</data>
|
||||
<data name="ThemeAppliedOnRestart" xml:space="preserve">
|
||||
<value>Οι αλλαγές θεμάτων θα ισχύουν όταν γίνει επανεκκίνηση της εφαρμογής.</value>
|
||||
@@ -1595,7 +1596,7 @@
|
||||
<comment>ex. Uppercase the first character of a word.</comment>
|
||||
</data>
|
||||
<data name="IncludeNumber" xml:space="preserve">
|
||||
<value>Συμπερίληψη Αριθμών</value>
|
||||
<value>Συμπερίληψη αριθμών</value>
|
||||
</data>
|
||||
<data name="Download" xml:space="preserve">
|
||||
<value>Λήψη</value>
|
||||
@@ -1610,13 +1611,13 @@
|
||||
<value>Η περίοδος σύνδεσης σας έχει λήξει.</value>
|
||||
</data>
|
||||
<data name="BiometricsDirection" xml:space="preserve">
|
||||
<value>Χρήση βιομετρικής μεθόδου για επαλήθευση.</value>
|
||||
<value>Βιομετρική επαλήθευση</value>
|
||||
</data>
|
||||
<data name="Biometrics" xml:space="preserve">
|
||||
<value>Βιομετρική</value>
|
||||
</data>
|
||||
<data name="UseBiometricsToUnlock" xml:space="preserve">
|
||||
<value>Χρήση βιομετρικής μεθόδου για ξεκλείδωμα</value>
|
||||
<value>Χρήση βιομετρικών για ξεκλείδωμα</value>
|
||||
</data>
|
||||
<data name="AccessibilityOverlayPermissionAlert" xml:space="preserve">
|
||||
<value>Το Bitwarden χρειάζεται προσοχή - Ανατρέξτε στην ενότητα "Υπηρεσία προσβασιμότητας αυτόματης συμπλήρωσης" από τις ρυθμίσεις Bitwarden</value>
|
||||
@@ -1640,7 +1641,7 @@
|
||||
<value>Χορηγήθηκε</value>
|
||||
</data>
|
||||
<data name="FileFormat" xml:space="preserve">
|
||||
<value>Μορφή Αρχείου</value>
|
||||
<value>Μορφή αρχείου</value>
|
||||
</data>
|
||||
<data name="ExportVaultMasterPasswordDescription" xml:space="preserve">
|
||||
<value>Εισαγάγετε τον κύριο κωδικό για εξαγωγή των δεδομένων vault.</value>
|
||||
@@ -1649,7 +1650,7 @@
|
||||
<value>Στείλτε έναν κωδικό επαλήθευσης στο email σας</value>
|
||||
</data>
|
||||
<data name="CodeSent" xml:space="preserve">
|
||||
<value>Ο Κωδικός Στάλθηκε</value>
|
||||
<value>Ο κωδικός στάλθηκε!</value>
|
||||
</data>
|
||||
<data name="ConfirmYourIdentity" xml:space="preserve">
|
||||
<value>Επιβεβαιώστε την ταυτότητα σας για να συνεχίσετε.</value>
|
||||
@@ -1664,7 +1665,7 @@
|
||||
<value>Τα κλειδιά κρυπτογράφησης λογαριασμού είναι μοναδικά για κάθε λογαριασμό χρήστη Bitwarden, οπότε δεν μπορείτε να εισάγετε μια κρυπτογραφημένη εξαγωγή σε διαφορετικό λογαριασμό.</value>
|
||||
</data>
|
||||
<data name="ExportVaultConfirmationTitle" xml:space="preserve">
|
||||
<value>Επιβεβαίωση εξαγωγής Vault</value>
|
||||
<value>Επιβεβαίωση εξαγωγής vault</value>
|
||||
<comment>Title for the alert to confirm vault exports.</comment>
|
||||
</data>
|
||||
<data name="Warning" xml:space="preserve">
|
||||
@@ -1907,7 +1908,7 @@
|
||||
<value>Ημερομηνία διαγραφής</value>
|
||||
</data>
|
||||
<data name="DeletionTime" xml:space="preserve">
|
||||
<value>Χρόνος Διαγραφής</value>
|
||||
<value>Χρόνος διαγραφής</value>
|
||||
</data>
|
||||
<data name="DeletionDateInfo" xml:space="preserve">
|
||||
<value>Το Send θα διαγραφεί οριστικά την καθορισμένη ημερομηνία και ώρα.</value>
|
||||
@@ -1917,7 +1918,7 @@
|
||||
<value>Εκκρεμεί διαγραφή</value>
|
||||
</data>
|
||||
<data name="ExpirationDate" xml:space="preserve">
|
||||
<value>Ημερομηνία Λήξης</value>
|
||||
<value>Ημερομηνία λήξης</value>
|
||||
</data>
|
||||
<data name="ExpirationTime" xml:space="preserve">
|
||||
<value>Χρόνος λήξης</value>
|
||||
@@ -1943,14 +1944,14 @@
|
||||
<value>Τρέχων Αριθμός Πρόσβασης</value>
|
||||
</data>
|
||||
<data name="NewPassword" xml:space="preserve">
|
||||
<value>Νέος Κωδικός</value>
|
||||
<value>Νέος κωδικός πρόσβασης</value>
|
||||
</data>
|
||||
<data name="PasswordInfo" xml:space="preserve">
|
||||
<value>Προαιρετικά απαιτείται κωδικός πρόσβασης για τους χρήστες για να έχουν πρόσβαση σε αυτό το Send.</value>
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="RemovePassword" xml:space="preserve">
|
||||
<value>Αφαίρεση κωδικού</value>
|
||||
<value>Αφαίρεση κωδικού πρόσβασης</value>
|
||||
</data>
|
||||
<data name="AreYouSureRemoveSendPassword" xml:space="preserve">
|
||||
<value>Είστε βέβαιοι ότι θέλετε να καταργήσετε τον κωδικό πρόσβασης;</value>
|
||||
@@ -1978,10 +1979,10 @@
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="CopyLink" xml:space="preserve">
|
||||
<value>Αντιγραφή Συνδέσμου</value>
|
||||
<value>Αντιγραφή συνδέσμου</value>
|
||||
</data>
|
||||
<data name="ShareLink" xml:space="preserve">
|
||||
<value>Κοινοποίηση Συνδέσμου</value>
|
||||
<value>Κοινοποίηση συνδέσμου</value>
|
||||
</data>
|
||||
<data name="SendLink" xml:space="preserve">
|
||||
<value>Σύνδεσμος Send</value>
|
||||
@@ -2076,7 +2077,7 @@
|
||||
<value>Το Captcha απέτυχε. Παρακαλώ προσπάθησε ξανα.</value>
|
||||
</data>
|
||||
<data name="UpdatedMasterPassword" xml:space="preserve">
|
||||
<value>Ενημερώθηκε ο κύριος κωδικός πρόσβασης</value>
|
||||
<value>Ενημερωμένος κύριος κωδικός πρόσβασης</value>
|
||||
</data>
|
||||
<data name="UpdateMasterPassword" xml:space="preserve">
|
||||
<value>Ενημερώστε τον κύριο κωδικό πρόσβασης</value>
|
||||
@@ -2091,7 +2092,7 @@
|
||||
<value>Δεν είναι δυνατή η ενημέρωση του κωδικού πρόσβασης</value>
|
||||
</data>
|
||||
<data name="RemoveMasterPassword" xml:space="preserve">
|
||||
<value>Αφαίρεση Κύριου Κωδικού Πρόσβασης</value>
|
||||
<value>Αφαίρεση κύριου κωδικού πρόσβασης</value>
|
||||
</data>
|
||||
<data name="RemoveMasterPasswordWarning" xml:space="preserve">
|
||||
<value>{0} χρησιμοποιεί SSO με κρυπτογράφηση διαχείρισης πελατών. Συνεχίζοντας θα καταργήσετε τον Κύριο Κωδικό από το λογαριασμό σας και θα απαιτήσετε SSO για να συνδεθείτε.</value>
|
||||
@@ -2876,12 +2877,12 @@
|
||||
<value>Ρυθμίστε μια επιλογή κλειδώματος για να αλλάξετε την ενέργεια στη λήξη χρόνου του vault σας.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>Duo σύνδεση δύο βημάτων απαιτείται για το λογαριασμό σας. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
<value>Ακολουθήστε τα βήματα από το Duo για να ολοκληρώσετε τη σύνδεση.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Εκκίνηση Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2823,7 +2823,7 @@ seleccione Agregar TOTP para almacenar la clave de forma segura</value>
|
||||
<value>¿Continuar con el servicio de asistencia?</value>
|
||||
</data>
|
||||
<data name="ContinueToPrivacyPolicy" xml:space="preserve">
|
||||
<value>Continue to privacy policy?</value>
|
||||
<value>¿Continuar a la política de privacidad?</value>
|
||||
</data>
|
||||
<data name="ContinueToAppStore" xml:space="preserve">
|
||||
<value>¿Continuar a la App Store?</value>
|
||||
@@ -2845,7 +2845,7 @@ seleccione Agregar TOTP para almacenar la clave de forma segura</value>
|
||||
<value>¿No encuentras lo que estás buscando? Contacta con el soporte de Bitwarden en bitwarden.com.</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyDescriptionLong" xml:space="preserve">
|
||||
<value>Check out our privacy policy on bitwarden.com.</value>
|
||||
<value>Revise nuestra política de privacidad en bitwarden.com.</value>
|
||||
</data>
|
||||
<data name="ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp" xml:space="preserve">
|
||||
<value>Explora más características de tu cuenta de Bitwarden en la aplicación web.</value>
|
||||
@@ -2879,12 +2879,12 @@ seleccione Agregar TOTP para almacenar la clave de forma segura</value>
|
||||
<value>Configura una opción de desbloqueo para cambiar tu acción de tiempo de espera de tu caja fuerte.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>Se requiere el inicio de sesión en dos pasos Duo para su cuenta. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
<value>Siga los pasos de Duo para terminar de iniciar sesión.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Iniciar Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2878,12 +2878,12 @@ Voulez-vous basculer vers ce compte ?</value>
|
||||
<value>Configurez une méthode de déverrouillage pour modifier l'action après délai d'expiration de votre coffre.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>L'authentification à deux facteurs Duo est requise pour votre compte. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Lancer Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -946,7 +946,7 @@ Nuskaitymas vyks automatiškai.</value>
|
||||
<value>Negalite naudoti šios funkcijos, kol neatnaujinsite šifravimo raktą.</value>
|
||||
</data>
|
||||
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
|
||||
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
|
||||
<value>Privaloma migruoti šifravimo raktą. Prašome prisijungti per internetinę saugyklą norint jį atnaujinti.</value>
|
||||
</data>
|
||||
<data name="LearnMore" xml:space="preserve">
|
||||
<value>Sužinoti daugiau</value>
|
||||
@@ -1741,7 +1741,7 @@ Nuskaitymas vyks automatiškai.</value>
|
||||
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
||||
</data>
|
||||
<data name="AccountBiometricInvalidated" xml:space="preserve">
|
||||
<value>Biometric unlock for this account is disabled pending verification of master password.</value>
|
||||
<value>Biometrinis atrakinimas išjungtas, kol neįvesite pagrindinio slaptažodžio.</value>
|
||||
</data>
|
||||
<data name="AccountBiometricInvalidatedExtension" xml:space="preserve">
|
||||
<value>Autofill biometric unlock for this account is disabled pending verification of master password.</value>
|
||||
@@ -2739,7 +2739,7 @@ Ar norite pereiti prie šios paskyros?</value>
|
||||
<value>Cannot edit multiple URIs at once</value>
|
||||
</data>
|
||||
<data name="LoginApproved" xml:space="preserve">
|
||||
<value>Login approved</value>
|
||||
<value>Prisijungimas patvirtintas</value>
|
||||
</data>
|
||||
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
|
||||
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
|
||||
@@ -2748,13 +2748,13 @@ Ar norite pereiti prie šios paskyros?</value>
|
||||
<value>Log in with device</value>
|
||||
</data>
|
||||
<data name="LoggingInOn" xml:space="preserve">
|
||||
<value>Logging in on</value>
|
||||
<value>Prisijungiama prie</value>
|
||||
</data>
|
||||
<data name="Vault" xml:space="preserve">
|
||||
<value>Vault</value>
|
||||
</data>
|
||||
<data name="Appearance" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
<value>Išvaizda</value>
|
||||
</data>
|
||||
<data name="AccountSecurity" xml:space="preserve">
|
||||
<value>Account security</value>
|
||||
@@ -2769,7 +2769,7 @@ Ar norite pereiti prie šios paskyros?</value>
|
||||
<value>Copy app information</value>
|
||||
</data>
|
||||
<data name="SyncNow" xml:space="preserve">
|
||||
<value>Sync now</value>
|
||||
<value>Sinchronizuoti dabar</value>
|
||||
</data>
|
||||
<data name="UnlockOptions" xml:space="preserve">
|
||||
<value>Unlock options</value>
|
||||
@@ -2859,23 +2859,23 @@ Ar norite pereiti prie šios paskyros?</value>
|
||||
<value>Choose the dark theme to use when your device’s dark mode is in use</value>
|
||||
</data>
|
||||
<data name="CreatedXY" xml:space="preserve">
|
||||
<value>Created {0}, {1}</value>
|
||||
<value>Sukurta {0}, {1}</value>
|
||||
<comment>To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time.</comment>
|
||||
</data>
|
||||
<data name="TooManyAttempts" xml:space="preserve">
|
||||
<value>Too many attempts</value>
|
||||
<value>Viršytas bandymų skaičius</value>
|
||||
</data>
|
||||
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
|
||||
<value>Account logged out.</value>
|
||||
<value>Atsijungta nuo paskyros.</value>
|
||||
</data>
|
||||
<data name="YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword" xml:space="preserve">
|
||||
<value>Your organization permissions were updated, requiring you to set a master password.</value>
|
||||
<value>Dėl pasikeitusių Jūsų organizacijos nuostatų, Jums reikia nustatyti pagrindinį slaptažodį.</value>
|
||||
</data>
|
||||
<data name="YourOrganizationRequiresYouToSetAMasterPassword" xml:space="preserve">
|
||||
<value>Your organization requires you to set a master password.</value>
|
||||
<value>Jūsų organizacijos nuostatos reikalauja Jus nustatyti pagrindinį slaptažodį.</value>
|
||||
</data>
|
||||
<data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve">
|
||||
<value>Set up an unlock option to change your vault timeout action.</value>
|
||||
<value>Nustatykite atrakinimo būdą, kad pakeistumėte Jūsų saugyklos laiko limito veiksmą.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
@@ -2884,6 +2884,6 @@ Ar norite pereiti prie šios paskyros?</value>
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Paleisti „Duo“</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -371,7 +371,7 @@
|
||||
<comment>Label for a username.</comment>
|
||||
</data>
|
||||
<data name="ValidationFieldRequired" xml:space="preserve">
|
||||
<value>Het {0}-veld is vereist.</value>
|
||||
<value>Vul het veld {0} in.</value>
|
||||
<comment>Validation message for when a form field is left blank and is required to be entered.</comment>
|
||||
</data>
|
||||
<data name="ValueHasBeenCopied" xml:space="preserve">
|
||||
@@ -946,7 +946,7 @@ Het scannen gebeurt automatisch.</value>
|
||||
<value>Je kunt deze functie pas gebruiken als je je encryptiesleutel bijwerkt.</value>
|
||||
</data>
|
||||
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
|
||||
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
|
||||
<value>Migratie van de encryptiesleutel vereist. Login via de website om je encryptiesleutel bij te werken.</value>
|
||||
</data>
|
||||
<data name="LearnMore" xml:space="preserve">
|
||||
<value>Meer informatie</value>
|
||||
@@ -996,7 +996,7 @@ Het scannen gebeurt automatisch.</value>
|
||||
<value>Beveiligingscode kopiëren</value>
|
||||
</data>
|
||||
<data name="Number" xml:space="preserve">
|
||||
<value>Kaartummer</value>
|
||||
<value>Kaartnummer</value>
|
||||
</data>
|
||||
<data name="SecurityCode" xml:space="preserve">
|
||||
<value>Beveiligingscode</value>
|
||||
@@ -2623,22 +2623,22 @@ Wilt u naar dit account wisselen?</value>
|
||||
<value>Huidig hoofdwachtwoord</value>
|
||||
</data>
|
||||
<data name="LoggedIn" xml:space="preserve">
|
||||
<value>Logged in!</value>
|
||||
<value>Ingelogd!</value>
|
||||
</data>
|
||||
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||
<value>Approve with my other device</value>
|
||||
<value>Goedkeuren met mijn andere apparaat</value>
|
||||
</data>
|
||||
<data name="RequestAdminApproval" xml:space="preserve">
|
||||
<value>Request admin approval</value>
|
||||
<value>Goedkeuring van beheerder vragen</value>
|
||||
</data>
|
||||
<data name="ApproveWithMasterPassword" xml:space="preserve">
|
||||
<value>Approve with master password</value>
|
||||
<value>Goedkeuren met hoofdwachtwoord</value>
|
||||
</data>
|
||||
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
|
||||
<value>Turn off using a public device</value>
|
||||
<value>Uitschakelen met een openbaar apparaat</value>
|
||||
</data>
|
||||
<data name="RememberThisDevice" xml:space="preserve">
|
||||
<value>Remember this device</value>
|
||||
<value>Dit apparaat onthouden</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Passkey</value>
|
||||
@@ -2668,7 +2668,7 @@ Wilt u naar dit account wisselen?</value>
|
||||
<value>Hulptekst hoofdwachtwoord opnieuw vragen</value>
|
||||
</data>
|
||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
|
||||
<value>Ontgrendelen kan mislukken als er onvoldoende geheugen is. Verminder je KDF-geheugeninstellingen of stel biometrische ontgrendeling in om dit op te lossen.</value>
|
||||
</data>
|
||||
<data name="InvalidAPIKey" xml:space="preserve">
|
||||
<value>Ongeldige API-sleutel</value>
|
||||
@@ -2677,22 +2677,22 @@ Wilt u naar dit account wisselen?</value>
|
||||
<value>Ongeldige API-token</value>
|
||||
</data>
|
||||
<data name="AdminApprovalRequested" xml:space="preserve">
|
||||
<value>Admin approval requested</value>
|
||||
<value>Goedkeuring van beheerder aangevraagd</value>
|
||||
</data>
|
||||
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
|
||||
<value>Your request has been sent to your admin.</value>
|
||||
<value>Je verzoek is naar je beheerder verstuurd.</value>
|
||||
</data>
|
||||
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
|
||||
<value>You will be notified once approved. </value>
|
||||
<value>Je krijgt een melding zodra je bent goedgekeurd.</value>
|
||||
</data>
|
||||
<data name="TroubleLoggingIn" xml:space="preserve">
|
||||
<value>Trouble logging in?</value>
|
||||
<value>Problemen met inloggen?</value>
|
||||
</data>
|
||||
<data name="LoggingInAsX" xml:space="preserve">
|
||||
<value>Logging in as {0}</value>
|
||||
<value>Inloggen als {0}</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
|
||||
<value>Vault timeout action changed to log out</value>
|
||||
<value>Kluis time-out actie gewijzigd naar uitloggen</value>
|
||||
</data>
|
||||
<data name="BlockAutoFill" xml:space="preserve">
|
||||
<value>Automatisch aanvullen blokkeren</value>
|
||||
@@ -2738,16 +2738,16 @@ Wilt u naar dit account wisselen?</value>
|
||||
<value>Meerdere URIs in één keer bewerken kan niet</value>
|
||||
</data>
|
||||
<data name="LoginApproved" xml:space="preserve">
|
||||
<value>Login approved</value>
|
||||
<value>Inloggen goedgekeurd</value>
|
||||
</data>
|
||||
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
|
||||
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
|
||||
<value>Je moet Inloggen met apparaat instellen in de instellingen van de Bitwarden-app. Behoefte aan een andere mogelijkheid?</value>
|
||||
</data>
|
||||
<data name="LogInWithDevice" xml:space="preserve">
|
||||
<value>Log in with device</value>
|
||||
<value>Inloggen met apparaat</value>
|
||||
</data>
|
||||
<data name="LoggingInOn" xml:space="preserve">
|
||||
<value>Logging in on</value>
|
||||
<value>Inloggen op</value>
|
||||
</data>
|
||||
<data name="Vault" xml:space="preserve">
|
||||
<value>Kluis</value>
|
||||
@@ -2862,16 +2862,16 @@ Wilt u naar dit account wisselen?</value>
|
||||
<comment>To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time.</comment>
|
||||
</data>
|
||||
<data name="TooManyAttempts" xml:space="preserve">
|
||||
<value>Too many attempts</value>
|
||||
<value>Te veel pogingen</value>
|
||||
</data>
|
||||
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
|
||||
<value>Account logged out.</value>
|
||||
<value>Account uitgelogd.</value>
|
||||
</data>
|
||||
<data name="YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword" xml:space="preserve">
|
||||
<value>Your organization permissions were updated, requiring you to set a master password.</value>
|
||||
<value>De organisatierechten zijn bijgewerkt, je moet een hoofdwachtwoord instellen.</value>
|
||||
</data>
|
||||
<data name="YourOrganizationRequiresYouToSetAMasterPassword" xml:space="preserve">
|
||||
<value>Your organization requires you to set a master password.</value>
|
||||
<value>Je organisatie vereist dat je een hoofdwachtwoord instelt.</value>
|
||||
</data>
|
||||
<data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve">
|
||||
<value>Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen.</value>
|
||||
|
||||
@@ -1570,7 +1570,7 @@ A leitura será efetuada automaticamente.</value>
|
||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="SolarizedDark" xml:space="preserve">
|
||||
<value>Solarized Dark</value>
|
||||
<value>Solarized (escuro)</value>
|
||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||
|
||||
@@ -1191,6 +1191,9 @@ Scanning will happen automatically.</value>
|
||||
<data name="WindowsHello" xml:space="preserve">
|
||||
<value>Windows Hello</value>
|
||||
</data>
|
||||
<data name="BitwardenCredentialProviderGoToSettings" xml:space="preserve">
|
||||
<value>We were unable to automatically open the Android credential provider settings menu for you. You can navigate to the credential provider settings menu manually from Android Settings > System > Passwords & accounts > Passwords, passkeys and data services.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillGoToSettings" xml:space="preserve">
|
||||
<value>We were unable to automatically open the Android autofill settings menu for you. You can navigate to the autofill settings menu manually from Android Settings > System > Languages and input > Advanced > Autofill service.</value>
|
||||
</data>
|
||||
@@ -1816,6 +1819,9 @@ Scanning will happen automatically.</value>
|
||||
<data name="AccessibilityDrawOverPermissionAlert" xml:space="preserve">
|
||||
<value>Bitwarden needs attention - Turn on "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
|
||||
</data>
|
||||
<data name="CredentialProviderService" xml:space="preserve">
|
||||
<value>Credential Provider service</value>
|
||||
</data>
|
||||
<data name="AutofillServices" xml:space="preserve">
|
||||
<value>Auto-fill services</value>
|
||||
</data>
|
||||
@@ -2799,6 +2805,9 @@ Do you want to switch to this account?</value>
|
||||
<data name="XHours" xml:space="preserve">
|
||||
<value>{0} hours</value>
|
||||
</data>
|
||||
<data name="CredentialProviderServiceExplanationLong" xml:space="preserve">
|
||||
<value>The Android Credential Provider is used for managing passkeys for use with websites and other apps on your device.</value>
|
||||
</data>
|
||||
<data name="AutofillServicesExplanationLong" xml:space="preserve">
|
||||
<value>The Android Autofill Framework is used to assist in filling login information into other apps on your device.</value>
|
||||
</data>
|
||||
@@ -2877,6 +2886,27 @@ Do you want to switch to this account?</value>
|
||||
<data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve">
|
||||
<value>Set up an unlock option to change your vault timeout action.</value>
|
||||
</data>
|
||||
<data name="ChooseALoginToSaveThisPasskeyTo" xml:space="preserve">
|
||||
<value>Choose a login to save this passkey to</value>
|
||||
</data>
|
||||
<data name="SavePasskeyAsNewLogin" xml:space="preserve">
|
||||
<value>Save passkey as new login</value>
|
||||
</data>
|
||||
<data name="SavePasskey" xml:space="preserve">
|
||||
<value>Save passkey</value>
|
||||
</data>
|
||||
<data name="PasskeysForX" xml:space="preserve">
|
||||
<value>Passkeys for {0}</value>
|
||||
</data>
|
||||
<data name="PasswordsForX" xml:space="preserve">
|
||||
<value>Passwords for {0}</value>
|
||||
</data>
|
||||
<data name="OverwritePasskey" xml:space="preserve">
|
||||
<value>Overwrite passkey?</value>
|
||||
</data>
|
||||
<data name="ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey" xml:space="preserve">
|
||||
<value>This item already contains a passkey. Are you sure you want to overwrite the current passkey?</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
</data>
|
||||
@@ -2886,4 +2916,51 @@ Do you want to switch to this account?</value>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
</data>
|
||||
<data name="VerificationRequiredByX" xml:space="preserve">
|
||||
<value>Verification required by {0}</value>
|
||||
</data>
|
||||
<data name="VerificationRequiredForThisActionSetUpAnUnlockMethodInBitwardenToContinue" xml:space="preserve">
|
||||
<value>Verification required for this action. Set up an unlock method in Bitwarden to continue.</value>
|
||||
</data>
|
||||
<data name="ErrorCreatingPasskey" xml:space="preserve">
|
||||
<value>Error creating passkey</value>
|
||||
</data>
|
||||
<data name="ErrorReadingPasskey" xml:space="preserve">
|
||||
<value>Error reading passkey</value>
|
||||
</data>
|
||||
<data name="ThereWasAProblemCreatingAPasskeyForXTryAgainLater" xml:space="preserve">
|
||||
<value>There was a problem creating a passkey for {0}. Try again later.</value>
|
||||
<comment>The parameter is the RpId</comment>
|
||||
</data>
|
||||
<data name="ThereWasAProblemReadingAPasskeyForXTryAgainLater" xml:space="preserve">
|
||||
<value>There was a problem reading your passkey for {0}. Try again later.</value>
|
||||
<comment>The parameter is the RpId</comment>
|
||||
</data>
|
||||
<data name="VerifyingIdentityEllipsis" xml:space="preserve">
|
||||
<value>Verifying identity...</value>
|
||||
</data>
|
||||
<data name="Passwords" xml:space="preserve">
|
||||
<value>Passwords</value>
|
||||
</data>
|
||||
<data name="UnknownAccount" xml:space="preserve">
|
||||
<value>Unknown account</value>
|
||||
</data>
|
||||
<data name="SetUpAutofill" xml:space="preserve">
|
||||
<value>Set up auto-fill</value>
|
||||
</data>
|
||||
<data name="GetInstantAccessToYourPasswordsAndPasskeys" xml:space="preserve">
|
||||
<value>Get instant access to your passwords and passkeys!</value>
|
||||
</data>
|
||||
<data name="SetUpAutoFillDescriptionLong" xml:space="preserve">
|
||||
<value>To set up password auto-fill and passkey management, set Bitwarden as your preferred provider in the iOS Settings.</value>
|
||||
</data>
|
||||
<data name="FirstDotGoToYourDeviceSettingsPasswordsPasswordOptions" xml:space="preserve">
|
||||
<value>1. Go to your device's Settings > Passwords > Password Options</value>
|
||||
</data>
|
||||
<data name="SecondDotTurnOnAutoFill" xml:space="preserve">
|
||||
<value>2. Turn on AutoFill</value>
|
||||
</data>
|
||||
<data name="ThirdDotSelectBitwardenToUseForPasswordsAndPasskeys" xml:space="preserve">
|
||||
<value>3. Select "Bitwarden" to use for passwords and passkeys</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2879,12 +2879,12 @@ Vill du byta till detta konto?</value>
|
||||
<value>Ställ in ett upplåsningsalternativ för att ändra vad som händer när tidsgränsen uppnås.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>Duo tvåstegsverifiering krävs för ditt konto. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
<value>Följ stegen från Duo för att slutföra inloggningen.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Starta Duo</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2878,12 +2878,12 @@ Bạn có muốn chuyển sang tài khoản này không?</value>
|
||||
<value>Thiết lập khóa khi hết thời gian chờ kho.</value>
|
||||
</data>
|
||||
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
|
||||
<value>Duo two-step login is required for your account. </value>
|
||||
<value>Tài khoản của bạn bắt buộc đăng nhập 2 bước Dou. </value>
|
||||
</data>
|
||||
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
|
||||
<value>Follow the steps from Duo to finish logging in.</value>
|
||||
<value>Làm theo các bước từ Dou để hoàn tất đăng nhập.</value>
|
||||
</data>
|
||||
<data name="LaunchDuo" xml:space="preserve">
|
||||
<value>Launch Duo</value>
|
||||
<value>Khởi động Dou</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Bit.Core.Services
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly Func<ISearchService> _searchService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ITotpService _totpService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly string _clearCipherCacheKey;
|
||||
private readonly string[] _allClearCipherCacheKeys;
|
||||
private Dictionary<string, HashSet<string>> _domainMatchBlacklist = new Dictionary<string, HashSet<string>>
|
||||
@@ -53,6 +55,8 @@ namespace Bit.Core.Services
|
||||
II18nService i18nService,
|
||||
Func<ISearchService> searchService,
|
||||
IConfigService configService,
|
||||
ITotpService totpService,
|
||||
IClipboardService clipboardService,
|
||||
string clearCipherCacheKey,
|
||||
string[] allClearCipherCacheKeys)
|
||||
{
|
||||
@@ -65,6 +69,8 @@ namespace Bit.Core.Services
|
||||
_i18nService = i18nService;
|
||||
_searchService = searchService;
|
||||
_configService = configService;
|
||||
_totpService = totpService;
|
||||
_clipboardService = clipboardService;
|
||||
_clearCipherCacheKey = clearCipherCacheKey;
|
||||
_allClearCipherCacheKeys = allClearCipherCacheKeys;
|
||||
}
|
||||
@@ -1286,6 +1292,51 @@ namespace Bit.Core.Services
|
||||
cipher.PasswordHistory = encPhs;
|
||||
}
|
||||
|
||||
public async Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams)
|
||||
{
|
||||
var newCipher = new CipherView
|
||||
{
|
||||
Name = newPasskeyParams.CredentialName,
|
||||
Type = CipherType.Login,
|
||||
Login = new LoginView
|
||||
{
|
||||
Username = newPasskeyParams.UserName,
|
||||
Uris = new List<LoginUriView>
|
||||
{
|
||||
new LoginUriView { Uri = newPasskeyParams.RpId }
|
||||
}
|
||||
},
|
||||
Card = new CardView(),
|
||||
Identity = new IdentityView(),
|
||||
SecureNote = new SecureNoteView
|
||||
{
|
||||
Type = SecureNoteType.Generic
|
||||
},
|
||||
Reprompt = CipherRepromptType.None
|
||||
};
|
||||
|
||||
var encryptedCipher = await EncryptAsync(newCipher);
|
||||
await SaveWithServerAsync(encryptedCipher);
|
||||
|
||||
return encryptedCipher.Id;
|
||||
}
|
||||
|
||||
public async Task CopyTotpCodeIfNeededAsync(CipherView cipher)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cipher?.Login?.Totp)
|
||||
||
|
||||
await _stateService.GetDisableAutoTotpCopyAsync() == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (cipher.OrganizationUseTotp || await _stateService.CanAccessPremiumAsync())
|
||||
{
|
||||
var totpCode = await _totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
await _clipboardService.CopyTextAsync(totpCode);
|
||||
}
|
||||
}
|
||||
|
||||
private class CipherLocaleComparer : IComparer<CipherView>
|
||||
{
|
||||
private readonly II18nService _i18nService;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@@ -11,7 +8,8 @@ namespace Bit.Core.Services
|
||||
private readonly ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>> _preconditionsTasks = new ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>>
|
||||
{
|
||||
[AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource<bool>(),
|
||||
[AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource<bool>()
|
||||
[AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource<bool>(),
|
||||
[AwaiterPrecondition.AutofillIOSExtensionViewDidAppear] = new TaskCompletionSource<bool>()
|
||||
};
|
||||
|
||||
public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition)
|
||||
@@ -39,5 +37,15 @@ namespace Bit.Core.Services
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Recreate(AwaiterPrecondition awaiterPrecondition)
|
||||
{
|
||||
if (_preconditionsTasks.TryRemove(awaiterPrecondition, out var oldTcs))
|
||||
{
|
||||
oldTcs.TrySetCanceled();
|
||||
|
||||
_preconditionsTasks.TryAdd(awaiterPrecondition, new TaskCompletionSource<bool>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
509
src/Core/Services/Fido2AuthenticatorService.cs
Normal file
509
src/Core/Services/Fido2AuthenticatorService.cs
Normal file
@@ -0,0 +1,509 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Formats.Cbor;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class Fido2AuthenticatorService : IFido2AuthenticatorService
|
||||
{
|
||||
// AAGUID: d548826e-79b4-db40-a3d8-11116f7e8349
|
||||
public static readonly byte[] AAGUID = new byte[] { 0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49 };
|
||||
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
||||
|
||||
public Fido2AuthenticatorService(ICipherService cipherService,
|
||||
ISyncService syncService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
IUserVerificationMediatorService userVerificationMediatorService)
|
||||
{
|
||||
_cipherService = cipherService;
|
||||
_syncService = syncService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_userVerificationMediatorService = userVerificationMediatorService;
|
||||
}
|
||||
|
||||
public async Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface)
|
||||
{
|
||||
if (makeCredentialParams.CredTypesAndPubKeyAlgs.All((p) => p.Alg != (int)Fido2AlgorithmIdentifier.ES256))
|
||||
{
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
await userInterface.EnsureUnlockedVaultAsync();
|
||||
await _syncService.FullSyncAsync(false);
|
||||
|
||||
var existingCipherIds = await FindExcludedCredentialsAsync(
|
||||
makeCredentialParams.ExcludeCredentialDescriptorList
|
||||
);
|
||||
if (existingCipherIds.Length > 0)
|
||||
{
|
||||
await userInterface.InformExcludedCredentialAsync(existingCipherIds);
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
var response = await userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams
|
||||
{
|
||||
CredentialName = makeCredentialParams.RpEntity.Name,
|
||||
UserName = makeCredentialParams.UserEntity.Name,
|
||||
UserVerificationPreference = makeCredentialParams.UserVerificationPreference,
|
||||
RpId = makeCredentialParams.RpEntity.Id
|
||||
});
|
||||
|
||||
var cipherId = response.CipherId;
|
||||
var userVerified = response.UserVerified;
|
||||
string credentialId;
|
||||
if (cipherId == null)
|
||||
{
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var keyPair = GenerateKeyPair();
|
||||
var fido2Credential = CreateCredentialView(makeCredentialParams, keyPair.privateKey);
|
||||
|
||||
var encrypted = await _cipherService.GetAsync(cipherId);
|
||||
var cipher = await encrypted.DecryptAsync();
|
||||
|
||||
if (!userVerified
|
||||
&&
|
||||
await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions(
|
||||
cipher.Reprompt != CipherRepromptType.None,
|
||||
makeCredentialParams.UserVerificationPreference,
|
||||
userInterface.HasVaultBeenUnlockedInThisTransaction)))
|
||||
{
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
cipher.Login.Fido2Credentials = new List<Fido2CredentialView> { fido2Credential };
|
||||
var reencrypted = await _cipherService.EncryptAsync(cipher);
|
||||
await _cipherService.SaveWithServerAsync(reencrypted);
|
||||
credentialId = fido2Credential.CredentialId;
|
||||
|
||||
var authData = await GenerateAuthDataAsync(
|
||||
rpId: makeCredentialParams.RpEntity.Id,
|
||||
counter: fido2Credential.CounterValue,
|
||||
userPresence: true,
|
||||
userVerification: userVerified,
|
||||
credentialId: credentialId.GuidToRawFormat(),
|
||||
publicKey: keyPair.publicKey
|
||||
);
|
||||
|
||||
return new Fido2AuthenticatorMakeCredentialResult
|
||||
{
|
||||
CredentialId = credentialId.GuidToRawFormat(),
|
||||
AttestationObject = EncodeAttestationObject(authData),
|
||||
AuthData = authData,
|
||||
PublicKey = keyPair.publicKey.ExportDer(),
|
||||
PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
|
||||
};
|
||||
}
|
||||
catch (NotAllowedError)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw new UnknownError();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface)
|
||||
{
|
||||
List<CipherView> cipherOptions;
|
||||
|
||||
await userInterface.EnsureUnlockedVaultAsync();
|
||||
await _syncService.FullSyncAsync(false);
|
||||
|
||||
if (assertionParams.AllowCredentialDescriptorList?.Length > 0)
|
||||
{
|
||||
cipherOptions = await FindCredentialsByIdAsync(
|
||||
assertionParams.AllowCredentialDescriptorList,
|
||||
assertionParams.RpId
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
cipherOptions = await FindCredentialsByRpAsync(assertionParams.RpId);
|
||||
}
|
||||
|
||||
if (cipherOptions.Count == 0)
|
||||
{
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
var response = await userInterface.PickCredentialAsync(
|
||||
cipherOptions.Select((cipher) => new Fido2GetAssertionUserInterfaceCredential
|
||||
{
|
||||
CipherId = cipher.Id,
|
||||
UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.GetUserVerificationPreferenceFrom(assertionParams.UserVerificationPreference, cipher.Reprompt)
|
||||
}).ToArray()
|
||||
);
|
||||
var selectedCipherId = response.CipherId;
|
||||
var userVerified = response.UserVerified;
|
||||
|
||||
var selectedCipher = cipherOptions.FirstOrDefault((c) => c.Id == selectedCipherId);
|
||||
if (selectedCipher == null)
|
||||
{
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
if (!userVerified
|
||||
&&
|
||||
await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions(
|
||||
selectedCipher.Reprompt != CipherRepromptType.None,
|
||||
assertionParams.UserVerificationPreference,
|
||||
userInterface.HasVaultBeenUnlockedInThisTransaction)))
|
||||
{
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var selectedFido2Credential = selectedCipher.Login.MainFido2Credential;
|
||||
var selectedCredentialId = selectedFido2Credential.CredentialId;
|
||||
|
||||
await _cipherService.UpdateLastUsedDateAsync(selectedCipher.Id);
|
||||
|
||||
if (selectedFido2Credential.CounterValue != 0)
|
||||
{
|
||||
++selectedFido2Credential.CounterValue;
|
||||
var encrypted = await _cipherService.EncryptAsync(selectedCipher);
|
||||
await _cipherService.SaveWithServerAsync(encrypted);
|
||||
}
|
||||
|
||||
var authenticatorData = await GenerateAuthDataAsync(
|
||||
rpId: selectedFido2Credential.RpId,
|
||||
userPresence: true,
|
||||
userVerification: userVerified,
|
||||
counter: selectedFido2Credential.CounterValue
|
||||
);
|
||||
|
||||
var signature = GenerateSignature(
|
||||
authData: authenticatorData,
|
||||
clientDataHash: assertionParams.Hash,
|
||||
privateKey: selectedFido2Credential.KeyBytes
|
||||
);
|
||||
|
||||
return new Fido2AuthenticatorGetAssertionResult
|
||||
{
|
||||
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
|
||||
{
|
||||
Id = selectedCredentialId.GuidToRawFormat(),
|
||||
UserHandle = selectedFido2Credential.UserHandleValue,
|
||||
Cipher = selectedCipher
|
||||
},
|
||||
AuthenticatorData = authenticatorData,
|
||||
Signature = signature
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw new UnknownError();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId)
|
||||
{
|
||||
var credentials = (await FindCredentialsByRpAsync(rpId)).Select(cipher => new Fido2AuthenticatorDiscoverableCredentialMetadata
|
||||
{
|
||||
Type = Constants.DefaultFido2CredentialType,
|
||||
Id = cipher.Login.MainFido2Credential.CredentialId.GuidToRawFormat(),
|
||||
RpId = cipher.Login.MainFido2Credential.RpId,
|
||||
UserHandle = cipher.Login.MainFido2Credential.UserHandleValue,
|
||||
UserName = cipher.Login.MainFido2Credential.UserName
|
||||
}).ToArray();
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds existing crendetials and returns the `CipherId` for each one
|
||||
/// </summary>
|
||||
private async Task<string[]> FindExcludedCredentialsAsync(
|
||||
PublicKeyCredentialDescriptor[] credentials
|
||||
)
|
||||
{
|
||||
if (credentials == null || credentials.Length == 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var ids = new List<string>();
|
||||
|
||||
foreach (var credential in credentials)
|
||||
{
|
||||
try
|
||||
{
|
||||
ids.Add(credential.Id.GuidToStandardFormat());
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (ids.Count == 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
return ciphers
|
||||
.FindAll(
|
||||
(cipher) =>
|
||||
!cipher.IsDeleted &&
|
||||
cipher.OrganizationId == null &&
|
||||
cipher.Type == CipherType.Login &&
|
||||
cipher.Login.HasFido2Credentials &&
|
||||
ids.Contains(cipher.Login.MainFido2Credential.CredentialId)
|
||||
)
|
||||
.Select((cipher) => cipher.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private async Task<List<CipherView>> FindCredentialsByIdAsync(PublicKeyCredentialDescriptor[] credentials, string rpId)
|
||||
{
|
||||
var ids = new List<string>();
|
||||
|
||||
foreach (var credential in credentials)
|
||||
{
|
||||
try
|
||||
{
|
||||
ids.Add(credential.Id.GuidToStandardFormat());
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (ids.Count == 0)
|
||||
{
|
||||
return new List<CipherView>();
|
||||
}
|
||||
|
||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.FindAll((cipher) =>
|
||||
!cipher.IsDeleted &&
|
||||
cipher.Type == CipherType.Login &&
|
||||
cipher.Login.HasFido2Credentials &&
|
||||
cipher.Login.MainFido2Credential.RpId == rpId &&
|
||||
ids.Contains(cipher.Login.MainFido2Credential.CredentialId)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<List<CipherView>> FindCredentialsByRpAsync(string rpId)
|
||||
{
|
||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.FindAll((cipher) =>
|
||||
!cipher.IsDeleted &&
|
||||
cipher.Type == CipherType.Login &&
|
||||
cipher.Login.HasFido2Credentials &&
|
||||
cipher.Login.MainFido2Credential.RpId == rpId &&
|
||||
cipher.Login.MainFido2Credential.DiscoverableValue
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Move this to a separate service
|
||||
private (PublicKey publicKey, byte[] privateKey) GenerateKeyPair()
|
||||
{
|
||||
var dsa = ECDsa.Create();
|
||||
dsa.GenerateKey(ECCurve.NamedCurves.nistP256);
|
||||
var privateKey = dsa.ExportPkcs8PrivateKey();
|
||||
|
||||
return (new PublicKey(dsa), privateKey);
|
||||
}
|
||||
|
||||
private Fido2CredentialView CreateCredentialView(Fido2AuthenticatorMakeCredentialParams makeCredentialsParams, byte[] privateKey)
|
||||
{
|
||||
return new Fido2CredentialView
|
||||
{
|
||||
CredentialId = Guid.NewGuid().ToString(),
|
||||
KeyType = Constants.DefaultFido2CredentialType,
|
||||
KeyAlgorithm = Constants.DefaultFido2CredentialAlgorithm,
|
||||
KeyCurve = Constants.DefaultFido2CredentialCurve,
|
||||
KeyValue = CoreHelpers.Base64UrlEncode(privateKey),
|
||||
RpId = makeCredentialsParams.RpEntity.Id,
|
||||
UserHandle = CoreHelpers.Base64UrlEncode(makeCredentialsParams.UserEntity.Id),
|
||||
UserName = makeCredentialsParams.UserEntity.Name,
|
||||
CounterValue = 0,
|
||||
RpName = makeCredentialsParams.RpEntity.Name,
|
||||
UserDisplayName = makeCredentialsParams.UserEntity.DisplayName,
|
||||
DiscoverableValue = makeCredentialsParams.RequireResidentKey,
|
||||
CreationDate = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<byte[]> GenerateAuthDataAsync(
|
||||
string rpId,
|
||||
bool userVerification,
|
||||
bool userPresence,
|
||||
int counter,
|
||||
byte[] credentialId = null,
|
||||
PublicKey publicKey = null
|
||||
)
|
||||
{
|
||||
var isAttestation = credentialId != null && publicKey != null;
|
||||
|
||||
List<byte> authData = new List<byte>();
|
||||
|
||||
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
|
||||
authData.AddRange(rpIdHash);
|
||||
|
||||
var flags = AuthDataFlags(
|
||||
extensionData: false,
|
||||
attestationData: isAttestation,
|
||||
userVerification: userVerification,
|
||||
userPresence: userPresence
|
||||
);
|
||||
authData.Add(flags);
|
||||
|
||||
authData.AddRange(new List<byte> {
|
||||
(byte)(counter >> 24),
|
||||
(byte)(counter >> 16),
|
||||
(byte)(counter >> 8),
|
||||
(byte)counter
|
||||
});
|
||||
|
||||
if (isAttestation)
|
||||
{
|
||||
var attestedCredentialData = new List<byte>();
|
||||
|
||||
attestedCredentialData.AddRange(AAGUID);
|
||||
|
||||
// credentialIdLength (2 bytes) and credential Id
|
||||
var credentialIdLength = new byte[] {
|
||||
(byte)((credentialId.Length - (credentialId.Length & 0xff)) / 256),
|
||||
(byte)(credentialId.Length & 0xff)
|
||||
};
|
||||
attestedCredentialData.AddRange(credentialIdLength);
|
||||
attestedCredentialData.AddRange(credentialId);
|
||||
attestedCredentialData.AddRange(publicKey.ExportCose());
|
||||
|
||||
authData.AddRange(attestedCredentialData);
|
||||
}
|
||||
|
||||
return authData.ToArray();
|
||||
}
|
||||
|
||||
private byte AuthDataFlags(bool extensionData, bool attestationData, bool userVerification, bool userPresence, bool backupEligibility = true, bool backupState = true)
|
||||
{
|
||||
byte flags = 0;
|
||||
|
||||
if (extensionData)
|
||||
{
|
||||
flags |= 0b1000000;
|
||||
}
|
||||
|
||||
if (attestationData)
|
||||
{
|
||||
flags |= 0b01000000;
|
||||
}
|
||||
|
||||
if (backupState)
|
||||
{
|
||||
flags |= 0b00010000;
|
||||
}
|
||||
|
||||
if (backupEligibility)
|
||||
{
|
||||
flags |= 0b00001000;
|
||||
}
|
||||
|
||||
if (userVerification)
|
||||
{
|
||||
flags |= 0b00000100;
|
||||
}
|
||||
|
||||
if (userPresence)
|
||||
{
|
||||
flags |= 0b00000001;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private byte[] EncodeAttestationObject(byte[] authData)
|
||||
{
|
||||
var attestationObject = new CborWriter(CborConformanceMode.Ctap2Canonical);
|
||||
attestationObject.WriteStartMap(3);
|
||||
attestationObject.WriteTextString("fmt");
|
||||
attestationObject.WriteTextString("none");
|
||||
attestationObject.WriteTextString("attStmt");
|
||||
attestationObject.WriteStartMap(0);
|
||||
attestationObject.WriteEndMap();
|
||||
attestationObject.WriteTextString("authData");
|
||||
attestationObject.WriteByteString(authData);
|
||||
attestationObject.WriteEndMap();
|
||||
|
||||
return attestationObject.Encode();
|
||||
}
|
||||
|
||||
// TODO: Move this to a separate service
|
||||
private byte[] GenerateSignature(byte[] authData, byte[] clientDataHash, byte[] privateKey)
|
||||
{
|
||||
var sigBase = authData.Concat(clientDataHash).ToArray();
|
||||
var dsa = ECDsa.Create();
|
||||
dsa.ImportPkcs8PrivateKey(privateKey, out var bytesRead);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
throw new Exception("Failed to import private key");
|
||||
}
|
||||
|
||||
return dsa.SignData(sigBase, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
|
||||
}
|
||||
|
||||
private class PublicKey
|
||||
{
|
||||
private readonly ECDsa _dsa;
|
||||
|
||||
public PublicKey(ECDsa dsa)
|
||||
{
|
||||
_dsa = dsa;
|
||||
}
|
||||
|
||||
public byte[] X => _dsa.ExportParameters(false).Q.X;
|
||||
public byte[] Y => _dsa.ExportParameters(false).Q.Y;
|
||||
|
||||
public byte[] ExportDer()
|
||||
{
|
||||
return _dsa.ExportSubjectPublicKeyInfo();
|
||||
}
|
||||
|
||||
public byte[] ExportCose()
|
||||
{
|
||||
var result = new CborWriter(CborConformanceMode.Ctap2Canonical);
|
||||
result.WriteStartMap(5);
|
||||
|
||||
// kty = EC2
|
||||
result.WriteInt32(1);
|
||||
result.WriteInt32(2);
|
||||
|
||||
// alg = ES256
|
||||
result.WriteInt32(3);
|
||||
result.WriteInt32((int)Fido2AlgorithmIdentifier.ES256);
|
||||
|
||||
// crv = P-256
|
||||
result.WriteInt32(-1);
|
||||
result.WriteInt32(1);
|
||||
|
||||
// x
|
||||
result.WriteInt32(-2);
|
||||
result.WriteByteString(X);
|
||||
|
||||
// y
|
||||
result.WriteInt32(-3);
|
||||
result.WriteByteString(Y);
|
||||
|
||||
result.WriteEndMap();
|
||||
|
||||
return result.Encode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
src/Core/Services/Fido2ClientService.cs
Normal file
261
src/Core/Services/Fido2ClientService.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class Fido2ClientService : IFido2ClientService
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IEnvironmentService _environmentService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IFido2AuthenticatorService _fido2AuthenticatorService;
|
||||
private readonly IFido2GetAssertionUserInterface _getAssertionUserInterface;
|
||||
private readonly IFido2MakeCredentialUserInterface _makeCredentialUserInterface;
|
||||
|
||||
public Fido2ClientService(
|
||||
IStateService stateService,
|
||||
IEnvironmentService environmentService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
IFido2AuthenticatorService fido2AuthenticatorService,
|
||||
IFido2GetAssertionUserInterface getAssertionUserInterface,
|
||||
IFido2MakeCredentialUserInterface makeCredentialUserInterface)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_environmentService = environmentService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_fido2AuthenticatorService = fido2AuthenticatorService;
|
||||
_getAssertionUserInterface = getAssertionUserInterface;
|
||||
_makeCredentialUserInterface = makeCredentialUserInterface;
|
||||
}
|
||||
|
||||
public async Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams)
|
||||
{
|
||||
var blockedUris = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||
var domain = CoreHelpers.GetHostname(createCredentialParams.Origin);
|
||||
if (blockedUris != null && blockedUris.Contains(domain))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.UriBlockedError,
|
||||
"Origin is blocked by the user");
|
||||
}
|
||||
|
||||
if (!await _stateService.IsAuthenticatedAsync())
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.InvalidStateError,
|
||||
"No user is logged in");
|
||||
}
|
||||
|
||||
if (createCredentialParams.Origin == _environmentService.GetWebVaultUrl())
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.NotAllowedError,
|
||||
"Saving Bitwarden credentials in a Bitwarden vault is not allowed");
|
||||
}
|
||||
|
||||
if (!createCredentialParams.SameOriginWithAncestors)
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.NotAllowedError,
|
||||
"Credential creation is now allowed from embedded contexts with different origins");
|
||||
}
|
||||
|
||||
if (createCredentialParams.User.Id.Length < 1 || createCredentialParams.User.Id.Length > 64)
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.TypeError,
|
||||
"The length of user.id is not between 1 and 64 bytes (inclusive)");
|
||||
}
|
||||
|
||||
if (!createCredentialParams.Origin.StartsWith("https://"))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.SecurityError,
|
||||
"Origin is not a valid https origin");
|
||||
}
|
||||
|
||||
if (!Fido2DomainUtils.IsValidRpId(createCredentialParams.Rp.Id, createCredentialParams.Origin))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.SecurityError,
|
||||
"RP ID cannot be used with this origin");
|
||||
}
|
||||
|
||||
PublicKeyCredentialParameters[] credTypesAndPubKeyAlgs;
|
||||
if (createCredentialParams.PubKeyCredParams?.Length > 0)
|
||||
{
|
||||
// Filter out all unsupported algorithms
|
||||
credTypesAndPubKeyAlgs = createCredentialParams.PubKeyCredParams
|
||||
.Where(kp => kp.Alg == (int)Fido2AlgorithmIdentifier.ES256 && kp.Type == Constants.DefaultFido2CredentialType)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assign default algorithms
|
||||
credTypesAndPubKeyAlgs = new PublicKeyCredentialParameters[]
|
||||
{
|
||||
new PublicKeyCredentialParameters { Alg = (int) Fido2AlgorithmIdentifier.ES256, Type = Constants.DefaultFido2CredentialType },
|
||||
new PublicKeyCredentialParameters { Alg = (int) Fido2AlgorithmIdentifier.RS256, Type = Constants.DefaultFido2CredentialType }
|
||||
};
|
||||
}
|
||||
|
||||
if (credTypesAndPubKeyAlgs.Length == 0)
|
||||
{
|
||||
throw new Fido2ClientException(Fido2ClientException.ErrorCode.NotSupportedError, "No supported algorithms found");
|
||||
}
|
||||
|
||||
var clientDataJSON = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "webauthn.create",
|
||||
challenge = CoreHelpers.Base64UrlEncode(createCredentialParams.Challenge),
|
||||
origin = createCredentialParams.Origin,
|
||||
crossOrigin = !createCredentialParams.SameOriginWithAncestors,
|
||||
// tokenBinding: {} // Not supported
|
||||
});
|
||||
var clientDataJSONBytes = Encoding.UTF8.GetBytes(clientDataJSON);
|
||||
var clientDataHash = await _cryptoFunctionService.HashAsync(clientDataJSONBytes, CryptoHashAlgorithm.Sha256);
|
||||
var makeCredentialParams = MapToMakeCredentialParams(createCredentialParams, credTypesAndPubKeyAlgs, clientDataHash);
|
||||
|
||||
try
|
||||
{
|
||||
var makeCredentialResult = await _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, _makeCredentialUserInterface);
|
||||
|
||||
return new Fido2ClientCreateCredentialResult
|
||||
{
|
||||
CredentialId = makeCredentialResult.CredentialId,
|
||||
AttestationObject = makeCredentialResult.AttestationObject,
|
||||
AuthData = makeCredentialResult.AuthData,
|
||||
ClientDataJSON = clientDataJSONBytes,
|
||||
PublicKey = makeCredentialResult.PublicKey,
|
||||
PublicKeyAlgorithm = makeCredentialResult.PublicKeyAlgorithm,
|
||||
Transports = createCredentialParams.Rp.Id == "google.com" ? new string[] { "internal", "usb" } : new string[] { "internal" } // workaround for a bug on Google's side
|
||||
};
|
||||
}
|
||||
catch (InvalidStateError)
|
||||
{
|
||||
throw new Fido2ClientException(Fido2ClientException.ErrorCode.InvalidStateError, "Unknown invalid state encountered");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new Fido2ClientException(Fido2ClientException.ErrorCode.UnknownError, $"An unknown error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams)
|
||||
{
|
||||
var blockedUris = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||
var domain = CoreHelpers.GetHostname(assertCredentialParams.Origin);
|
||||
if (blockedUris != null && blockedUris.Contains(domain))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.UriBlockedError,
|
||||
"Origin is blocked by the user");
|
||||
}
|
||||
|
||||
if (!await _stateService.IsAuthenticatedAsync())
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.InvalidStateError,
|
||||
"No user is logged in");
|
||||
}
|
||||
|
||||
if (assertCredentialParams.Origin == _environmentService.GetWebVaultUrl())
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.NotAllowedError,
|
||||
"Saving Bitwarden credentials in a Bitwarden vault is not allowed");
|
||||
}
|
||||
|
||||
if (!assertCredentialParams.Origin.StartsWith("https://"))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.SecurityError,
|
||||
"Origin is not a valid https origin");
|
||||
}
|
||||
|
||||
if (!Fido2DomainUtils.IsValidRpId(assertCredentialParams.RpId, assertCredentialParams.Origin))
|
||||
{
|
||||
throw new Fido2ClientException(
|
||||
Fido2ClientException.ErrorCode.SecurityError,
|
||||
"RP ID cannot be used with this origin");
|
||||
}
|
||||
|
||||
var clientDataJSON = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "webauthn.get",
|
||||
challenge = CoreHelpers.Base64UrlEncode(assertCredentialParams.Challenge),
|
||||
origin = assertCredentialParams.Origin,
|
||||
crossOrigin = !assertCredentialParams.SameOriginWithAncestors,
|
||||
});
|
||||
var clientDataJSONBytes = Encoding.UTF8.GetBytes(clientDataJSON);
|
||||
var clientDataHash = await _cryptoFunctionService.HashAsync(clientDataJSONBytes, CryptoHashAlgorithm.Sha256);
|
||||
var getAssertionParams = MapToGetAssertionParams(assertCredentialParams, clientDataHash);
|
||||
|
||||
try
|
||||
{
|
||||
var getAssertionResult = await _fido2AuthenticatorService.GetAssertionAsync(getAssertionParams, _getAssertionUserInterface);
|
||||
|
||||
return new Fido2ClientAssertCredentialResult
|
||||
{
|
||||
AuthenticatorData = getAssertionResult.AuthenticatorData,
|
||||
ClientDataJSON = clientDataJSONBytes,
|
||||
Id = CoreHelpers.Base64UrlEncode(getAssertionResult.SelectedCredential.Id),
|
||||
RawId = getAssertionResult.SelectedCredential.Id,
|
||||
Signature = getAssertionResult.Signature,
|
||||
UserHandle = getAssertionResult.SelectedCredential.UserHandle,
|
||||
Cipher = getAssertionResult.SelectedCredential.Cipher
|
||||
};
|
||||
}
|
||||
catch (InvalidStateError)
|
||||
{
|
||||
throw new Fido2ClientException(Fido2ClientException.ErrorCode.InvalidStateError, "Unknown invalid state encountered");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new Fido2ClientException(Fido2ClientException.ErrorCode.UnknownError, $"An unknown error occurred");
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Fido2AuthenticatorMakeCredentialParams MapToMakeCredentialParams(
|
||||
Fido2ClientCreateCredentialParams createCredentialParams,
|
||||
PublicKeyCredentialParameters[] credTypesAndPubKeyAlgs,
|
||||
byte[] clientDataHash)
|
||||
{
|
||||
var requireResidentKey = createCredentialParams.AuthenticatorSelection?.ResidentKey == "required" ||
|
||||
createCredentialParams.AuthenticatorSelection?.ResidentKey == "preferred" ||
|
||||
(createCredentialParams.AuthenticatorSelection?.ResidentKey == null &&
|
||||
createCredentialParams.AuthenticatorSelection?.RequireResidentKey == true);
|
||||
|
||||
return new Fido2AuthenticatorMakeCredentialParams
|
||||
{
|
||||
RequireResidentKey = requireResidentKey,
|
||||
UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.ToFido2UserVerificationPreference(createCredentialParams.AuthenticatorSelection?.UserVerification),
|
||||
ExcludeCredentialDescriptorList = createCredentialParams.ExcludeCredentials,
|
||||
CredTypesAndPubKeyAlgs = credTypesAndPubKeyAlgs,
|
||||
Hash = clientDataHash,
|
||||
RpEntity = createCredentialParams.Rp,
|
||||
UserEntity = createCredentialParams.User,
|
||||
Extensions = createCredentialParams.Extensions
|
||||
};
|
||||
}
|
||||
|
||||
private Fido2AuthenticatorGetAssertionParams MapToGetAssertionParams(
|
||||
Fido2ClientAssertCredentialParams assertCredentialParams,
|
||||
byte[] cliendDataHash)
|
||||
{
|
||||
return new Fido2AuthenticatorGetAssertionParams {
|
||||
RpId = assertCredentialParams.RpId,
|
||||
Challenge = assertCredentialParams.Challenge,
|
||||
AllowCredentialDescriptorList = assertCredentialParams.AllowCredentials,
|
||||
UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.ToFido2UserVerificationPreference(assertCredentialParams?.UserVerification),
|
||||
Hash = cliendDataHash
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/Core/Services/Fido2MediatorService.cs
Normal file
60
src/Core/Services/Fido2MediatorService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class Fido2MediatorService : IFido2MediatorService
|
||||
{
|
||||
private readonly IFido2AuthenticatorService _fido2AuthenticatorService;
|
||||
private readonly IFido2ClientService _fido2ClientService;
|
||||
private readonly ICipherService _cipherService;
|
||||
|
||||
public Fido2MediatorService(IFido2AuthenticatorService fido2AuthenticatorService,
|
||||
IFido2ClientService fido2ClientService,
|
||||
ICipherService cipherService)
|
||||
{
|
||||
_fido2AuthenticatorService = fido2AuthenticatorService;
|
||||
_fido2ClientService = fido2ClientService;
|
||||
_cipherService = cipherService;
|
||||
}
|
||||
|
||||
public async Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams)
|
||||
{
|
||||
var result = await _fido2ClientService.AssertCredentialAsync(assertCredentialParams);
|
||||
|
||||
if (result?.Cipher != null)
|
||||
{
|
||||
await _cipherService.CopyTotpCodeIfNeededAsync(result.Cipher);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams)
|
||||
{
|
||||
return _fido2ClientService.CreateCredentialAsync(createCredentialParams);
|
||||
}
|
||||
|
||||
public async Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface)
|
||||
{
|
||||
var result = await _fido2AuthenticatorService.GetAssertionAsync(assertionParams, userInterface);
|
||||
|
||||
if (result?.SelectedCredential?.Cipher != null)
|
||||
{
|
||||
await _cipherService.CopyTotpCodeIfNeededAsync(result.SelectedCredential.Cipher);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface)
|
||||
{
|
||||
return _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, userInterface);
|
||||
}
|
||||
|
||||
public Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId)
|
||||
{
|
||||
return _fido2AuthenticatorService.SilentCredentialDiscoveryAsync(rpId);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/Core/Services/Logging/ClipLogger.cs
Normal file
66
src/Core/Services/Logging/ClipLogger.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
#if IOS
|
||||
using UIKit;
|
||||
#endif
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// This logger can be used to help debug iOS extensions where we cannot use the .NET debugger yet
|
||||
/// so we can use this that copies the logs to the clipboard so one
|
||||
/// can paste them and analyze its output.
|
||||
/// </summary>
|
||||
public class ClipLogger : ILogger
|
||||
{
|
||||
private static readonly StringBuilder _currentBreadcrumbs = new StringBuilder();
|
||||
|
||||
static ILogger _instance;
|
||||
public static ILogger Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance is null)
|
||||
{
|
||||
_instance = new ClipLogger();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected ClipLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Log(string breadcrumb)
|
||||
{
|
||||
_currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}");
|
||||
#if IOS
|
||||
MainThread.BeginInvokeOnMainThread(() => UIPasteboard.General.String = _currentBreadcrumbs.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Error(string message, IDictionary<string, string> extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}";
|
||||
var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}";
|
||||
var properties = new Dictionary<string, string>
|
||||
{
|
||||
["File"] = filePathAndLineNumber,
|
||||
["Method"] = memberName
|
||||
};
|
||||
|
||||
Log(message ?? $"Error found in: {classAndMethod}, {filePathAndLineNumber}");
|
||||
}
|
||||
|
||||
public void Exception(Exception ex) => Log(ex?.ToString());
|
||||
|
||||
public Task InitAsync() => Task.CompletedTask;
|
||||
|
||||
public Task<bool> IsEnabled() => Task.FromResult(true);
|
||||
|
||||
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@@ -22,9 +21,9 @@ namespace Bit.Core.Services
|
||||
#if !FDROID
|
||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
||||
// we need to track the error as well
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
//Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
ClipLogger.Log(ex?.ToString());
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Bit.App.Services
|
||||
return passwordValid;
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldByPassMasterPasswordRepromptAsync()
|
||||
public async Task<bool> ShouldByPassMasterPasswordRepromptAsync()
|
||||
{
|
||||
return await _cryptoService.GetMasterKeyHashAsync() is null;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Plugin.Fingerprint;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Microsoft.Maui.ApplicationModel.DataTransfer;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Devices;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
@@ -245,31 +238,34 @@ namespace Bit.App.Services
|
||||
return await stateService.IsAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
|
||||
Action fallback = null, bool logOutOnTooManyAttempts = false)
|
||||
public async Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
|
||||
Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
text = AppResources.BiometricsDirection;
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
|
||||
}
|
||||
#if IOS
|
||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
|
||||
#endif
|
||||
}
|
||||
var biometricRequest = new AuthenticationRequestConfiguration(AppResources.Bitwarden, text)
|
||||
{
|
||||
CancelTitle = AppResources.Cancel,
|
||||
FallbackTitle = fallbackText
|
||||
FallbackTitle = fallbackText,
|
||||
AllowAlternativeAuthentication = allowAlternativeAuthentication
|
||||
};
|
||||
var result = await CrossFingerprint.Current.AuthenticateAsync(biometricRequest);
|
||||
if (result.Authenticated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (result.Status == FingerprintAuthenticationResultStatus.Canceled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
||||
{
|
||||
fallback?.Invoke();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using PCLCrypto;
|
||||
using static PCLCrypto.WinRTCrypto;
|
||||
|
||||
|
||||
@@ -1688,6 +1688,11 @@ namespace Bit.Core.Services
|
||||
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
|
||||
}
|
||||
|
||||
public async Task ReloadStateAsync()
|
||||
{
|
||||
_state = await GetStateFromStorageAsync() ?? new State();
|
||||
}
|
||||
|
||||
private async Task CheckStateAsync()
|
||||
{
|
||||
if (!_migrationChecked)
|
||||
@@ -1699,7 +1704,7 @@ namespace Bit.Core.Services
|
||||
|
||||
if (_state == null)
|
||||
{
|
||||
_state = await GetStateFromStorageAsync() ?? new State();
|
||||
await ReloadStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
@@ -7,11 +8,25 @@ namespace Bit.App.Services
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
|
||||
public UserPinService(IStateService stateService, ICryptoService cryptoService)
|
||||
public UserPinService(IStateService stateService, ICryptoService cryptoService, IVaultTimeoutService vaultTimeoutService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_cryptoService = cryptoService;
|
||||
_vaultTimeoutService = vaultTimeoutService;
|
||||
}
|
||||
|
||||
public async Task<bool> IsPinLockEnabledAsync()
|
||||
{
|
||||
var pinLockType = await _vaultTimeoutService.GetPinLockTypeAsync();
|
||||
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
|
||||
return (pinLockType == PinLockType.Transient && ephemeralPinSet != null)
|
||||
||
|
||||
pinLockType == PinLockType.Persistent;
|
||||
}
|
||||
|
||||
public async Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart)
|
||||
@@ -34,5 +49,59 @@ namespace Bit.App.Services
|
||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyPinAsync(string inputPin)
|
||||
{
|
||||
var (email, kdfConfig) = await _stateService.GetActiveUserCustomDataAsync(a => a?.Profile is null ? (null, default) : (a.Profile.Email, new KdfConfig(a.Profile)));
|
||||
if (kdfConfig.Type is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await VerifyPinAsync(inputPin, email, kdfConfig, await _vaultTimeoutService.GetPinLockTypeAsync());
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyPinAsync(string inputPin, string email, KdfConfig kdfConfig, PinLockType pinLockType)
|
||||
{
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (pinLockType == PinLockType.Persistent)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (pinLockType == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
pinLockType == PinLockType.Transient,
|
||||
inputPin,
|
||||
email,
|
||||
kdfConfig,
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
inputPin,
|
||||
email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
|
||||
return decryptedPin == inputPin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Services.UserVerification
|
||||
{
|
||||
public class Fido2UserVerificationPreferredServiceStrategy : IUserVerificationServiceStrategy
|
||||
{
|
||||
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
||||
|
||||
public Fido2UserVerificationPreferredServiceStrategy(IUserVerificationMediatorService userVerificationMediatorService)
|
||||
{
|
||||
_userVerificationMediatorService = userVerificationMediatorService;
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
|
||||
{
|
||||
if (options.HasVaultBeenUnlockedInTransaction)
|
||||
{
|
||||
return new CancellableResult<bool>(true);
|
||||
}
|
||||
|
||||
if (options.OnNeedUITask != null)
|
||||
{
|
||||
await options.OnNeedUITask();
|
||||
}
|
||||
|
||||
var osUnlockVerification = await _userVerificationMediatorService.PerformOSUnlockAsync();
|
||||
if (osUnlockVerification.IsCancelled)
|
||||
{
|
||||
return new CancellableResult<bool>(false, true);
|
||||
}
|
||||
if (osUnlockVerification.Result.CanPerform)
|
||||
{
|
||||
return new CancellableResult<bool>(osUnlockVerification.Result.IsVerified);
|
||||
}
|
||||
|
||||
return new CancellableResult<bool>(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Services.UserVerification
|
||||
{
|
||||
public class Fido2UserVerificationRequiredServiceStrategy : IUserVerificationServiceStrategy
|
||||
{
|
||||
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
|
||||
public Fido2UserVerificationRequiredServiceStrategy(IUserVerificationMediatorService userVerificationMediatorService,
|
||||
IPlatformUtilsService platformUtilsService)
|
||||
{
|
||||
_userVerificationMediatorService = userVerificationMediatorService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
|
||||
{
|
||||
if (options.HasVaultBeenUnlockedInTransaction)
|
||||
{
|
||||
return new CancellableResult<bool>(true);
|
||||
}
|
||||
|
||||
if (options.OnNeedUITask != null)
|
||||
{
|
||||
await options.OnNeedUITask();
|
||||
}
|
||||
|
||||
var osUnlockVerification = await _userVerificationMediatorService.PerformOSUnlockAsync();
|
||||
if (osUnlockVerification.IsCancelled)
|
||||
{
|
||||
return new CancellableResult<bool>(false, true);
|
||||
}
|
||||
if (osUnlockVerification.Result.CanPerform)
|
||||
{
|
||||
return new CancellableResult<bool>(osUnlockVerification.Result.IsVerified);
|
||||
}
|
||||
|
||||
var pinVerification = await _userVerificationMediatorService.VerifyPinCodeAsync();
|
||||
if (pinVerification.IsCancelled)
|
||||
{
|
||||
return new CancellableResult<bool>(false, true);
|
||||
}
|
||||
if (pinVerification.Result.CanPerform)
|
||||
{
|
||||
return new CancellableResult<bool>(pinVerification.Result.IsVerified);
|
||||
}
|
||||
|
||||
var mpVerification = await _userVerificationMediatorService.VerifyMasterPasswordAsync(false);
|
||||
if (mpVerification.IsCancelled)
|
||||
{
|
||||
return new CancellableResult<bool>(false, true);
|
||||
}
|
||||
if (mpVerification.Result.CanPerform)
|
||||
{
|
||||
return new CancellableResult<bool>(mpVerification.Result.IsVerified);
|
||||
}
|
||||
|
||||
// TODO: Setup PIN code. For the sake of simplicity, we're not implementing this step now and just telling the user to do it in the main app.
|
||||
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationRequiredForThisActionSetUpAnUnlockMethodInBitwardenToContinue,
|
||||
string.Format(AppResources.VerificationRequiredByX, options.RpId),
|
||||
AppResources.Ok);
|
||||
|
||||
return new CancellableResult<bool>(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
|
||||
namespace Bit.Core.Services.UserVerification
|
||||
{
|
||||
public interface IUserVerificationServiceStrategy
|
||||
{
|
||||
Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Plugin.Fingerprint;
|
||||
using static Bit.Core.Abstractions.IUserVerificationMediatorService;
|
||||
using FingerprintAvailability = Plugin.Fingerprint.Abstractions.FingerprintAvailability;
|
||||
|
||||
namespace Bit.Core.Services.UserVerification
|
||||
{
|
||||
public class UserVerificationMediatorService : IUserVerificationMediatorService
|
||||
{
|
||||
private const byte MAX_ATTEMPTS = 5;
|
||||
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IUserPinService _userPinService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
|
||||
private readonly Dictionary<Fido2UserVerificationPreference, IUserVerificationServiceStrategy> _fido2UserVerificationStrategies = new Dictionary<Fido2UserVerificationPreference, IUserVerificationServiceStrategy>();
|
||||
|
||||
public UserVerificationMediatorService(
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IPasswordRepromptService passwordRepromptService,
|
||||
IUserPinService userPinService,
|
||||
IDeviceActionService deviceActionService,
|
||||
IUserVerificationService userVerificationService)
|
||||
{
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_passwordRepromptService = passwordRepromptService;
|
||||
_userPinService = userPinService;
|
||||
_deviceActionService = deviceActionService;
|
||||
_userVerificationService = userVerificationService;
|
||||
|
||||
_fido2UserVerificationStrategies.Add(Fido2UserVerificationPreference.Required, new Fido2UserVerificationRequiredServiceStrategy(this, _platformUtilsService));
|
||||
_fido2UserVerificationStrategies.Add(Fido2UserVerificationPreference.Preferred, new Fido2UserVerificationPreferredServiceStrategy(this));
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
|
||||
{
|
||||
if (await ShouldPerformMasterPasswordRepromptAsync(options))
|
||||
{
|
||||
if (options.OnNeedUITask != null)
|
||||
{
|
||||
await options.OnNeedUITask();
|
||||
}
|
||||
|
||||
var mpVerification = await VerifyMasterPasswordAsync(true);
|
||||
return new CancellableResult<bool>(
|
||||
!mpVerification.IsCancelled && mpVerification.Result.CanPerform && mpVerification.Result.IsVerified,
|
||||
mpVerification.IsCancelled
|
||||
);
|
||||
}
|
||||
|
||||
if (!_fido2UserVerificationStrategies.TryGetValue(options.UserVerificationPreference, out var userVerificationServiceStrategy))
|
||||
{
|
||||
return new CancellableResult<bool>(false, false);
|
||||
}
|
||||
|
||||
return await userVerificationServiceStrategy.VerifyUserForFido2Async(options);
|
||||
}
|
||||
|
||||
public async Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options)
|
||||
{
|
||||
if (await ShouldPerformMasterPasswordRepromptAsync(options))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return options.HasVaultBeenUnlockedInTransaction
|
||||
||
|
||||
await CrossFingerprint.Current.GetAvailabilityAsync() == FingerprintAvailability.Available
|
||||
||
|
||||
await CrossFingerprint.Current.GetAvailabilityAsync(true) == FingerprintAvailability.Available;
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options)
|
||||
{
|
||||
return options.ShouldCheckMasterPasswordReprompt && !await _passwordRepromptService.ShouldByPassMasterPasswordRepromptAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options)
|
||||
{
|
||||
switch (options.UserVerificationPreference)
|
||||
{
|
||||
case Fido2UserVerificationPreference.Required:
|
||||
return true;
|
||||
case Fido2UserVerificationPreference.Discouraged:
|
||||
return await ShouldPerformMasterPasswordRepromptAsync(options);
|
||||
default:
|
||||
return await CanPerformUserVerificationPreferredAsync(options);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<UVResult>> PerformOSUnlockAsync()
|
||||
{
|
||||
var availability = await CrossFingerprint.Current.GetAvailabilityAsync();
|
||||
if (availability == FingerprintAvailability.Available)
|
||||
{
|
||||
var isValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null);
|
||||
if (!isValid.HasValue)
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable(true);
|
||||
}
|
||||
return new UVResult(true, isValid.Value).AsCancellable();
|
||||
}
|
||||
|
||||
var alternativeAuthAvailability = await CrossFingerprint.Current.GetAvailabilityAsync(true);
|
||||
if (alternativeAuthAvailability == FingerprintAvailability.Available)
|
||||
{
|
||||
var isNonBioValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null, allowAlternativeAuthentication: true);
|
||||
if (!isNonBioValid.HasValue)
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable(true);
|
||||
}
|
||||
return new UVResult(true, isNonBioValid.Value).AsCancellable();
|
||||
}
|
||||
|
||||
return new UVResult(false, false).AsCancellable();
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<UVResult>> VerifyPinCodeAsync()
|
||||
{
|
||||
return await VerifyWithAttemptsAsync(async () =>
|
||||
{
|
||||
if (!await _userPinService.IsPinLockEnabledAsync())
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable();
|
||||
}
|
||||
|
||||
var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN,
|
||||
AppResources.VerifyPIN, null, AppResources.Ok, AppResources.Cancel, password: true);
|
||||
if (pin is null)
|
||||
{
|
||||
// cancelled by the user
|
||||
return new UVResult(true, false).AsCancellable(true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var isVerified = await _userPinService.VerifyPinAsync(pin);
|
||||
return new UVResult(true, isVerified).AsCancellable();
|
||||
}
|
||||
catch (SymmetricCryptoKey.ArgumentKeyNullException)
|
||||
{
|
||||
return new UVResult(true, false).AsCancellable();
|
||||
}
|
||||
catch (SymmetricCryptoKey.InvalidKeyOperationException)
|
||||
{
|
||||
return new UVResult(true, false).AsCancellable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt)
|
||||
{
|
||||
return await VerifyWithAttemptsAsync(async () =>
|
||||
{
|
||||
if (!await _userVerificationService.HasMasterPasswordAsync(true))
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable();
|
||||
}
|
||||
|
||||
var title = isMasterPasswordReprompt ? AppResources.PasswordConfirmation : AppResources.MasterPassword;
|
||||
var body = isMasterPasswordReprompt ? AppResources.PasswordConfirmationDesc : string.Empty;
|
||||
|
||||
var (password, isValid) = await _platformUtilsService.ShowPasswordDialogAndGetItAsync(title, body, _userVerificationService.VerifyMasterPasswordAsync);
|
||||
if (password is null)
|
||||
{
|
||||
return new UVResult(true, false).AsCancellable(true);
|
||||
}
|
||||
|
||||
return new UVResult(true, isValid).AsCancellable();
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<CancellableResult<UVResult>> VerifyWithAttemptsAsync(Func<Task<CancellableResult<UVResult>>> verifyAsync)
|
||||
{
|
||||
byte attempts = 0;
|
||||
do
|
||||
{
|
||||
var verification = await verifyAsync();
|
||||
if (verification.IsCancelled)
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable(true);
|
||||
}
|
||||
if (!verification.Result.CanPerform)
|
||||
{
|
||||
return new UVResult(false, false).AsCancellable();
|
||||
}
|
||||
if (verification.Result.IsVerified)
|
||||
{
|
||||
return new UVResult(true, true).AsCancellable();
|
||||
}
|
||||
} while (++attempts < MAX_ATTEMPTS);
|
||||
|
||||
return new UVResult(true, false).AsCancellable();
|
||||
}
|
||||
}
|
||||
|
||||
public static class UVResultExtensions
|
||||
{
|
||||
public static CancellableResult<UVResult> AsCancellable(this UVResult result, bool isCancelled = false)
|
||||
{
|
||||
return new CancellableResult<UVResult>(result, isCancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace Bit.Core.Services
|
||||
_keyConnectorService = keyConnectorService;
|
||||
}
|
||||
|
||||
async public Task<bool> VerifyUser(string secret, VerificationType verificationType)
|
||||
public async Task<bool> VerifyUser(string secret, VerificationType verificationType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
{
|
||||
@@ -61,6 +61,12 @@ namespace Bit.Core.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyMasterPasswordAsync(string masterPassword)
|
||||
{
|
||||
var masterKey = await _cryptoService.GetOrDeriveMasterKeyAsync(masterPassword);
|
||||
return await _cryptoService.CompareAndUpdateKeyHashAsync(masterPassword, masterKey);
|
||||
}
|
||||
|
||||
async private Task InvalidSecretErrorAsync(VerificationType verificationType)
|
||||
{
|
||||
var errorMessage = verificationType == VerificationType.OTP
|
||||
|
||||
15
src/Core/Utilities/CancellableResult.cs
Normal file
15
src/Core/Utilities/CancellableResult.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public readonly struct CancellableResult<T>
|
||||
{
|
||||
public CancellableResult(T result, bool isCancelled = false)
|
||||
{
|
||||
Result = result;
|
||||
IsCancelled = isCancelled;
|
||||
}
|
||||
|
||||
public T Result { get; }
|
||||
|
||||
public bool IsCancelled { get; }
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,38 @@ namespace Bit.Core.Utilities
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the host (and not port) of the given uri.
|
||||
/// Does not support plain hostnames without a protocol.
|
||||
///
|
||||
/// Input => Output examples:
|
||||
/// <para>https://bitwarden.com => bitwarden.com</para>
|
||||
/// <para>https://login.bitwarden.com:1337 => login.bitwarden.com</para>
|
||||
/// <para>https://sub.login.bitwarden.com:1337 => sub.login.bitwarden.com</para>
|
||||
/// <para>https://localhost:8080 => localhost</para>
|
||||
/// <para>localhost => null</para>
|
||||
/// <para>bitwarden => null</para>
|
||||
/// <para>127.0.0.1 => 127.0.0.1</para>
|
||||
/// </summary>
|
||||
public static string GetHostname(string uriString)
|
||||
{
|
||||
var uri = GetUri(uriString);
|
||||
return string.IsNullOrEmpty(uri?.Host) ? null : uri.Host;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the host and port of the given uri.
|
||||
/// Does not support plain hostnames without
|
||||
///
|
||||
/// Input => Output examples:
|
||||
/// <para>https://bitwarden.com => bitwarden.com</para>
|
||||
/// <para>https://login.bitwarden.com:1337 => login.bitwarden.com:1337</para>
|
||||
/// <para>https://sub.login.bitwarden.com:1337 => sub.login.bitwarden.com:1337</para>
|
||||
/// <para>https://localhost:8080 => localhost:8080</para>
|
||||
/// <para>localhost => null</para>
|
||||
/// <para>bitwarden => null</para>
|
||||
/// <para>127.0.0.1 => 127.0.0.1</para>
|
||||
/// </summary>
|
||||
public static string GetHost(string uriString)
|
||||
{
|
||||
var uri = GetUri(uriString);
|
||||
@@ -61,6 +87,19 @@ namespace Bit.Core.Utilities
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the second and top level domain of the given uri.
|
||||
/// Does not support plain hostnames without
|
||||
///
|
||||
/// Input => Output examples:
|
||||
/// <para>https://bitwarden.com => bitwarden.com</para>
|
||||
/// <para>https://login.bitwarden.com:1337 => bitwarden.com</para>
|
||||
/// <para>https://sub.login.bitwarden.com:1337 => bitwarden.com</para>
|
||||
/// <para>https://localhost:8080 => localhost</para>
|
||||
/// <para>localhost => null</para>
|
||||
/// <para>bitwarden => null</para>
|
||||
/// <para>127.0.0.1 => 127.0.0.1</para>
|
||||
/// </summary>
|
||||
public static string GetDomain(string uriString)
|
||||
{
|
||||
var uri = GetUri(uriString);
|
||||
|
||||
18
src/Core/Utilities/Fido2/AuthenticatorSelectionCriteria.cs
Normal file
18
src/Core/Utilities/Fido2/AuthenticatorSelectionCriteria.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
#nullable enable
|
||||
/// <summary>
|
||||
/// The Relying Party's requirements of the authenticator used in the creation of the credential.
|
||||
/// </summary>
|
||||
public class AuthenticatorSelectionCriteria
|
||||
{
|
||||
public bool? RequireResidentKey { get; set; }
|
||||
public string? ResidentKey { get; set; }
|
||||
public string UserVerification { get; set; } = "preferred";
|
||||
|
||||
/// <summary>
|
||||
/// This member is intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the create() operation.
|
||||
/// </summary>
|
||||
// public AuthenticatorAttachment? AuthenticatorAttachment { get; set; } // not used
|
||||
}
|
||||
}
|
||||
8
src/Core/Utilities/Fido2/Fido2AlgorithmIdentifier.cs
Normal file
8
src/Core/Utilities/Fido2/Fido2AlgorithmIdentifier.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public enum Fido2AlgorithmIdentifier : int
|
||||
{
|
||||
ES256 = -7,
|
||||
RS256 = -257,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/// <summary>
|
||||
/// Represents the metadata of a discoverable credential for a FIDO2 authenticator.
|
||||
/// See: https://www.w3.org/TR/webauthn-3/#sctn-op-silent-discovery
|
||||
/// </summary>
|
||||
public class Fido2AuthenticatorDiscoverableCredentialMetadata
|
||||
{
|
||||
public string Type { get; set; }
|
||||
|
||||
public byte[] Id { get; set; }
|
||||
|
||||
public string RpId { get; set; }
|
||||
|
||||
public byte[] UserHandle { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
37
src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs
Normal file
37
src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public class Fido2AuthenticatorException : Exception
|
||||
{
|
||||
public Fido2AuthenticatorException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class NotAllowedError : Fido2AuthenticatorException
|
||||
{
|
||||
public NotAllowedError() : base("NotAllowedError")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class NotSupportedError : Fido2AuthenticatorException
|
||||
{
|
||||
public NotSupportedError() : base("NotSupportedError")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidStateError : Fido2AuthenticatorException
|
||||
{
|
||||
public InvalidStateError() : base("InvalidStateError")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UnknownError : Fido2AuthenticatorException
|
||||
{
|
||||
public UnknownError() : base("UnknownError")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public class Fido2AuthenticatorGetAssertionParams
|
||||
{
|
||||
/** The caller’s RP ID, as determined by the user agent and the client. */
|
||||
public string RpId { get; set; }
|
||||
|
||||
/** The hash of the serialized client data, provided by the client. */
|
||||
public byte[] Hash { get; set; }
|
||||
|
||||
public PublicKeyCredentialDescriptor[] AllowCredentialDescriptorList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Instructs the authenticator the user verification preference in order to complete the request. Examples of UV gestures are fingerprint scan or a PIN.
|
||||
/// </summary>
|
||||
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The challenge to be signed by the authenticator.
|
||||
/// </summary>
|
||||
public byte[] Challenge { get; set; }
|
||||
|
||||
public object Extensions { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public class Fido2AuthenticatorGetAssertionResult
|
||||
{
|
||||
public byte[] AuthenticatorData { get; set; }
|
||||
|
||||
public byte[] Signature { get; set; }
|
||||
|
||||
public Fido2AuthenticatorGetAssertionSelectedCredential SelectedCredential { get; set; }
|
||||
}
|
||||
|
||||
public class Fido2AuthenticatorGetAssertionSelectedCredential {
|
||||
public byte[] Id { get; set; }
|
||||
|
||||
#nullable enable
|
||||
public byte[]? UserHandle { get; set; }
|
||||
|
||||
public CipherView? Cipher { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public class Fido2AuthenticatorMakeCredentialParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The Relying Party's PublicKeyCredentialRpEntity.
|
||||
/// </summary>
|
||||
public PublicKeyCredentialRpEntity RpEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Relying Party's PublicKeyCredentialRpEntity.
|
||||
/// </summary>
|
||||
public PublicKeyCredentialUserEntity UserEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The hash of the serialized client data, provided by the client.
|
||||
/// </summary>
|
||||
public byte[] Hash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A sequence of pairs of PublicKeyCredentialType and public key algorithms (COSEAlgorithmIdentifier) requested by the Relying Party. This sequence is ordered from most preferred to least preferred. The authenticator makes a best-effort to create the most preferred credential that it can.
|
||||
/// </summary>
|
||||
public PublicKeyCredentialParameters[] CredTypesAndPubKeyAlgs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///An OPTIONAL list of PublicKeyCredentialDescriptor objects provided by the Relying Party with the intention that, if any of these are known to the authenticator, it SHOULD NOT create a new credential. excludeCredentialDescriptorList contains a list of known credentials.
|
||||
/// </summary>
|
||||
public PublicKeyCredentialDescriptor[] ExcludeCredentialDescriptorList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The effective resident key requirement for credential creation, a Boolean value determined by the client. Resident is synonymous with discoverable. */
|
||||
/// </summary>
|
||||
public bool RequireResidentKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The effective user verification preference for assertion, provided by the client.
|
||||
/// </summary>
|
||||
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CTAP2 authenticators support setting this to false, but we only support the WebAuthn authenticator model which does not have that option.
|
||||
/// </summary>
|
||||
// public bool RequireUserPresence { get; set; } // Always required
|
||||
|
||||
/// <summary>
|
||||
/// The authenticator's attestation preference, a string provided by the client. This is a hint that the client gives to the authenticator about what kind of attestation statement it would like. The authenticator makes a best-effort to satisfy the preference.
|
||||
/// Note: Attestation statements are not supported at this time.
|
||||
/// </summary>
|
||||
// public string AttestationPreference { get; set; }
|
||||
|
||||
public object Extensions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace Bit.Core.Utilities.Fido2
|
||||
{
|
||||
public class Fido2AuthenticatorMakeCredentialResult
|
||||
{
|
||||
public byte[] CredentialId { get; set; }
|
||||
|
||||
public byte[] AttestationObject { get; set; }
|
||||
|
||||
public byte[] AuthData { get; set; }
|
||||
|
||||
public byte[] PublicKey { get; set; }
|
||||
|
||||
public int PublicKeyAlgorithm { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user