1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-05 01:53:17 +00:00

Compare commits

..

21 Commits

Author SHA1 Message Date
Kyle Spearrin
02ad4adff5 support for aes gcm crypto 2021-05-05 15:02:19 -04:00
Matt Portune
faa6904ce3 update firebase messaging lib and implementation (#1383)
* update firebase messaging lib and implementation

* update clean-fdroid script for updated firebase
2021-05-03 13:36:09 -04:00
Matt Portune
c27da8e7c4 fix for disappearing share sheet (#1386) 2021-04-27 17:49:01 -04:00
Matt Gibson
3ef5ca9cc0 Limit file upload sizes to 100MB (#1385)
both iOS and Android are having trouble with the current method
of loading the entire file to memory, encrypting it, and sending
to azure in one go.

We will need to come up with a chunking scheme to support
larger files in the future
2021-04-27 15:14:54 -05:00
Vincent Salucci
2fbd3b4538 [Version] Bump to 2.10.0 (#1382) 2021-04-22 12:00:01 -05:00
laymanZ
b3b21ea6b1 add microsoft package to support beta/dev/canary (#1370)
* add microsoft package to support beta/dev/canary

* Add package name in the necessary file
2021-04-22 00:16:19 -04:00
Matt Gibson
a3b4ede8f3 Use CipherByteArray to signify encrypted byte[] (#1366)
* Use CipherByteArray to signify encrypted  byte[]

* Rename CipherString and CipherByteArray to EncString and EncByteArray
2021-04-21 15:27:14 -05:00
Matt Portune
10ea6a86e3 clear cache on logout (#1375) 2021-04-19 15:38:49 -04:00
Thomas Rittson
3b2b37b3b0 Use UserService to manage emailVerified (#1367) 2021-04-15 14:54:58 +10:00
Matt Gibson
75e27ffbe3 Move renew endpoint to fix overlapping endpoint issue (#1362) 2021-04-12 09:45:17 -05:00
Thomas Rittson
a2cff6da28 Require user to verify email to use file Send (#1360) 2021-04-08 06:42:00 +10:00
Daniel James Smith
fe80fd0ba1 Replaced appveyor build badge with one from Github Workflow (#1350)
* Deleted appveyor.yml

* Remove Include of appveyor.yml from sln-file

* Deleted ci-build-apks.ps1 referenced by appveyor.yml

* Replaced build badge in README.md
2021-04-05 07:28:37 -07:00
dldhk97
5c6b9fa471 Add support for Soul browser (#1348) 2021-04-01 14:01:21 -04:00
Matt Portune
d926565358 Share-to-Send for Android (#1343)
* Android implementation

* remove iOS attempt for now
2021-03-31 10:19:05 -04:00
Matt Gibson
ce0b8bc62d Attachment azure upload blobs (#1345)
* Update Size limits

* Add new Api paths for direct upload of Cipher Attachments

* Add Attachment upload to fileUploadService

* Save with direct upload and fallback to legacy uplaod

CipherID is required for direct upload to request an upload URL

* Inform on when to remove legacy code

* Test Attachment upload
2021-03-30 18:42:43 -05:00
Thomas Rittson
04aeddc5de Hide email address in Sends (#1340)
* Add HideEmail model properties and locale strings

* Fix UI strings

* Add HideEmail to SendService

* Add HideEmail option to UI

* Tidy up declarations

* Add Bitwarden Send translation warning
2021-03-29 12:01:42 -04:00
Matt Gibson
13ffbe911a Send azure upload (#1334)
* Add direct upload api endpoints

* Create azure upload service

* Update max file size

* Update send file upload test

* Move internationalization string to correct document

* Allow for one shot blob uploads

* Remove unused helper

* Use FileUploadService

Fallback to legacy method on old server implementations.
2021-03-29 09:45:04 -05:00
Jamal
ab04759b0e Add support for Styx browser. (#1336)
https://github.com/jamal2362/Styx
2021-03-23 15:09:32 -04:00
Stéphane Lenclud
798cfef391 Add support for Fulguris browser. (#1333) 2021-03-22 16:54:55 -04:00
Matt Portune
3b5cdfe03c bump to 2.9.2 (#1321) 2021-03-16 15:56:28 -04:00
Contribucious
63449a3832 [KnownUsernameField] Entries update (Amazon) (#1320) 2021-03-16 14:57:45 -04:00
94 changed files with 1377 additions and 618 deletions

View File

@@ -30,16 +30,6 @@ $xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable); $nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android"); $nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$firebaseReceiver1=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
$nsAndroid);
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
$firebaseReceiver2=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
$nsAndroid);
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
$xml.Save($androidManifest); $xml.Save($androidManifest);
Write-Output "########################################" Write-Output "########################################"
@@ -56,6 +46,10 @@ $firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns); "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode); $firebaseNode.ParentNode.RemoveChild($firebaseNode);
$daggerNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
$daggerNode.ParentNode.RemoveChild($daggerNode);
$safetyNetNode=$xml.SelectSingleNode(` $safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns); "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode); $safetyNetNode.ParentNode.RemoveChild($safetyNetNode);

View File

@@ -1,4 +1,4 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/mobile?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/mobile) [![Github Workflow build on master](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)

View File

@@ -1,146 +0,0 @@
image:
- Visual Studio 2019
- Ubuntu1804
branches:
except:
- l10n_master
- gh-pages
configuration: Release
stack: node 10
init:
- sh: |
if [ "${DEBUG_SSH}" == "true" ]
then
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
fi
- ps: |
if($isWindows -and $env:DEBUG_RDP -eq "true") {
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
- ps: |
if($env:APPVEYOR_REPO_TAG -eq "true") {
$tagName = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
$env:RELEASE_NAME = "Version ${tagName}"
}
install:
- sh: |
curl -sflL 'https://raw.githubusercontent.com/appveyor/secure-file/master/install.sh' | bash -e -
./appveyor-tools/secure-file -decrypt ./store/fdroid/keystore.jks.enc -secret $FDROID_KEYSTORE_ENC_PASSWORD
- sh: npm install
- sh: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
then
git config --global credential.helper store
echo "https://${GH_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
fi
- cmd: choco install cloc --no-progress
- cmd: "cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML"
#- cmd: appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
#- cmd: appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
#- cmd: vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
#- cmd: ps: .\src\Android\update-android.ps1
before_build:
- ps: |
if($isWindows) {
nuget restore
if($env:UPLOAD_KEYSTORE_DEC_SECRET -or$env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
nuget install secure-file -ExcludeVersion
}
if($env:GOOGLE_SERVICES_DEC_SECRET) {
secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc `
-secret $env:GOOGLE_SERVICES_DEC_SECRET
}
}
build_script:
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" ]
then
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$APPVEYOR_BUILD_FOLDER/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
https://github.com/bitwarden/mobile/releases/download/$APPVEYOR_REPO_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $APPVEYOR_BUILD_FOLDER
fi
- ps: |
if($isWindows -and $env:KEYSTORE_DEC_SECRET) {
msbuild bitwarden-mobile.sln `
"/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
"/p:Configuration=Release"
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
.\src\Android\ci-build-apks.ps1
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
Push-AppveyorArtifact .\com.x8bit.bitwarden.aab
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
}
on_success:
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
then
npm run deploy
fi
- ps: |
if($isWindows -and $env:PLAY_DEC_SECRET -and $env:APPVEYOR_REPO_BRANCH -eq 'master') {
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
cd store\google\Publisher\bin\Release\netcoreapp2.0
dotnet Publisher.dll `
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.aab `
alpha
cd $env:APPVEYOR_BUILD_FOLDER
}
on_finish:
- sh: |
if [ "${DEBUG_SSH}" == "true" ]
then
export APPVEYOR_SSH_BLOCK=true
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
fi
- ps: |
if($isWindows -and $env:DEBUG_RDP -eq "true") {
$blockRdp = $true
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
deploy:
tag: $(APPVEYOR_REPO_TAG_NAME)
release: $(RELEASE_NAME)
provider: GitHub
auth_token: $(GH_TOKEN)
artifact: /.*/
force_update: true
on:
branch: master
APPVEYOR_REPO_TAG: true

View File

@@ -23,7 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
.gitignore = .gitignore .gitignore = .gitignore
appveyor.yml = appveyor.yml
.github\workflows\build.yml = .github\workflows\build.yml .github\workflows\build.yml = .github\workflows\build.yml
CONTRIBUTING.md = CONTRIBUTING.md CONTRIBUTING.md = CONTRIBUTING.md
crowdin.yml = crowdin.yml crowdin.yml = crowdin.yml

View File

@@ -50,10 +50,15 @@ namespace Bit.Droid.Accessibility
new Browser("com.ecosia.android", "url_bar"), new Browser("com.ecosia.android", "url_bar"),
new Browser("com.google.android.apps.chrome", "url_bar"), new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"), new Browser("com.google.android.apps.chrome_dev", "url_bar"),
new Browser("com.jamal2367.styx", "search"),
new Browser("com.kiwibrowser.browser", "url_bar"), new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.microsoft.emmx", "url_bar"), new Browser("com.microsoft.emmx", "url_bar"),
new Browser("com.microsoft.emmx.beta", "url_bar"),
new Browser("com.microsoft.emmx.canary", "url_bar"),
new Browser("com.microsoft.emmx.dev", "url_bar"),
new Browser("com.mmbox.browser", "search_box"), new Browser("com.mmbox.browser", "search_box"),
new Browser("com.mmbox.xbrowser", "search_box"), new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"), new Browser("com.naver.whale", "url_bar"),
new Browser("com.opera.browser", "url_field"), new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"), new Browser("com.opera.browser.beta", "url_field"),
@@ -77,6 +82,10 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"), new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"), new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"), new Browser("mark.via.gp", "as"),
new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "search"),
new Browser("net.slions.fulguris.full.playstore", "search"),
new Browser("net.slions.fulguris.full.playstore.debug", "search"),
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2) new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2) new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.bromite.bromite", "url_bar"), new Browser("org.bromite.bromite", "url_bar"),
@@ -182,7 +191,9 @@ namespace Bit.Droid.Accessibility
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }), new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }), new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }), new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.pl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }), new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.se", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }), new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
// Amazon Web Services // Amazon Web Services

View File

@@ -82,7 +82,7 @@
<Version>1.5.3.2</Version> <Version>1.5.3.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging"> <PackageReference Include="Xamarin.Firebase.Messaging">
<Version>71.1740.0</Version> <Version>121.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" /> <PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" /> <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
@@ -90,6 +90,7 @@
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" /> <PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" /> <PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" /> <PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.27.0" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet"> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>71.1600.0</Version> <Version>71.1600.0</Version>
</PackageReference> </PackageReference>
@@ -113,7 +114,6 @@
<Compile Include="Effects\FixedSizeEffect.cs" /> <Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" /> <Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" /> <Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Push\FirebaseInstanceIdService.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" /> <Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" /> <Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" /> <Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
@@ -154,7 +154,6 @@
<AndroidAsset Include="Assets\RobotoMono_Regular.ttf" /> <AndroidAsset Include="Assets\RobotoMono_Regular.ttf" />
<AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" /> <AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" />
<None Include="8bit.keystore.enc" /> <None Include="8bit.keystore.enc" />
<None Include="ci-build-apks.ps1" />
<GoogleServicesJson Include="google-services.json" /> <GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" /> <GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" /> <None Include="fdroid-keystore.jks.enc" />

View File

@@ -66,10 +66,15 @@ namespace Bit.Droid.Autofill
"com.ecosia.android", "com.ecosia.android",
"com.google.android.apps.chrome", "com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev", "com.google.android.apps.chrome_dev",
"com.jamal2367.styx",
"com.kiwibrowser.browser", "com.kiwibrowser.browser",
"com.microsoft.emmx", "com.microsoft.emmx",
"com.microsoft.emmx.beta",
"com.microsoft.emmx.canary",
"com.microsoft.emmx.dev",
"com.mmbox.browser", "com.mmbox.browser",
"com.mmbox.xbrowser", "com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser",
"com.naver.whale", "com.naver.whale",
"com.opera.browser", "com.opera.browser",
"com.opera.browser.beta", "com.opera.browser.beta",
@@ -92,6 +97,10 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven", "io.github.forkmaintainers.iceraven",
"mark.via", "mark.via",
"mark.via.gp", "mark.via.gp",
"net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore",
"net.slions.fulguris.full.playstore.debug",
"org.adblockplus.browser", "org.adblockplus.browser",
"org.adblockplus.browser.beta", "org.adblockplus.browser.beta",
"org.bromite.bromite", "org.bromite.bromite",

View File

@@ -30,6 +30,16 @@ namespace Bit.Droid
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation)] ConfigChanges.Navigation)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
DataMimeTypes = new[]
{
@"application/*",
@"image/*",
@"video/*",
@"text/*"
})]
[Register("com.x8bit.bitwarden.MainActivity")] [Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{ {
@@ -169,7 +179,7 @@ namespace Bit.Droid
_appOptions.GeneratorTile = true; _appOptions.GeneratorTile = true;
} }
} }
if (intent.GetBooleanExtra("myVaultTile", false)) else if (intent.GetBooleanExtra("myVaultTile", false))
{ {
_messagingService.Send("popAllAndGoToTabMyVault"); _messagingService.Send("popAllAndGoToTabMyVault");
if (_appOptions != null) if (_appOptions != null)
@@ -177,6 +187,14 @@ namespace Bit.Droid
_appOptions.MyVaultTile = true; _appOptions.MyVaultTile = true;
} }
} }
else if (intent.Action == Intent.ActionSend && intent.Type != null)
{
if (_appOptions != null)
{
_appOptions.CreateSend = GetCreateSendRequest(intent);
}
_messagingService.Send("popAllAndGoToTabSend");
}
else else
{ {
ParseYubiKey(intent.DataString); ParseYubiKey(intent.DataString);
@@ -298,7 +316,8 @@ namespace Bit.Droid
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"), Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false), MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false), GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false) FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
CreateSend = GetCreateSendRequest(Intent)
}; };
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0); var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if (fillType > 0) if (fillType > 0)
@@ -320,6 +339,37 @@ namespace Bit.Droid
return options; return options;
} }
private Tuple<SendType, string, byte[], string> GetCreateSendRequest(Intent intent)
{
if (intent.Action == Intent.ActionSend && intent.Type != null)
{
var type = intent.Type;
if (type.Contains("text/"))
{
var subject = intent.GetStringExtra(Intent.ExtraSubject);
var text = intent.GetStringExtra(Intent.ExtraText);
return new Tuple<SendType, string, byte[], string>(SendType.Text, subject, null, text);
}
else
{
var data = intent.ClipData?.GetItemAt(0);
var uri = data?.Uri;
var filename = AndroidHelpers.GetFileName(ApplicationContext, uri);
try
{
using (var stream = ContentResolver.OpenInputStream(uri))
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return new Tuple<SendType, string, byte[], string>(SendType.File, filename, memoryStream.ToArray(), null);
}
}
catch (Java.IO.FileNotFoundException) { }
}
}
return null;
}
private void ParseYubiKey(string data) private void ParseYubiKey(string data)
{ {
if (data == null) if (data == null)

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1" android:versionCode="1"
android:versionName="2.9.1" android:versionName="2.10.0"
android:installLocation="internalOnly" android:installLocation="internalOnly"
package="com.x8bit.bitwarden"> package="com.x8bit.bitwarden">
@@ -40,20 +40,6 @@
android:resource="@xml/filepaths" /> android:resource="@xml/filepaths" />
</provider> </provider>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" />
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.x8bit.bitwarden" />
</intent-filter>
</receiver>
<meta-data android:name="android.max_aspect" android:value="2.1" /> <meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" /> <meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />

View File

@@ -1,25 +0,0 @@
#if !FDROID
using Android.App;
using Android.Content;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Firebase.Iid;
namespace Bit.Droid.Push
{
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
{
public async override void OnTokenRefresh()
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
await pushNotificationService.RegisterAsync();
}
}
}
#endif

View File

@@ -1,7 +1,7 @@
#if !FDROID #if !FDROID
using Android.App; using Android.App;
using Android.Content;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Firebase.Messaging; using Firebase.Messaging;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -10,10 +10,19 @@ using Xamarin.Forms;
namespace Bit.Droid.Push namespace Bit.Droid.Push
{ {
[Service] [Service(Exported=false)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })] [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
{ {
public async override void OnNewToken(string token)
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
await pushNotificationService.RegisterAsync();
}
public async override void OnMessageReceived(RemoteMessage message) public async override void OnMessageReceived(RemoteMessage message)
{ {
if (message?.Data == null) if (message?.Data == null)

View File

@@ -65,18 +65,33 @@
<compatibility-package <compatibility-package
android:name="com.google.android.apps.chrome_dev" android:name="com.google.android.apps.chrome_dev"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.jamal2367.styx"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.kiwibrowser.browser" android:name="com.kiwibrowser.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.microsoft.emmx" android:name="com.microsoft.emmx"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.beta"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.canary"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.dev"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.mmbox.browser" android:name="com.mmbox.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.mmbox.xbrowser" android:name="com.mmbox.xbrowser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.mycompany.app.soulbrowser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.naver.whale" android:name="com.naver.whale"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -143,6 +158,18 @@
<compatibility-package <compatibility-package
android:name="mark.via.gp" android:name="mark.via.gp"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.download"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.download.debug"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.playstore"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.playstore.debug"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="org.adblockplus.browser" android:name="org.adblockplus.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>

View File

@@ -1,5 +1,7 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Javax.Crypto;
using Javax.Crypto.Spec;
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Generators;
@@ -33,5 +35,25 @@ namespace Bit.Droid.Services
generator.Init(password, salt, iterations); generator.Init(password, salt, iterations);
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey(); return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
} }
public byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key)
{
var secKey = new SecretKeySpec(key, "AES");
var cipher = Cipher.GetInstance("AES/GCM/NoPadding");
var gcmSpec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.EncryptMode, secKey, gcmSpec);
var result = cipher.DoFinal(data);
return result;
}
public byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key)
{
var secKey = new SecretKeySpec(key, "AES");
var cipher = Cipher.GetInstance("AES/GCM/NoPadding");
var gcmSpec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.DecryptMode, secKey, gcmSpec);
var result = cipher.DoFinal(data);
return result;
}
} }
} }

View File

@@ -760,6 +760,17 @@ namespace Bit.Droid.Services
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime() // ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime(); return SystemClock.ElapsedRealtime();
} }
public void CloseMainApp()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return;
}
activity.Finish();
_messagingService.Send("finishMainActivity");
}
private bool DeleteDir(Java.IO.File dir) private bool DeleteDir(Java.IO.File dir)
{ {

View File

@@ -1,166 +0,0 @@
$rootPath = $env:APPVEYOR_BUILD_FOLDER;
$androidPath = $($rootPath + "\src\Android\Android.csproj");
$appPath = $($rootPath + "\src\App\App.csproj");
echo "########################################"
echo "##### Increment Version"
echo "########################################"
$androidManifest = $($rootPath + "\src\Android\Properties\AndroidManifest.xml");
$xml=New-Object XML;
$xml.Load($androidManifest);
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", $env:APPVEYOR_BUILD_NUMBER);
$xml.Save($androidManifest);
echo "########################################"
echo "##### Decrypt Keystore"
echo "########################################"
$encKeystorePath = $($rootPath + "\src\Android\8bit.keystore.enc");
$encFdroidKeystorePath = $($rootPath + "\src\Android\fdroid-keystore.jks.enc");
$encUploadKeystorePath = $($rootPath + "\src\Android\upload-keystore.jks.enc");
$secureFilePath = $($rootPath + "\secure-file\tools\secure-file.exe");
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encKeystorePath) -secret $($env:keystore_dec_secret)"
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encFdroidKeystorePath) -secret $($env:fdroid_apk_keystore_dec_secret)"
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encUploadKeystorePath) -secret $($env:upload_keystore_dec_secret)"
echo "########################################"
echo "##### Sign Google Play Bundle Release Configuration"
echo "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:upload_keystore_password)" `
"/p:AndroidSigningKeyStore=upload-keystore.jks" "/p:AndroidSigningStorePass=$($env:upload_keystore_password)" `
"/p:AndroidPackageFormat=aab" "/v:quiet"
echo "########################################"
echo "##### Copy Google Play Bundle to project root"
echo "########################################"
$signedAabPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($rootPath + "\com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
echo "########################################"
echo "##### Sign APK Release Configuration"
echo "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:keystore_password)" `
"/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=$($env:keystore_password)" "/v:quiet"
echo "########################################"
echo "##### Copy Release APK to project root"
echo "########################################"
$signedApkPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath
echo "########################################"
echo "##### Clean Android and App"
echo "########################################"
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
echo "########################################"
echo "##### Backup project files"
echo "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
echo "########################################"
echo "##### Cleanup Android Manifest"
echo "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$firebaseReceiver1=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
$nsAndroid);
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
$firebaseReceiver2=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
$nsAndroid);
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
$xml.Save($androidManifest);
echo "########################################"
echo "##### Uninstall from Android.csproj"
echo "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
$firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
$xml.Save($androidPath);
echo "########################################"
echo "##### Uninstall from App.csproj"
echo "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
echo "########################################"
echo "##### Restore NuGet"
echo "########################################"
Invoke-Expression "& nuget restore"
echo "########################################"
echo "##### Build and Sign FDroid Configuration"
echo "########################################"
msbuild "$($androidPath)" "/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
"/p:Configuration=FDroid"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:fdroid_apk_keystore_password)" `
"/p:AndroidSigningKeyStore=fdroid-keystore.jks" "/p:AndroidSigningStorePass=$($env:fdroid_apk_keystore_password)" `
"/v:quiet"
echo "########################################"
echo "##### Copy FDroid apk to project root"
echo "########################################"
$signedApkPath = $($rootPath + "\src\Android\bin\FDroid\com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
echo "########################################"
echo "##### Done"
echo "########################################"

View File

@@ -44,5 +44,6 @@ namespace Bit.App.Abstractions
void OpenAutofillSettings(); void OpenAutofillSettings();
bool UsingDarkTheme(); bool UsingDarkTheme();
long GetActiveTime(); long GetActiveTime();
void CloseMainApp();
} }
} }

View File

@@ -131,7 +131,8 @@ namespace Bit.App
await SetMainPageAsync(); await SetMainPageAsync();
} }
else if (message.Command == "popAllAndGoToTabGenerator" || else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault") message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend")
{ {
Device.BeginInvokeOnMainThread(async () => Device.BeginInvokeOnMainThread(async () =>
{ {
@@ -146,11 +147,15 @@ namespace Bit.App
Options.MyVaultTile = false; Options.MyVaultTile = false;
tabsPage.ResetToVaultPage(); tabsPage.ResetToVaultPage();
} }
else else if (message.Command == "popAllAndGoToTabGenerator")
{ {
Options.GeneratorTile = false; Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage(); tabsPage.ResetToGeneratorPage();
} }
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
} }
}); });
} }
@@ -244,7 +249,8 @@ namespace Bit.App
_collectionService.ClearAsync(userId), _collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(), _passwordGenerationService.ClearAsync(),
_vaultTimeoutService.ClearAsync(), _vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync()); _stateService.PurgeAsync(),
_deviceActionService.ClearCacheAsync());
_vaultTimeoutService.BiometricLocked = true; _vaultTimeoutService.BiometricLocked = true;
_searchService.ClearIndex(); _searchService.ClearIndex();
_authService.LogOut(() => _authService.LogOut(() =>
@@ -274,6 +280,10 @@ namespace Bit.App
{ {
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options)); Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
} }
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else else
{ {
Current.MainPage = new TabsPage(Options); Current.MainPage = new TabsPage(Options);

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums; using System;
using Bit.Core.Enums;
namespace Bit.App.Models namespace Bit.App.Models
{ {
@@ -19,6 +20,7 @@ namespace Bit.App.Models
public string SaveCardExpYear { get; set; } public string SaveCardExpYear { get; set; }
public string SaveCardCode { get; set; } public string SaveCardCode { get; set; }
public bool IosExtension { get; set; } public bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public void SetAllFrom(AppOptions o) public void SetAllFrom(AppOptions o)
{ {
@@ -41,6 +43,7 @@ namespace Bit.App.Models
SaveCardExpYear = o.SaveCardExpYear; SaveCardExpYear = o.SaveCardExpYear;
SaveCardCode = o.SaveCardCode; SaveCardCode = o.SaveCardCode;
IosExtension = o.IosExtension; IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
} }
} }
} }

View File

@@ -203,7 +203,7 @@ namespace Bit.App.Pages
_vaultTimeoutService.PinProtectedKey); _vaultTimeoutService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin; failed = decPin != Pin;
if (!failed) if (!failed)
{ {
@@ -272,7 +272,7 @@ namespace Bit.App.Pages
{ {
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);

View File

@@ -140,7 +140,7 @@ namespace Bit.App.Pages
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
Tuple<SymmetricCryptoKey, CipherString> encKey; Tuple<SymmetricCryptoKey, EncString> encKey;
var existingEncKey = await _cryptoService.GetEncKeyAsync(); var existingEncKey = await _cryptoService.GetEncKeyAsync();
if (existingEncKey == null) if (existingEncKey == null)
{ {

View File

@@ -89,6 +89,18 @@
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" /> HorizontalTextAlignment="Center" />
</Frame> </Frame>
<Frame
IsVisible="{Binding SendOptionsPolicyInEffect}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label
Text="{u:I18n Name}" Text="{u:I18n Name}"
@@ -105,7 +117,7 @@
</StackLayout> </StackLayout>
<StackLayout <StackLayout
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"> IsVisible="{Binding ShowTypeButtons}">
<Label <Label
Text="{u:I18n Type}" Text="{u:I18n Type}"
StyleClass="box-label" /> StyleClass="box-label" />
@@ -210,6 +222,7 @@
HorizontalTextAlignment="Center" /> HorizontalTextAlignment="Center" />
<Button <Button
Text="{u:I18n ChooseFile}" Text="{u:I18n ChooseFile}"
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}" IsEnabled="{Binding SendEnabled}"
StyleClass="box-button-row" StyleClass="box-button-row"
Clicked="ChooseFile_Clicked" /> Clicked="ChooseFile_Clicked" />
@@ -222,7 +235,7 @@
</StackLayout> </StackLayout>
<Label <Label
Text="{u:I18n TypeFileInfo}" Text="{u:I18n TypeFileInfo}"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowTypeButtons}"
StyleClass="box-footer-label" StyleClass="box-footer-label"
Margin="0,5,0,0" /> Margin="0,5,0,0" />
</StackLayout> </StackLayout>
@@ -490,6 +503,20 @@
StyleClass="box-footer-label" StyleClass="box-footer-label"
Margin="0,5,0,0" /> Margin="0,5,0,0" />
</StackLayout> </StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,5,0,0">
<Label
Text="{u:I18n HideEmail}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Send.HideEmail}"
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
<StackLayout <StackLayout
StyleClass="box-row, box-row-switch" StyleClass="box-row, box-row-switch"
Margin="0,5,0,0"> Margin="0,5,0,0">
@@ -513,4 +540,4 @@
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -15,18 +17,21 @@ namespace Bit.App.Pages
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm; private SendAddEditPageViewModel _vm;
public SendAddEditPage( public SendAddEditPage(
AppOptions appOptions = null,
string sendId = null, string sendId = null,
SendType? type = null) SendType? type = null)
{ {
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as SendAddEditPageViewModel; _vm = BindingContext as SendAddEditPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.SendId = sendId; _vm.SendId = sendId;
_vm.Type = type; _vm.Type = appOptions?.CreateSend?.Item1 ?? type;
SetActivityIndicator(); SetActivityIndicator();
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@@ -95,6 +100,7 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
return; return;
} }
await HandleCreateRequest();
if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name)) if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
{ {
RequestFocus(_nameEntry); RequestFocus(_nameEntry);
@@ -271,5 +277,33 @@ namespace Bit.App.Pages
ToolbarItems.Remove(_shareLink); ToolbarItems.Remove(_shareLink);
} }
} }
private async Task HandleCreateRequest()
{
if (_appOptions?.CreateSend == null)
{
return;
}
_vm.IsAddFromShare = true;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
var type = _appOptions.CreateSend.Item1;
if (type == SendType.File)
{
_vm.FileData = _appOptions.CreateSend.Item3;
_vm.FileName = name;
FileType_Clicked(null, null);
}
else
{
var text = _appOptions.CreateSend.Item4;
_vm.Send.Text.Text = text;
TextType_Clicked(null, null);
}
_appOptions.CreateSend = null;
}
} }
} }

View File

@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private readonly ISendService _sendService; private readonly ISendService _sendService;
private bool _sendEnabled; private bool _sendEnabled;
private bool _canAccessPremium; private bool _canAccessPremium;
private bool _emailVerified;
private SendView _send; private SendView _send;
private string _fileName; private string _fileName;
private bool _showOptions; private bool _showOptions;
@@ -42,6 +43,9 @@ namespace Bit.App.Pages
nameof(IsText), nameof(IsText),
nameof(IsFile), nameof(IsFile),
}; };
private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect;
private bool _disableHideEmailControl;
public SendAddEditPageViewModel() public SendAddEditPageViewModel()
{ {
@@ -91,6 +95,8 @@ namespace Bit.App.Pages
public byte[] FileData { get; set; } public byte[] FileData { get; set; }
public string NewPassword { get; set; } public string NewPassword { get; set; }
public bool ShareOnSave { get; set; } public bool ShareOnSave { get; set; }
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public List<KeyValuePair<string, SendType>> TypeOptions { get; } public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; } public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; } public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@@ -194,6 +200,17 @@ namespace Bit.App.Pages
nameof(ShowPasswordIcon) nameof(ShowPasswordIcon)
}); });
} }
public bool DisableHideEmail
{
get => _disableHideEmail;
set => SetProperty(ref _disableHideEmail, value);
}
public bool SendOptionsPolicyInEffect
{
get => _sendOptionsPolicyInEffect;
set => SetProperty(ref _sendOptionsPolicyInEffect, value);
}
public bool ShowTypeButtons => !EditMode && !IsAddFromShare;
public bool EditMode => !string.IsNullOrWhiteSpace(SendId); public bool EditMode => !string.IsNullOrWhiteSpace(SendId);
public bool IsText => Send?.Type == SendType.Text; public bool IsText => Send?.Type == SendType.Text;
public bool IsFile => Send?.Type == SendType.File; public bool IsFile => Send?.Type == SendType.File;
@@ -205,7 +222,10 @@ namespace Bit.App.Pages
{ {
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend; PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync(); _canAccessPremium = await _userService.CanAccessPremiumAsync();
_emailVerified = await _userService.GetEmailVerifiedAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync(); SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
} }
public async Task<bool> LoadAsync() public async Task<bool> LoadAsync()
@@ -228,7 +248,7 @@ namespace Bit.App.Pages
} }
else else
{ {
var defaultType = _canAccessPremium ? SendType.File : SendType.Text; var defaultType = _canAccessPremium && _emailVerified ? SendType.File : SendType.Text;
Send = new SendView Send = new SendView
{ {
Type = Type.GetValueOrDefault(defaultType), Type = Type.GetValueOrDefault(defaultType),
@@ -243,6 +263,10 @@ namespace Bit.App.Pages
_isOverridingPickers = false; _isOverridingPickers = false;
} }
DisableHideEmailControl = !SendEnabled ||
(!EditMode && DisableHideEmail) ||
(EditMode && DisableHideEmail && !Send.HideEmail);
return true; return true;
} }
@@ -315,7 +339,12 @@ namespace Bit.App.Pages
{ {
if (!_canAccessPremium) if (!_canAccessPremium)
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired); await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
return false;
}
if (!_emailVerified)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
return false; return false;
} }
if (!EditMode) if (!EditMode)
@@ -354,10 +383,6 @@ namespace Bit.App.Pages
var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData); var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
await Page.Navigation.PopModalAsync();
if (Device.RuntimePlatform == Device.Android && IsFile) if (Device.RuntimePlatform == Device.Android && IsFile)
{ {
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418 // Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418
@@ -366,6 +391,21 @@ namespace Bit.App.Pages
_messagingService.Send("sendUpdated"); _messagingService.Send("sendUpdated");
} }
if (!ShareOnSave)
{
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
if (ShareOnSave) if (ShareOnSave)
{ {
var savedSend = await _sendService.GetAsync(sendId); var savedSend = await _sendService.GetAsync(sendId);
@@ -375,7 +415,7 @@ namespace Bit.App.Pages
await AppHelpers.ShareSendUrlAsync(savedSendView); await AppHelpers.ShareSendUrlAsync(savedSendView);
} }
} }
return true; return true;
} }
catch (ApiException e) catch (ApiException e)
@@ -412,11 +452,37 @@ namespace Bit.App.Pages
public async Task TypeChangedAsync(SendType type) public async Task TypeChangedAsync(SendType type)
{ {
if (!SendEnabled)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendDisabledWarning);
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
return;
}
if (Send != null) if (Send != null)
{ {
if (!EditMode && type == SendType.File && !_canAccessPremium) if (!EditMode && type == SendType.File && (!_canAccessPremium || !_emailVerified))
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired); if (!_canAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
}
else if (!_emailVerified)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
return;
}
type = SendType.Text; type = SendType.Text;
} }
Send.Type = type; Send.Type = type;

View File

@@ -19,10 +19,11 @@ namespace Bit.App.Pages
private readonly SendGroupingsPageViewModel _vm; private readonly SendGroupingsPageViewModel _vm;
private readonly string _pageName; private readonly string _pageName;
private AppOptions _appOptions;
private PreviousPageInfo _previousPage; private PreviousPageInfo _previousPage;
public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null, public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null,
PreviousPageInfo previousPage = null) AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{ {
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks); _pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent(); InitializeComponent();
@@ -35,6 +36,7 @@ namespace Bit.App.Pages
_vm.Page = this; _vm.Page = this;
_vm.MainPage = mainPage; _vm.MainPage = mainPage;
_vm.Type = type; _vm.Type = type;
_appOptions = appOptions;
_previousPage = previousPage; _previousPage = previousPage;
if (pageTitle != null) if (pageTitle != null)
{ {
@@ -109,8 +111,8 @@ namespace Bit.App.Pages
} }
} }
await ShowPreviousPageAsync();
AdjustToolbar(); AdjustToolbar();
await CheckAddRequest();
}, _mainContent); }, _mainContent);
} }
@@ -122,6 +124,18 @@ namespace Bit.App.Pages
_vm.DisableRefreshing(); _vm.DisableRefreshing();
} }
private async Task CheckAddRequest()
{
if (_appOptions?.CreateSend != null)
{
if (DoOnce())
{
var page = new SendAddEditPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e) private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
{ {
((ListView)sender).SelectedItem = null; ((ListView)sender).SelectedItem = null;
@@ -172,28 +186,11 @@ namespace Bit.App.Pages
{ {
if (DoOnce()) if (DoOnce())
{ {
var page = new SendAddEditPage(null, _vm.Type); var page = new SendAddEditPage(null, null, _vm.Type);
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }
private async Task ShowPreviousPageAsync()
{
if (_previousPage == null)
{
return;
}
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.SendId)));
}
else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.SendId)));
}
_previousPage = null;
}
private void AdjustToolbar() private void AdjustToolbar()
{ {
_addItem.IsEnabled = _vm.SendEnabled; _addItem.IsEnabled = _vm.SendEnabled;

View File

@@ -208,7 +208,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send) public async Task SelectSendAsync(SendView send)
{ {
var page = new SendAddEditPage(send.Id); var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page)); await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }

View File

@@ -113,7 +113,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send) public async Task SelectSendAsync(SendView send)
{ {
var page = new SendAddEditPage(send.Id); var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page)); await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }

View File

@@ -20,7 +20,7 @@ namespace Bit.App.Pages
}; };
Children.Add(_groupingsPage); Children.Add(_groupingsPage);
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true)) _sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
{ {
Title = AppResources.Send, Title = AppResources.Send,
IconImageSource = "paper_plane.png", IconImageSource = "paper_plane.png",
@@ -60,6 +60,10 @@ namespace Bit.App.Pages
{ {
appOptions.MyVaultTile = false; appOptions.MyVaultTile = false;
} }
else if (appOptions?.CreateSend != null)
{
ResetToSendPage();
}
} }
public void ResetToVaultPage() public void ResetToVaultPage()
@@ -71,6 +75,11 @@ namespace Bit.App.Pages
{ {
CurrentPage = _generatorPage; CurrentPage = _generatorPage;
} }
public void ResetToSendPage()
{
CurrentPage = _sendGroupingsPage;
}
protected async override void OnCurrentPageChanged() protected async override void OnCurrentPageChanged()
{ {

View File

@@ -477,7 +477,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading); await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
try try
{ {
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment, Cipher.OrganizationId); var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
if (data == null) if (data == null)
{ {

View File

@@ -3484,5 +3484,29 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("AboutSend", resourceCulture); return ResourceManager.GetString("AboutSend", resourceCulture);
} }
} }
public static string HideEmail {
get {
return ResourceManager.GetString("HideEmail", resourceCulture);
}
}
public static string SendOptionsPolicyInEffect {
get {
return ResourceManager.GetString("SendOptionsPolicyInEffect", resourceCulture);
}
}
public static string SendFilePremiumRequired {
get {
return ResourceManager.GetString("SendFilePremiumRequired", resourceCulture);
}
}
public static string SendFileEmailVerificationRequired {
get {
return ResourceManager.GetString("SendFileEmailVerificationRequired", resourceCulture);
}
}
} }
} }

View File

@@ -1972,4 +1972,19 @@
<value>About Send</value> <value>About Send</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment> <comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data> </data>
<data name="HideEmail" xml:space="preserve">
<value>Hide my email address from recipients.</value>
</data>
<data name="SendOptionsPolicyInEffect" xml:space="preserve">
<value>One or more organization policies are affecting your Send options.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="SendFilePremiumRequired" xml:space="preserve">
<value>Free accounts are restricted to sharing text only. A premium membership is required to use files with 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="SendFileEmailVerificationRequired" xml:space="preserve">
<value>You must verify your email to use files with Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
</root> </root>

View File

@@ -154,7 +154,7 @@ namespace Bit.App.Utilities
} }
else if (selection == AppResources.Edit) else if (selection == AppResources.Edit)
{ {
await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(send.Id))); await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(null, send.Id)));
} }
else if (selection == AppResources.CopyLink) else if (selection == AppResources.CopyLink)
{ {
@@ -311,6 +311,26 @@ namespace Bit.App.Utilities
}); });
} }
public static async Task<bool> IsHideEmailDisabledByPolicyAsync()
{
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var userService = ServiceContainer.Resolve<IUserService>("userService");
var policies = await policyService.GetAll(PolicyType.SendOptions);
var organizations = await userService.GetAllOrganizationAsync();
return organizations.Any(o =>
{
return o.Enabled &&
o.Status == OrganizationUserStatusType.Confirmed &&
o.UsePolicies &&
!o.canManagePolicies &&
policies.Any(p => p.OrganizationId == o.Id &&
p.Enabled &&
p.Data.ContainsKey("disableHideEmail") &&
(bool)p.Data["disableHideEmail"]);
});
}
public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService, public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService,
IDeviceActionService deviceActionService, IStorageService storageService) IDeviceActionService deviceActionService, IStorageService storageService)
{ {
@@ -389,6 +409,11 @@ namespace Bit.App.Utilities
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions)); Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions));
return true; return true;
} }
if (appOptions.CreateSend != null)
{
Application.Current.MainPage = new NavigationPage(new SendAddEditPage(appOptions));
return true;
}
} }
return false; return false;
} }

View File

@@ -46,9 +46,14 @@ namespace Bit.Core.Abstractions
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse); TRequest body, bool authed, bool hasResponse);
void SetUrls(EnvironmentUrls urls); void SetUrls(EnvironmentUrls urls);
Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data); [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request);
Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId);
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data, Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
string organizationId); string organizationId);
Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string id, string attachmentId);
Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data);
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username); Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request); Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request); Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
@@ -56,7 +61,11 @@ namespace Bit.Core.Abstractions
Task<SendResponse> GetSendAsync(string id); Task<SendResponse> GetSendAsync(string id);
Task<SendResponse> PostSendAsync(SendRequest request); Task<SendResponse> PostSendAsync(SendRequest request);
Task<SendFileUploadDataResponse> PostFileTypeSendAsync(SendRequest request);
Task PostSendFileAsync(string sendId, string fileId, MultipartFormDataContent data);
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data); Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data);
Task<SendFileUploadDataResponse> RenewFileUploadUrlAsync(string sendId, string fileId);
Task<SendResponse> PutSendAsync(string id, SendRequest request); Task<SendResponse> PutSendAsync(string id, SendRequest request);
Task<SendResponse> PutSendRemovePasswordAsync(string id); Task<SendResponse> PutSendRemovePasswordAsync(string id);
Task DeleteSendAsync(string id); Task DeleteSendAsync(string id);

View File

@@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions
{
public interface IAzureFileUploadService
{
Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback);
}
}

View File

@@ -35,7 +35,7 @@ namespace Bit.Core.Abstractions
Task UpdateLastUsedDateAsync(string id); Task UpdateLastUsedDateAsync(string id);
Task UpsertAsync(CipherData cipher); Task UpsertAsync(CipherData cipher);
Task UpsertAsync(List<CipherData> cipher); Task UpsertAsync(List<CipherData> cipher);
Task<byte[]> DownloadAndDecryptAttachmentAsync(AttachmentView attachment, string organizationId); Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
Task SoftDeleteWithServerAsync(string id); Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id); Task RestoreWithServerAsync(string id);
} }

View File

@@ -20,8 +20,8 @@ namespace Bit.Core.Abstractions
Task<byte[]> HashAsync(byte[] value, CryptoHashAlgorithm algorithm); Task<byte[]> HashAsync(byte[] value, CryptoHashAlgorithm algorithm);
Task<byte[]> HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm); Task<byte[]> HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm);
Task<bool> CompareAsync(byte[] a, byte[] b); Task<bool> CompareAsync(byte[] a, byte[] b);
Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key); Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode);
Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key); Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode);
Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey, CryptoHashAlgorithm algorithm); Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey, CryptoHashAlgorithm algorithm);
Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey, CryptoHashAlgorithm algorithm); Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey, CryptoHashAlgorithm algorithm);
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey); Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);

View File

@@ -5,5 +5,7 @@ namespace Bit.Core.Abstractions
public interface ICryptoPrimitiveService public interface ICryptoPrimitiveService
{ {
byte[] Pbkdf2(byte[] password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations); byte[] Pbkdf2(byte[] password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations);
byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key);
byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key);
} }
} }

View File

@@ -17,11 +17,11 @@ namespace Bit.Core.Abstractions
Task ClearOrgKeysAsync(bool memoryOnly = false); Task ClearOrgKeysAsync(bool memoryOnly = false);
Task ClearPinProtectedKeyAsync(); Task ClearPinProtectedKeyAsync();
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key); Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
Task<byte[]> DecryptToBytesAsync(CipherString cipherString, SymmetricCryptoKey key = null); Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
Task<string> DecryptToUtf8Async(CipherString cipherString, SymmetricCryptoKey key = null); Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
Task<CipherString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<CipherString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<byte[]> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null); Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null); Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<SymmetricCryptoKey> GetKeyAsync(); Task<SymmetricCryptoKey> GetKeyAsync();
@@ -33,17 +33,17 @@ namespace Bit.Core.Abstractions
Task<bool> HasEncKeyAsync(); Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key); Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key);
Task<bool> HasKeyAsync(); Task<bool> HasKeyAsync();
Task<Tuple<SymmetricCryptoKey, CipherString>> MakeEncKeyAsync(SymmetricCryptoKey key); Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,
CipherString protectedKeyCs = null); EncString protectedKeyEs = null);
Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null); Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations); Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations);
Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync(); Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial); Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<int> RandomNumberAsync(int min, int max); Task<int> RandomNumberAsync(int min, int max);
Task<Tuple<SymmetricCryptoKey, CipherString>> RemakeEncKeyAsync(SymmetricCryptoKey key); Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
Task<CipherString> RsaEncryptAsync(byte[] data, byte[] publicKey = null); Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Task SetEncKeyAsync(string encKey); Task SetEncKeyAsync(string encKey);
Task SetEncPrivateKeyAsync(string encPrivateKey); Task SetEncPrivateKeyAsync(string encPrivateKey);
Task SetKeyAsync(SymmetricCryptoKey key); Task SetKeyAsync(SymmetricCryptoKey key);

View File

@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
namespace Bit.Core.Abstractions {
public interface IFileUploadService {
Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, string fileName, EncByteArray encryptedFileData);
Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData);
}
}

View File

@@ -9,12 +9,12 @@ namespace Bit.Core.Abstractions
public interface ISendService public interface ISendService
{ {
void ClearCache(); void ClearCache();
Task<(Send send, byte[] encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password, Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password,
SymmetricCryptoKey key = null); SymmetricCryptoKey key = null);
Task<Send> GetAsync(string id); Task<Send> GetAsync(string id);
Task<List<Send>> GetAllAsync(); Task<List<Send>> GetAllAsync();
Task<List<SendView>> GetAllDecryptedAsync(); Task<List<SendView>> GetAllDecryptedAsync();
Task<string> SaveWithServerAsync(Send sendData, byte[] encryptedFileData); Task<string> SaveWithServerAsync(Send sendData, EncByteArray encryptedFileData);
Task UpsertAsync(params SendData[] send); Task UpsertAsync(params SendData[] send);
Task ReplaceAsync(Dictionary<string, SendData> sends); Task ReplaceAsync(Dictionary<string, SendData> sends);
Task ClearAsync(string userId); Task ClearAsync(string userId);

View File

@@ -17,10 +17,12 @@ namespace Bit.Core.Abstractions
Task<int?> GetKdfIterationsAsync(); Task<int?> GetKdfIterationsAsync();
Task<Organization> GetOrganizationAsync(string id); Task<Organization> GetOrganizationAsync(string id);
Task<string> GetSecurityStampAsync(); Task<string> GetSecurityStampAsync();
Task<bool> GetEmailVerifiedAsync();
Task<string> GetUserIdAsync(); Task<string> GetUserIdAsync();
Task<bool> IsAuthenticatedAsync(); Task<bool> IsAuthenticatedAsync();
Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations); Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations);
Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations); Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations);
Task SetSecurityStampAsync(string stamp); Task SetSecurityStampAsync(string stamp);
Task SetEmailVerifiedAsync(bool emailVerified);
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Bit.Core.Abstractions
{ {
public interface IVaultTimeoutService public interface IVaultTimeoutService
{ {
CipherString PinProtectedKey { get; set; } EncString PinProtectedKey { get; set; }
bool BiometricLocked { get; set; } bool BiometricLocked { get; set; }
Task CheckVaultTimeoutAsync(); Task CheckVaultTimeoutAsync();

View File

@@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum AesMode : byte
{
CBC = 0,
GCM = 1,
}
}

View File

@@ -8,6 +8,7 @@
Rsa2048_OaepSha256_B64 = 3, Rsa2048_OaepSha256_B64 = 3,
Rsa2048_OaepSha1_B64 = 4, Rsa2048_OaepSha1_B64 = 4,
Rsa2048_OaepSha256_HmacSha256_B64 = 5, Rsa2048_OaepSha256_HmacSha256_B64 = 5,
Rsa2048_OaepSha1_HmacSha256_B64 = 6 Rsa2048_OaepSha1_HmacSha256_B64 = 6,
AesGcm256_B64 = 7
} }
} }

View File

@@ -0,0 +1,9 @@
using System;
namespace Bit.Core.Enums
{
public enum FileUploadType
{
Direct = 0,
Azure = 1,
}
}

View File

@@ -9,5 +9,6 @@
RequireSso = 4, // Requires users to authenticate with SSO RequireSso = 4, // Requires users to authenticate with SSO
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
DisableSend = 6, // Disables the ability to create and edit Sends DisableSend = 6, // Disables the ability to create and edit Sends
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
} }
} }

View File

@@ -24,6 +24,7 @@ namespace Bit.Core.Models.Data
DeletionDate = response.DeletionDate; DeletionDate = response.DeletionDate;
Password = response.Password; Password = response.Password;
Disabled = response.Disabled; Disabled = response.Disabled;
HideEmail = response.HideEmail.GetValueOrDefault();
switch (Type) switch (Type)
{ {
@@ -54,5 +55,6 @@ namespace Bit.Core.Models.Data
public DateTime DeletionDate { get; set; } public DateTime DeletionDate { get; set; }
public string Password { get; set; } public string Password { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
public bool HideEmail { get; set; }
} }
} }

View File

@@ -30,8 +30,8 @@ namespace Bit.Core.Models.Domain
public string Url { get; set; } public string Url { get; set; }
public string Size { get; set; } public string Size { get; set; }
public string SizeName { get; set; } public string SizeName { get; set; }
public CipherString Key { get; set; } public EncString Key { get; set; }
public CipherString FileName { get; set; } public EncString FileName { get; set; }
public async Task<AttachmentView> DecryptAsync(string orgId) public async Task<AttachmentView> DecryptAsync(string orgId)
{ {

View File

@@ -24,12 +24,12 @@ namespace Bit.Core.Models.Domain
BuildDomainModel(this, obj, _map, alreadyEncrypted); BuildDomainModel(this, obj, _map, alreadyEncrypted);
} }
public CipherString CardholderName { get; set; } public EncString CardholderName { get; set; }
public CipherString Brand { get; set; } public EncString Brand { get; set; }
public CipherString Number { get; set; } public EncString Number { get; set; }
public CipherString ExpMonth { get; set; } public EncString ExpMonth { get; set; }
public CipherString ExpYear { get; set; } public EncString ExpYear { get; set; }
public CipherString Code { get; set; } public EncString Code { get; set; }
public Task<CardView> DecryptAsync(string orgId) public Task<CardView> DecryptAsync(string orgId)
{ {

View File

@@ -58,8 +58,8 @@ namespace Bit.Core.Models.Domain
public string Id { get; set; } public string Id { get; set; }
public string OrganizationId { get; set; } public string OrganizationId { get; set; }
public string FolderId { get; set; } public string FolderId { get; set; }
public CipherString Name { get; set; } public EncString Name { get; set; }
public CipherString Notes { get; set; } public EncString Notes { get; set; }
public Enums.CipherType Type { get; set; } public Enums.CipherType Type { get; set; }
public bool Favorite { get; set; } public bool Favorite { get; set; }
public bool OrganizationUseTotp { get; set; } public bool OrganizationUseTotp { get; set; }

View File

@@ -29,7 +29,7 @@ namespace Bit.Core.Models.Domain
public string Id { get; set; } public string Id { get; set; }
public string OrganizationId { get; set; } public string OrganizationId { get; set; }
public CipherString Name { get; set; } public EncString Name { get; set; }
public string ExternalId { get; set; } public string ExternalId { get; set; }
public bool ReadOnly { get; set; } public bool ReadOnly { get; set; }

View File

@@ -24,13 +24,13 @@ namespace Bit.Core.Models.Domain
else else
{ {
domainPropInfo.SetValue(domain, domainPropInfo.SetValue(domain,
dataObjProp != null ? new CipherString(dataObjProp as string) : null, null); dataObjProp != null ? new EncString(dataObjProp as string) : null, null);
} }
} }
} }
protected void BuildDataModel<D, O>(D domain, O dataObj, HashSet<string> map, protected void BuildDataModel<D, O>(D domain, O dataObj, HashSet<string> map,
HashSet<string> notCipherStringList = null) HashSet<string> notEncryptedStringList = null)
where D : Domain where D : Domain
where O : Data.Data where O : Data.Data
{ {
@@ -41,13 +41,13 @@ namespace Bit.Core.Models.Domain
var domainPropInfo = domainType.GetProperty(prop); var domainPropInfo = domainType.GetProperty(prop);
var domainProp = domainPropInfo.GetValue(domain); var domainProp = domainPropInfo.GetValue(domain);
var dataObjPropInfo = dataObjType.GetProperty(prop); var dataObjPropInfo = dataObjType.GetProperty(prop);
if (notCipherStringList?.Contains(prop) ?? false) if (notEncryptedStringList?.Contains(prop) ?? false)
{ {
dataObjPropInfo.SetValue(dataObj, domainProp, null); dataObjPropInfo.SetValue(dataObj, domainProp, null);
} }
else else
{ {
dataObjPropInfo.SetValue(dataObj, (domainProp as CipherString)?.EncryptedString, null); dataObjPropInfo.SetValue(dataObj, (domainProp as EncString)?.EncryptedString, null);
} }
} }
} }
@@ -62,7 +62,7 @@ namespace Bit.Core.Models.Domain
{ {
var domainPropInfo = domainType.GetProperty(propName); var domainPropInfo = domainType.GetProperty(propName);
string val = null; string val = null;
if (domainPropInfo.GetValue(domain) is CipherString domainProp) if (domainPropInfo.GetValue(domain) is EncString domainProp)
{ {
val = await domainProp.DecryptAsync(orgId, key); val = await domainProp.DecryptAsync(orgId, key);
} }

View File

@@ -0,0 +1,12 @@
namespace Bit.Core.Models.Domain
{
public class EncByteArray
{
public byte[] Buffer { get; }
public EncByteArray(byte[] encryptedByteArray)
{
Buffer = encryptedByteArray;
}
}
}

View File

@@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace Bit.Core.Models.Domain namespace Bit.Core.Models.Domain
{ {
public class CipherString public class EncString
{ {
private string _decryptedValue; private string _decryptedValue;
public CipherString(EncryptionType encryptionType, string data, string iv = null, string mac = null) public EncString(EncryptionType encryptionType, string data, string iv = null, string mac = null)
{ {
if (string.IsNullOrWhiteSpace(data)) if (string.IsNullOrWhiteSpace(data))
{ {
@@ -37,7 +37,7 @@ namespace Bit.Core.Models.Domain
Mac = mac; Mac = mac;
} }
public CipherString(string encryptedString) public EncString(string encryptedString)
{ {
if (string.IsNullOrWhiteSpace(encryptedString)) if (string.IsNullOrWhiteSpace(encryptedString))
{ {
@@ -73,6 +73,7 @@ namespace Bit.Core.Models.Domain
Mac = encPieces[2]; Mac = encPieces[2];
break; break;
case EncryptionType.AesCbc256_B64: case EncryptionType.AesCbc256_B64:
case EncryptionType.AesGcm256_B64:
if (encPieces.Length != 2) if (encPieces.Length != 2)
{ {
return; return;

View File

@@ -22,8 +22,8 @@ namespace Bit.Core.Models.Domain
BuildDomainModel(this, obj, _map, alreadyEncrypted); BuildDomainModel(this, obj, _map, alreadyEncrypted);
} }
public CipherString Name { get; set; } public EncString Name { get; set; }
public CipherString Value { get; set; } public EncString Value { get; set; }
public FieldType Type { get; set; } public FieldType Type { get; set; }
public Task<FieldView> DecryptAsync(string orgId) public Task<FieldView> DecryptAsync(string orgId)

View File

@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
} }
public string Id { get; set; } public string Id { get; set; }
public CipherString Name { get; set; } public EncString Name { get; set; }
public DateTime RevisionDate { get; set; } public DateTime RevisionDate { get; set; }
public Task<FolderView> DecryptAsync() public Task<FolderView> DecryptAsync()

View File

@@ -36,24 +36,24 @@ namespace Bit.Core.Models.Domain
BuildDomainModel(this, obj, _map, alreadyEncrypted); BuildDomainModel(this, obj, _map, alreadyEncrypted);
} }
public CipherString Title { get; set; } public EncString Title { get; set; }
public CipherString FirstName { get; set; } public EncString FirstName { get; set; }
public CipherString MiddleName { get; set; } public EncString MiddleName { get; set; }
public CipherString LastName { get; set; } public EncString LastName { get; set; }
public CipherString Address1 { get; set; } public EncString Address1 { get; set; }
public CipherString Address2 { get; set; } public EncString Address2 { get; set; }
public CipherString Address3 { get; set; } public EncString Address3 { get; set; }
public CipherString City { get; set; } public EncString City { get; set; }
public CipherString State { get; set; } public EncString State { get; set; }
public CipherString PostalCode { get; set; } public EncString PostalCode { get; set; }
public CipherString Country { get; set; } public EncString Country { get; set; }
public CipherString Company { get; set; } public EncString Company { get; set; }
public CipherString Email { get; set; } public EncString Email { get; set; }
public CipherString Phone { get; set; } public EncString Phone { get; set; }
public CipherString SSN { get; set; } public EncString SSN { get; set; }
public CipherString Username { get; set; } public EncString Username { get; set; }
public CipherString PassportNumber { get; set; } public EncString PassportNumber { get; set; }
public CipherString LicenseNumber { get; set; } public EncString LicenseNumber { get; set; }
public Task<IdentityView> DecryptAsync(string orgId) public Task<IdentityView> DecryptAsync(string orgId)
{ {

View File

@@ -24,10 +24,10 @@ namespace Bit.Core.Models.Domain
} }
public List<LoginUri> Uris { get; set; } public List<LoginUri> Uris { get; set; }
public CipherString Username { get; set; } public EncString Username { get; set; }
public CipherString Password { get; set; } public EncString Password { get; set; }
public DateTime? PasswordRevisionDate { get; set; } public DateTime? PasswordRevisionDate { get; set; }
public CipherString Totp { get; set; } public EncString Totp { get; set; }
public async Task<LoginView> DecryptAsync(string orgId) public async Task<LoginView> DecryptAsync(string orgId)
{ {

View File

@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
BuildDomainModel(this, obj, _map, alreadyEncrypted); BuildDomainModel(this, obj, _map, alreadyEncrypted);
} }
public CipherString Uri { get; set; } public EncString Uri { get; set; }
public UriMatchType? Match { get; set; } public UriMatchType? Match { get; set; }
public Task<LoginUriView> DecryptAsync(string orgId) public Task<LoginUriView> DecryptAsync(string orgId)

View File

@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
LastUsedDate = obj.LastUsedDate.GetValueOrDefault(); LastUsedDate = obj.LastUsedDate.GetValueOrDefault();
} }
public CipherString Password { get; set; } public EncString Password { get; set; }
public DateTime LastUsedDate { get; set; } public DateTime LastUsedDate { get; set; }
public Task<PasswordHistoryView> DecryptAsync(string orgId) public Task<PasswordHistoryView> DecryptAsync(string orgId)

View File

@@ -15,11 +15,11 @@ namespace Bit.Core.Models.Domain
public string AccessId { get; set; } public string AccessId { get; set; }
public string UserId { get; set; } public string UserId { get; set; }
public SendType Type { get; set; } public SendType Type { get; set; }
public CipherString Name { get; set; } public EncString Name { get; set; }
public CipherString Notes { get; set; } public EncString Notes { get; set; }
public SendFile File { get; set; } public SendFile File { get; set; }
public SendText Text { get; set; } public SendText Text { get; set; }
public CipherString Key { get; set; } public EncString Key { get; set; }
public int? MaxAccessCount { get; set; } public int? MaxAccessCount { get; set; }
public int AccessCount { get; set; } public int AccessCount { get; set; }
public DateTime RevisionDate { get; set; } public DateTime RevisionDate { get; set; }
@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Domain
public DateTime DeletionDate { get; set; } public DateTime DeletionDate { get; set; }
public string Password { get; set; } public string Password { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
public bool HideEmail { get; set; }
public Send() : base() { } public Send() : base() { }
@@ -49,6 +50,7 @@ namespace Bit.Core.Models.Domain
RevisionDate = data.RevisionDate; RevisionDate = data.RevisionDate;
DeletionDate = data.DeletionDate; DeletionDate = data.DeletionDate;
ExpirationDate = data.ExpirationDate; ExpirationDate = data.ExpirationDate;
HideEmail = data.HideEmail;
switch (Type) switch (Type)
{ {

View File

@@ -10,7 +10,7 @@ namespace Bit.Core.Models.Domain
public string Id { get; set; } public string Id { get; set; }
public string Size { get; set; } public string Size { get; set; }
public string SizeName { get; set; } public string SizeName { get; set; }
public CipherString FileName { get; set; } public EncString FileName { get; set; }
public SendFile() : base() { } public SendFile() : base() { }

View File

@@ -8,7 +8,7 @@ namespace Bit.Core.Models.Domain
{ {
public class SendText : Domain public class SendText : Domain
{ {
public CipherString Text { get; set; } public EncString Text { get; set; }
public bool Hidden { get; set; } public bool Hidden { get; set; }
public SendText() : base() { } public SendText() : base() { }

View File

@@ -1,5 +1,6 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Bit.Core.Models.Domain namespace Bit.Core.Models.Domain
@@ -30,22 +31,24 @@ namespace Bit.Core.Models.Domain
} }
Key = key; Key = key;
EncType = encType.Value; EncTypes.Add(encType.Value);
if (EncType == EncryptionType.AesCbc256_B64 && Key.Length == 32) if (encType.Value == EncryptionType.AesCbc256_B64 && Key.Length == 32)
{ {
EncKey = Key; EncKey = Key;
MacKey = null; MacKey = null;
EncTypes.Add(EncryptionType.AesGcm256_B64);
} }
else if (EncType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32) else if (encType.Value == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
{ {
EncKey = new ArraySegment<byte>(Key, 0, 16).ToArray(); EncKey = new ArraySegment<byte>(Key, 0, 16).ToArray();
MacKey = new ArraySegment<byte>(Key, 16, 16).ToArray(); MacKey = new ArraySegment<byte>(Key, 16, 16).ToArray();
} }
else if (EncType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64) else if (encType.Value == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
{ {
EncKey = new ArraySegment<byte>(Key, 0, 32).ToArray(); EncKey = new ArraySegment<byte>(Key, 0, 32).ToArray();
MacKey = new ArraySegment<byte>(Key, 32, 32).ToArray(); MacKey = new ArraySegment<byte>(Key, 32, 32).ToArray();
EncTypes.Add(EncryptionType.AesGcm256_B64);
} }
else else
{ {
@@ -69,9 +72,9 @@ namespace Bit.Core.Models.Domain
public byte[] Key { get; set; } public byte[] Key { get; set; }
public byte[] EncKey { get; set; } public byte[] EncKey { get; set; }
public byte[] MacKey { get; set; } public byte[] MacKey { get; set; }
public EncryptionType EncType { get; set; }
public string KeyB64 { get; set; } public string KeyB64 { get; set; }
public string EncKeyB64 { get; set; } public string EncKeyB64 { get; set; }
public string MacKeyB64 { get; set; } public string MacKeyB64 { get; set; }
public HashSet<EncryptionType> EncTypes { get; set; } = new HashSet<EncryptionType>();
} }
} }

View File

@@ -6,5 +6,6 @@ namespace Bit.Core.Models.Request
{ {
public string FileName { get; set; } public string FileName { get; set; }
public string Key { get; set; } public string Key { get; set; }
public long FileSize { get; set; }
} }
} }

View File

@@ -19,6 +19,7 @@ namespace Bit.Core.Models.Request
public SendFileApi File { get; set; } public SendFileApi File { get; set; }
public string Password { get; set; } public string Password { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
public bool HideEmail { get; set; }
public SendRequest(Send send, long? fileLength) public SendRequest(Send send, long? fileLength)
{ {
@@ -32,6 +33,7 @@ namespace Bit.Core.Models.Request
Key = send.Key?.EncryptedString; Key = send.Key?.EncryptedString;
Password = send.Password; Password = send.Password;
Disabled = send.Disabled; Disabled = send.Disabled;
HideEmail = send.HideEmail;
switch (Type) switch (Type)
{ {

View File

@@ -0,0 +1,12 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.Response
{
public class AttachmentUploadDataResponse
{
public string AttachmentId { get; set; }
public FileUploadType FileUploadType { get; set; }
public CipherResponse CipherResponse { get; set; }
public string Url { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Response
{
public class SendFileUploadDataResponse
{
public string Url { get; set; }
public FileUploadType FileUploadType { get; set; }
public SendResponse SendResponse { get; set; }
}
}

View File

@@ -21,5 +21,6 @@ namespace Bit.Core.Models.Response
public DateTime DeletionDate { get; set; } public DateTime DeletionDate { get; set; }
public string Password { get; set; } public string Password { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
public bool? HideEmail { get; set; }
} }
} }

View File

@@ -21,6 +21,7 @@ namespace Bit.Core.Models.View
ExpirationDate = send.ExpirationDate; ExpirationDate = send.ExpirationDate;
Disabled = send.Disabled; Disabled = send.Disabled;
Password = send.Password; Password = send.Password;
HideEmail = send.HideEmail;
} }
public string Id { get; set; } public string Id { get; set; }
@@ -45,5 +46,6 @@ namespace Bit.Core.Models.View
public bool Expired => ExpirationDate.HasValue && ExpirationDate.Value <= DateTime.UtcNow; public bool Expired => ExpirationDate.HasValue && ExpirationDate.Value <= DateTime.UtcNow;
public bool PendingDelete => DeletionDate <= DateTime.UtcNow; public bool PendingDelete => DeletionDate <= DateTime.UtcNow;
public string DisplayDate => DeletionDate.ToLocalTime().ToString("MMM d, yyyy, h:mm tt"); public string DisplayDate => DeletionDate.ToLocalTime().ToString("MMM d, yyyy, h:mm tt");
public bool HideEmail { get; set; }
} }
} }

View File

@@ -223,9 +223,19 @@ namespace Bit.Core.Services
public Task<SendResponse> PostSendAsync(SendRequest request) => public Task<SendResponse> PostSendAsync(SendRequest request) =>
SendAsync<SendRequest, SendResponse>(HttpMethod.Post, "/sends", request, true, true); SendAsync<SendRequest, SendResponse>(HttpMethod.Post, "/sends", request, true, true);
public Task<SendFileUploadDataResponse> PostFileTypeSendAsync(SendRequest request) =>
SendAsync<SendRequest, SendFileUploadDataResponse>(HttpMethod.Post, "/sends/file/v2", request, true, true);
public Task PostSendFileAsync(string sendId, string fileId, MultipartFormDataContent data) =>
SendAsync<MultipartFormDataContent, object>(HttpMethod.Post, $"/sends/{sendId}/file/{fileId}", data, true, false);
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
public Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data) => public Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data) =>
SendAsync<MultipartFormDataContent, SendResponse>(HttpMethod.Post, "/sends/file", data, true, true); SendAsync<MultipartFormDataContent, SendResponse>(HttpMethod.Post, "/sends/file", data, true, true);
public Task<SendFileUploadDataResponse> RenewFileUploadUrlAsync(string sendId, string fileId) =>
SendAsync<object, SendFileUploadDataResponse>(HttpMethod.Get, $"/sends/{sendId}/file/{fileId}", null, true, true);
public Task<SendResponse> PutSendAsync(string id, SendRequest request) => public Task<SendResponse> PutSendAsync(string id, SendRequest request) =>
SendAsync<SendRequest, SendResponse>(HttpMethod.Put, $"/sends/{id}", request, true, true); SendAsync<SendRequest, SendResponse>(HttpMethod.Put, $"/sends/{id}", request, true, true);
@@ -293,16 +303,26 @@ namespace Bit.Core.Services
#region Attachments APIs #region Attachments APIs
public Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data) [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
public Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data)
{ {
return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post, return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post,
string.Concat("/ciphers/", id, "/attachment"), data, true, true); string.Concat("/ciphers/", id, "/attachment"), data, true, true);
} }
public Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request)
{
return SendAsync<AttachmentRequest, AttachmentUploadDataResponse>(HttpMethod.Post,
$"/ciphers/{id}/attachment/v2", request, true, true);
}
public Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId) =>
SendAsync<AttachmentResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}", true);
public Task DeleteCipherAttachmentAsync(string id, string attachmentId) public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
{ {
return SendAsync<object, object>(HttpMethod.Delete, return SendAsync(HttpMethod.Delete,
string.Concat("/ciphers/", id, "/attachment/", attachmentId), null, true, false); string.Concat("/ciphers/", id, "/attachment/", attachmentId), true);
} }
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data, public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
@@ -313,6 +333,13 @@ namespace Bit.Core.Services
data, true, false); data, true, false);
} }
public Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId) =>
SendAsync<AttachmentUploadDataResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}/renew", true);
public Task PostAttachmentFileAsync(string cipherId, string attachmentId, MultipartFormDataContent data) =>
SendAsync(HttpMethod.Post,
$"/ciphers/{cipherId}/attachment/{attachmentId}", data, true);
#endregion #endregion
#region Sync APIs #region Sync APIs
@@ -427,6 +454,12 @@ namespace Bit.Core.Services
} }
} }
public Task SendAsync(HttpMethod method, string path, bool authed) =>
SendAsync<object, object>(method, path, null, authed, false);
public Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body, bool authed) =>
SendAsync<TRequest, object>(method, path, body, authed, false);
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
SendAsync<object, TResponse>(method, path, null, authed, true);
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body, public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
bool authed, bool hasResponse) bool authed, bool hasResponse)
{ {

View File

@@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class AzureFileUploadService : IAzureFileUploadService
{
private const long MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB
private const int MAX_BLOCKS_PER_BLOB = 50000;
private const decimal MAX_MOBILE_BLOCK_SIZE = 5 * 1024 * 1024; // 5 MB
private readonly HttpClient _httpClient = new HttpClient();
public AzureFileUploadService()
{
_httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue()
{
NoCache = true,
};
}
public async Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback)
{
if (data?.Buffer?.Length <= MAX_SINGLE_BLOB_UPLOAD_SIZE)
{
await AzureUploadBlob(uri, data);
}
else
{
await AzureUploadBlocks(uri, data, renewalCallback);
}
}
private async Task AzureUploadBlob(string uri, EncByteArray data)
{
using (var requestMessage = new HttpRequestMessage())
{
var uriBuilder = new UriBuilder(uri);
var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query);
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
requestMessage.Headers.Add("x-ms-version", paramValues["sv"]);
requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
requestMessage.Content = new ByteArrayContent(data.Buffer);
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = uriBuilder.Uri;
var blobResponse = await _httpClient.SendAsync(requestMessage);
if (blobResponse.StatusCode != HttpStatusCode.Created)
{
throw new Exception("Failed to create Azure blob");
}
}
}
private async Task AzureUploadBlocks(string uri, EncByteArray data, Func<Task<string>> renewalFunc)
{
_httpClient.Timeout = TimeSpan.FromHours(3);
var baseParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
var blockSize = MaxBlockSize(baseParams["sv"]);
var blockIndex = 0;
var numBlocks = Math.Ceiling((decimal)data.Buffer.Length / blockSize);
var blocksStaged = new List<string>();
if (numBlocks > MAX_BLOCKS_PER_BLOB)
{
throw new Exception($"Cannot upload file, exceeds maximum size of {blockSize * MAX_BLOCKS_PER_BLOB}");
}
while (blockIndex < numBlocks)
{
uri = await RenewUriIfNecessary(uri, renewalFunc);
var blockUriBuilder = new UriBuilder(uri);
var blockId = EncodeBlockId(blockIndex);
var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query);
blockParams.Add("comp", "block");
blockParams.Add("blockid", blockId);
blockUriBuilder.Query = blockParams.ToString();
using (var requestMessage = new HttpRequestMessage())
{
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
requestMessage.Headers.Add("x-ms-version", baseParams["sv"]);
requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
requestMessage.Content = new ByteArrayContent(data.Buffer.Skip(blockIndex * blockSize).Take(blockSize).ToArray());
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = blockUriBuilder.Uri;
var blockResponse = await _httpClient.SendAsync(requestMessage);
if (blockResponse.StatusCode != HttpStatusCode.Created)
{
throw new Exception("Failed to create Azure block");
}
}
blocksStaged.Add(blockId);
blockIndex++;
}
using (var requestMessage = new HttpRequestMessage())
{
uri = await RenewUriIfNecessary(uri, renewalFunc);
var blockListXml = GenerateBlockListXml(blocksStaged);
var blockListUriBuilder = new UriBuilder(uri);
var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query);
blockListParams.Add("comp", "blocklist");
blockListUriBuilder.Query = blockListParams.ToString();
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
requestMessage.Headers.Add("x-ms-version", baseParams["sv"]);
requestMessage.Content = new StringContent(blockListXml);
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Put;
requestMessage.RequestUri = blockListUriBuilder.Uri;
var blockListResponse = await _httpClient.SendAsync(requestMessage);
if (blockListResponse.StatusCode != HttpStatusCode.Created)
{
throw new Exception("Failed to PUT Azure block list");
}
}
}
private async Task<string> RenewUriIfNecessary(string uri, Func<Task<string>> renewalFunc)
{
var uriParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
if (DateTime.TryParse(uriParams.Get("se") ?? "", out DateTime expiry) && expiry < DateTime.UtcNow.AddSeconds(1))
{
return await renewalFunc();
}
return uri;
}
private string GenerateBlockListXml(List<string> blocksStaged)
{
var xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?><BlockList>");
foreach(var blockId in blocksStaged)
{
xml.Append($"<Latest>{blockId}</Latest>");
}
xml.Append("</BlockList>");
return xml.ToString();
}
private string EncodeBlockId(int index)
{
// Encoded blockId max size is 64, so pre-encoding max size is 48
var paddedString = index.ToString("D48");
return Convert.ToBase64String(Encoding.UTF8.GetBytes(paddedString));
}
private int MaxBlockSize(string version)
{
long maxSize = 4194304L; // 4 MiB
if (CompareAzureVersions(version, "2019-12-12") >= 0)
{
maxSize = 4194304000L; // 4000 MiB
}
else if (CompareAzureVersions(version, "2016-05-31") >= 0)
{
maxSize = 104857600L; // 100 MiB
}
return maxSize > MAX_MOBILE_BLOCK_SIZE ? (int)MAX_MOBILE_BLOCK_SIZE : (int) maxSize;
}
private int CompareAzureVersions(string a, string b)
{
var v1Parts = a.Split('-').Select(p => int.Parse(p));
var v2Parts = b.Split('-').Select(p => int.Parse(p));
return a[0] != b[0] ? a[0] - b[0] :
a[1] != b[1] ? a[1] - b[1] :
a[2] != b[2] ? a[2] - b[2] :
0;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
namespace Bit.Core.Services
{
public class BitwardenFileUploadService
{
public BitwardenFileUploadService(ApiService apiService)
{
_apiService = apiService;
}
private readonly ApiService _apiService;
public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func<MultipartFormDataContent, Task> apiCall)
{
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
{
{ new ByteArrayContent(encryptedFileData.Buffer), "data", encryptedFileName }
};
await apiCall(fd);
}
}
}

View File

@@ -30,6 +30,7 @@ namespace Bit.Core.Services
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
private readonly IFileUploadService _fileUploadService;
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly Func<ISearchService> _searchService; private readonly Func<ISearchService> _searchService;
@@ -47,6 +48,7 @@ namespace Bit.Core.Services
IUserService userService, IUserService userService,
ISettingsService settingsService, ISettingsService settingsService,
IApiService apiService, IApiService apiService,
IFileUploadService fileUploadService,
IStorageService storageService, IStorageService storageService,
II18nService i18nService, II18nService i18nService,
Func<ISearchService> searchService, Func<ISearchService> searchService,
@@ -57,6 +59,7 @@ namespace Bit.Core.Services
_userService = userService; _userService = userService;
_settingsService = settingsService; _settingsService = settingsService;
_apiService = apiService; _apiService = apiService;
_fileUploadService = fileUploadService;
_storageService = storageService; _storageService = storageService;
_i18nService = i18nService; _i18nService = i18nService;
_searchService = searchService; _searchService = searchService;
@@ -553,21 +556,47 @@ namespace Bit.Core.Services
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
{ {
var key = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId); var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
var encFileName = await _cryptoService.EncryptAsync(filename, key); var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key); var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
var encData = await _cryptoService.EncryptToBytesAsync(data, dataEncKey.Item1); var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary); CipherResponse response;
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key"); try
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString); {
var response = await _apiService.PostCipherAttachmentAsync(cipher.Id, fd); var request = new AttachmentRequest
{
Key = orgEncAttachmentKey.EncryptedString,
FileName = encFileName.EncryptedString,
FileSize = encFileData.Buffer.Length,
};
var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request);
response = uploadDataResponse.CipherResponse;
await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName.EncryptedString, encFileData);
}
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
{
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
}
var userId = await _userService.GetUserIdAsync(); var userId = await _userService.GetUserIdAsync();
var cData = new CipherData(response, userId, cipher.CollectionIds); var cData = new CipherData(response, userId, cipher.CollectionIds);
await UpsertAsync(cData); await UpsertAsync(cData);
return new Cipher(cData); return new Cipher(cData);
} }
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
private async Task<CipherResponse> LegacyServerAttachmentFileUploadAsync(string cipherId,
EncString encFileName, EncByteArray encFileData, EncString key)
{
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary);
fd.Add(new StringContent(key.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
return await _apiService.PostCipherAttachmentLegacyAsync(cipherId, fd);
}
public async Task SaveCollectionsWithServerAsync(Cipher cipher) public async Task SaveCollectionsWithServerAsync(Cipher cipher)
{ {
var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList()); var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList());
@@ -706,11 +735,23 @@ namespace Bit.Core.Services
} }
} }
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(AttachmentView attachment, string organizationId) public async Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId)
{ {
string url;
try try
{ {
var response = await _httpClient.GetAsync(new Uri(attachment.Url)); var attachmentDownloadResponse = await _apiService.GetAttachmentData(cipherId, attachment.Id);
url = attachmentDownloadResponse.Url;
}
// TODO: Delete this catch when all Servers are updated to respond to the above method
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound)
{
url = attachment.Url;
}
try
{
var response = await _httpClient.GetAsync(new Uri(url));
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
return null; return null;
@@ -787,7 +828,7 @@ namespace Bit.Core.Services
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks); var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary); var fd = new MultipartFormDataContent(boundary);
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key"); fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString); fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString);
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId); await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
} }
@@ -996,7 +1037,7 @@ namespace Bit.Core.Services
{ {
var modelPropInfo = modelType.GetProperty(propName); var modelPropInfo = modelType.GetProperty(propName);
var modelProp = modelPropInfo.GetValue(model) as string; var modelProp = modelPropInfo.GetValue(model) as string;
CipherString val = null; EncString val = null;
if (!string.IsNullOrWhiteSpace(modelProp)) if (!string.IsNullOrWhiteSpace(modelProp))
{ {
val = await _cryptoService.EncryptAsync(modelProp, key); val = await _cryptoService.EncryptAsync(modelProp, key);

View File

@@ -148,7 +148,7 @@ namespace Bit.Core.Services
} }
byte[] decEncKey = null; byte[] decEncKey = null;
var encKeyCipher = new CipherString(encKey); var encKeyCipher = new EncString(encKey);
if (encKeyCipher.EncryptionType == EncryptionType.AesCbc256_B64) if (encKeyCipher.EncryptionType == EncryptionType.AesCbc256_B64)
{ {
decEncKey = await DecryptToBytesAsync(encKeyCipher, key); decEncKey = await DecryptToBytesAsync(encKeyCipher, key);
@@ -205,7 +205,7 @@ namespace Bit.Core.Services
{ {
return null; return null;
} }
_privateKey = await DecryptToBytesAsync(new CipherString(encPrivateKey), null); _privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), null);
return _privateKey; return _privateKey;
} }
@@ -389,7 +389,7 @@ namespace Bit.Core.Services
} }
public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt,
KdfType kdf, int kdfIterations, CipherString protectedKeyCs = null) KdfType kdf, int kdfIterations, EncString protectedKeyCs = null)
{ {
if (protectedKeyCs == null) if (protectedKeyCs == null)
{ {
@@ -398,27 +398,27 @@ namespace Bit.Core.Services
{ {
throw new Exception("No PIN protected key found."); throw new Exception("No PIN protected key found.");
} }
protectedKeyCs = new CipherString(pinProtectedKey); protectedKeyCs = new EncString(pinProtectedKey);
} }
var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations); var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations);
var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey); var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey);
return new SymmetricCryptoKey(decKey); return new SymmetricCryptoKey(decKey);
} }
public async Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync() public async Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync()
{ {
var shareKey = await _cryptoFunctionService.RandomBytesAsync(64); var shareKey = await _cryptoFunctionService.RandomBytesAsync(64);
var publicKey = await GetPublicKeyAsync(); var publicKey = await GetPublicKeyAsync();
var encShareKey = await RsaEncryptAsync(shareKey, publicKey); var encShareKey = await RsaEncryptAsync(shareKey, publicKey);
return new Tuple<CipherString, SymmetricCryptoKey>(encShareKey, new SymmetricCryptoKey(shareKey)); return new Tuple<EncString, SymmetricCryptoKey>(encShareKey, new SymmetricCryptoKey(shareKey));
} }
public async Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null) public async Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null)
{ {
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048); var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
var publicB64 = Convert.ToBase64String(keyPair.Item1); var publicB64 = Convert.ToBase64String(keyPair.Item1);
var privateEnc = await EncryptAsync(keyPair.Item2, key); var privateEnc = await EncryptAsync(keyPair.Item2, key);
return new Tuple<string, CipherString>(publicB64, privateEnc); return new Tuple<string, EncString>(publicB64, privateEnc);
} }
public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations) public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations)
@@ -447,20 +447,20 @@ namespace Bit.Core.Services
return Convert.ToBase64String(hash); return Convert.ToBase64String(hash);
} }
public async Task<Tuple<SymmetricCryptoKey, CipherString>> MakeEncKeyAsync(SymmetricCryptoKey key) public async Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key)
{ {
var theKey = await GetKeyForEncryptionAsync(key); var theKey = await GetKeyForEncryptionAsync(key);
var encKey = await _cryptoFunctionService.RandomBytesAsync(64); var encKey = await _cryptoFunctionService.RandomBytesAsync(64);
return await BuildEncKeyAsync(theKey, encKey); return await BuildEncKeyAsync(theKey, encKey);
} }
public async Task<Tuple<SymmetricCryptoKey, CipherString>> RemakeEncKeyAsync(SymmetricCryptoKey key) public async Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key)
{ {
var encKey = await GetEncKeyAsync(); var encKey = await GetEncKeyAsync();
return await BuildEncKeyAsync(key, encKey.Key); return await BuildEncKeyAsync(key, encKey.Key);
} }
public async Task<CipherString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null) public async Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null)
{ {
if (plainValue == null) if (plainValue == null)
{ {
@@ -469,7 +469,7 @@ namespace Bit.Core.Services
return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key); return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key);
} }
public async Task<CipherString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null) public async Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{ {
if (plainValue == null) if (plainValue == null)
{ {
@@ -479,10 +479,10 @@ namespace Bit.Core.Services
var iv = Convert.ToBase64String(encObj.Iv); var iv = Convert.ToBase64String(encObj.Iv);
var data = Convert.ToBase64String(encObj.Data); var data = Convert.ToBase64String(encObj.Data);
var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null; var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null;
return new CipherString(encObj.Key.EncType, data, iv, mac); return new EncString(encObj.Type, data, iv, mac);
} }
public async Task<byte[]> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null) public async Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null)
{ {
var encValue = await AesEncryptAsync(plainValue, key); var encValue = await AesEncryptAsync(plainValue, key);
var macLen = 0; var macLen = 0;
@@ -491,17 +491,17 @@ namespace Bit.Core.Services
macLen = encValue.Mac.Length; macLen = encValue.Mac.Length;
} }
var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length]; var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length];
Buffer.BlockCopy(new byte[] { (byte)encValue.Key.EncType }, 0, encBytes, 0, 1); Buffer.BlockCopy(new byte[] { (byte)encValue.Type }, 0, encBytes, 0, 1);
Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length); Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length);
if (encValue.Mac != null) if (encValue.Mac != null)
{ {
Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length); Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length);
} }
Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length); Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length);
return encBytes; return new EncByteArray(encBytes);
} }
public async Task<CipherString> RsaEncryptAsync(byte[] data, byte[] publicKey = null) public async Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null)
{ {
if (publicKey == null) if (publicKey == null)
{ {
@@ -512,21 +512,21 @@ namespace Bit.Core.Services
throw new Exception("Public key unavailable."); throw new Exception("Public key unavailable.");
} }
var encBytes = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey, CryptoHashAlgorithm.Sha1); var encBytes = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey, CryptoHashAlgorithm.Sha1);
return new CipherString(EncryptionType.Rsa2048_OaepSha1_B64, Convert.ToBase64String(encBytes)); return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Convert.ToBase64String(encBytes));
} }
public async Task<byte[]> DecryptToBytesAsync(CipherString cipherString, SymmetricCryptoKey key = null) public async Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null)
{ {
var iv = Convert.FromBase64String(cipherString.Iv); var iv = Convert.FromBase64String(encString.Iv);
var data = Convert.FromBase64String(cipherString.Data); var data = Convert.FromBase64String(encString.Data);
var mac = !string.IsNullOrWhiteSpace(cipherString.Mac) ? Convert.FromBase64String(cipherString.Mac) : null; var mac = !string.IsNullOrWhiteSpace(encString.Mac) ? Convert.FromBase64String(encString.Mac) : null;
return await AesDecryptToBytesAsync(cipherString.EncryptionType, data, iv, mac, key); return await AesDecryptToBytesAsync(encString.EncryptionType, data, iv, mac, key);
} }
public async Task<string> DecryptToUtf8Async(CipherString cipherString, SymmetricCryptoKey key = null) public async Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null)
{ {
return await AesDecryptToUtf8Async(cipherString.EncryptionType, cipherString.Data, return await AesDecryptToUtf8Async(encString.EncryptionType, encString.Data,
cipherString.Iv, cipherString.Mac, key); encString.Iv, encString.Mac, key);
} }
public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key) public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key)
@@ -561,6 +561,14 @@ namespace Bit.Core.Services
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray(); ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray(); ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray();
break; break;
case EncryptionType.AesGcm256_B64:
if (encBytes.Length < 13) // 1 + 12 + ctLength
{
return null;
}
ivBytes = new ArraySegment<byte>(encBytes, 1, 12).ToArray();
ctBytes = new ArraySegment<byte>(encBytes, 13, encBytes.Length - 13).ToArray();
break;
default: default:
return null; return null;
} }
@@ -589,16 +597,27 @@ namespace Bit.Core.Services
{ {
var obj = new EncryptedObject var obj = new EncryptedObject
{ {
Key = await GetKeyForEncryptionAsync(key), Key = await GetKeyForEncryptionAsync(key)
Iv = await _cryptoFunctionService.RandomBytesAsync(16)
}; };
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey); if (true)
if (obj.Key.MacKey != null)
{ {
var macData = new byte[obj.Iv.Length + obj.Data.Length]; obj.Type = EncryptionType.AesCbc256_HmacSha256_B64;
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length); obj.Iv = await _cryptoFunctionService.RandomBytesAsync(16);
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length); obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey, AesMode.CBC);
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256); if (obj.Key.MacKey != null)
{
var macData = new byte[obj.Iv.Length + obj.Data.Length];
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length);
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length);
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256);
}
}
else
{
// TODO: make this case live when we switch to GCM encryption
obj.Type = EncryptionType.AesGcm256_B64;
obj.Iv = await _cryptoFunctionService.RandomBytesAsync(12);
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey, AesMode.GCM);
} }
return obj; return obj;
} }
@@ -608,12 +627,15 @@ namespace Bit.Core.Services
{ {
var keyForEnc = await GetKeyForEncryptionAsync(key); var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc); var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
var macType = encType == EncryptionType.AesCbc128_HmacSha256_B64 ||
encType == EncryptionType.AesCbc256_HmacSha256_B64;
if (macType && theKey.MacKey != null && mac == null)
{ {
// Mac required. // Mac required.
return null; return null;
} }
if (theKey.EncType != encType) if (!theKey.EncTypes.Contains(encType))
{ {
// encType unavailable. // encType unavailable.
return null; return null;
@@ -652,7 +674,8 @@ namespace Bit.Core.Services
} }
} }
var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey); var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey,
encType == EncryptionType.AesGcm256_B64 ? AesMode.GCM : AesMode.CBC);
return Encoding.UTF8.GetString(decBytes); return Encoding.UTF8.GetString(decBytes);
} }
@@ -662,12 +685,15 @@ namespace Bit.Core.Services
var keyForEnc = await GetKeyForEncryptionAsync(key); var keyForEnc = await GetKeyForEncryptionAsync(key);
var theKey = ResolveLegacyKey(encType, keyForEnc); var theKey = ResolveLegacyKey(encType, keyForEnc);
if (theKey.MacKey != null && mac == null)
var macType = encType == EncryptionType.AesCbc128_HmacSha256_B64 ||
encType == EncryptionType.AesCbc256_HmacSha256_B64;
if (macType && theKey.MacKey != null && mac == null)
{ {
// Mac required. // Mac required.
return null; return null;
} }
if (theKey.EncType != encType) if (!theKey.EncTypes.Contains(encType))
{ {
// encType unavailable. // encType unavailable.
return null; return null;
@@ -694,7 +720,8 @@ namespace Bit.Core.Services
} }
} }
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey); return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey,
encType == EncryptionType.AesGcm256_B64 ? AesMode.GCM : AesMode.CBC);
} }
private async Task<byte[]> RsaDecryptAsync(string encValue) private async Task<byte[]> RsaDecryptAsync(string encValue)
@@ -763,7 +790,8 @@ namespace Bit.Core.Services
private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key) private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key)
{ {
if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 && key.EncType == EncryptionType.AesCbc256_B64) if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncTypes.Contains(EncryptionType.AesCbc256_B64))
{ {
// Old encrypt-then-mac scheme, make a new key // Old encrypt-then-mac scheme, make a new key
if (_legacyEtmKey == null) if (_legacyEtmKey == null)
@@ -809,10 +837,10 @@ namespace Bit.Core.Services
return phrase; return phrase;
} }
private async Task<Tuple<SymmetricCryptoKey, CipherString>> BuildEncKeyAsync(SymmetricCryptoKey key, private async Task<Tuple<SymmetricCryptoKey, EncString>> BuildEncKeyAsync(SymmetricCryptoKey key,
byte[] encKey) byte[] encKey)
{ {
CipherString encKeyEnc = null; EncString encKeyEnc = null;
if (key.Key.Length == 32) if (key.Key.Length == 32)
{ {
var newKey = await StretchKeyAsync(key); var newKey = await StretchKeyAsync(key);
@@ -826,11 +854,12 @@ namespace Bit.Core.Services
{ {
throw new Exception("Invalid key size."); throw new Exception("Invalid key size.");
} }
return new Tuple<SymmetricCryptoKey, CipherString>(new SymmetricCryptoKey(encKey), encKeyEnc); return new Tuple<SymmetricCryptoKey, EncString>(new SymmetricCryptoKey(encKey), encKeyEnc);
} }
private class EncryptedObject private class EncryptedObject
{ {
public EncryptionType Type { get; set; }
public byte[] Iv { get; set; } public byte[] Iv { get; set; }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public byte[] Mac { get; set; } public byte[] Mac { get; set; }

View File

@@ -0,0 +1,80 @@
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Enums;
using System;
namespace Bit.Core.Services {
public class FileUploadService : IFileUploadService
{
public FileUploadService(ApiService apiService)
{
_apiService = apiService;
_bitwardenFileUploadService = new BitwardenFileUploadService(apiService);
_azureFileUploadService = new AzureFileUploadService();
}
private readonly BitwardenFileUploadService _bitwardenFileUploadService;
private readonly AzureFileUploadService _azureFileUploadService;
private readonly ApiService _apiService;
public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData,
string encryptedFileName, EncByteArray encryptedFileData)
{
try
{
switch (uploadData.FileUploadType)
{
case FileUploadType.Direct:
await _bitwardenFileUploadService.Upload(encryptedFileName, encryptedFileData,
fd => _apiService.PostAttachmentFileAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, fd));
break;
case FileUploadType.Azure:
Func<Task<string>> renewalCallback = async () =>
{
var response = await _apiService.RenewAttachmentUploadUrlAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
return response.Url;
};
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback);
break;
default:
throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}");
}
} catch
{
await _apiService.DeleteCipherAttachmentAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
throw;
}
}
public async Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData)
{
try
{
switch (uploadData.FileUploadType)
{
case FileUploadType.Direct:
await _bitwardenFileUploadService.Upload(fileName.EncryptedString, encryptedFileData,
fd => _apiService.PostSendFileAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id, fd));
break;
case FileUploadType.Azure:
Func<Task<string>> renewalCallback = async () =>
{
var response = await _apiService.RenewFileUploadUrlAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id);
return response.Url;
};
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback);
break;
default:
throw new Exception("Unknown file upload type");
}
}
catch (Exception)
{
await _apiService.DeleteSendAsync(uploadData.SendResponse.Id);
throw;
}
}
}
}

View File

@@ -633,7 +633,7 @@ namespace Bit.Core.Services
} }
var tasks = history.Select(async item => var tasks = history.Select(async item =>
{ {
var decrypted = await _cryptoService.DecryptToUtf8Async(new CipherString(item.Password)); var decrypted = await _cryptoService.DecryptToUtf8Async(new EncString(item.Password));
return new GeneratedPasswordHistory return new GeneratedPasswordHistory
{ {
Password = decrypted, Password = decrypted,

View File

@@ -143,16 +143,24 @@ namespace Bit.Core.Services
return true; return true;
} }
public Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key) public Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode)
{ {
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); if (mode == AesMode.GCM)
{
return Task.FromResult(_cryptoPrimitiveService.AesGcmEncrypt(data, iv, key));
}
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(AesModeToSymmetricAlgorithm(mode));
var cryptoKey = provider.CreateSymmetricKey(key); var cryptoKey = provider.CreateSymmetricKey(key);
return Task.FromResult(CryptographicEngine.Encrypt(cryptoKey, data, iv)); return Task.FromResult(CryptographicEngine.Encrypt(cryptoKey, data, iv));
} }
public Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key) public Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode)
{ {
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); if (mode == AesMode.GCM)
{
return Task.FromResult(_cryptoPrimitiveService.AesGcmDecrypt(data, iv, key));
}
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(AesModeToSymmetricAlgorithm(mode));
var cryptoKey = provider.CreateSymmetricKey(key); var cryptoKey = provider.CreateSymmetricKey(key);
return Task.FromResult(CryptographicEngine.Decrypt(cryptoKey, data, iv)); return Task.FromResult(CryptographicEngine.Decrypt(cryptoKey, data, iv));
} }
@@ -286,5 +294,18 @@ namespace Bit.Core.Services
throw new ArgumentException($"Invalid hkdf algorithm type, {hkdfAlgorithm}"); throw new ArgumentException($"Invalid hkdf algorithm type, {hkdfAlgorithm}");
} }
} }
private SymmetricAlgorithm AesModeToSymmetricAlgorithm(AesMode aesMode)
{
switch (aesMode)
{
case AesMode.CBC:
return SymmetricAlgorithm.AesCbcPkcs7;
case AesMode.GCM:
return SymmetricAlgorithm.AesGcm;
default:
throw new ArgumentException($"Invalid aes mode type, {aesMode}");
}
}
} }
} }

View File

@@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
@@ -25,11 +26,13 @@ namespace Bit.Core.Services
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly ICryptoFunctionService _cryptoFunctionService; private readonly ICryptoFunctionService _cryptoFunctionService;
private Task<List<SendView>> _getAllDecryptedTask; private Task<List<SendView>> _getAllDecryptedTask;
private readonly IFileUploadService _fileUploadService;
public SendService( public SendService(
ICryptoService cryptoService, ICryptoService cryptoService,
IUserService userService, IUserService userService,
IApiService apiService, IApiService apiService,
IFileUploadService fileUploadService,
IStorageService storageService, IStorageService storageService,
II18nService i18nService, II18nService i18nService,
ICryptoFunctionService cryptoFunctionService) ICryptoFunctionService cryptoFunctionService)
@@ -37,6 +40,7 @@ namespace Bit.Core.Services
_cryptoService = cryptoService; _cryptoService = cryptoService;
_userService = userService; _userService = userService;
_apiService = apiService; _apiService = apiService;
_fileUploadService = fileUploadService;
_storageService = storageService; _storageService = storageService;
_i18nService = i18nService; _i18nService = i18nService;
_cryptoFunctionService = cryptoFunctionService; _cryptoFunctionService = cryptoFunctionService;
@@ -77,7 +81,7 @@ namespace Bit.Core.Services
await DeleteAsync(id); await DeleteAsync(id);
} }
public async Task<(Send send, byte[] encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, public async Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData,
string password, SymmetricCryptoKey key = null) string password, SymmetricCryptoKey key = null)
{ {
if (model.Key == null) if (model.Key == null)
@@ -97,8 +101,9 @@ namespace Bit.Core.Services
Key = await _cryptoService.EncryptAsync(model.Key, key), Key = await _cryptoService.EncryptAsync(model.Key, key),
Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey), Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey),
Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey), Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey),
HideEmail = model.HideEmail
}; };
byte[] encryptedFileData = null; EncByteArray encryptedFileData = null;
if (password != null) if (password != null)
{ {
@@ -192,10 +197,10 @@ namespace Bit.Core.Services
_decryptedSendsCache = null; _decryptedSendsCache = null;
} }
public async Task<string> SaveWithServerAsync(Send send, byte[] encryptedFileData) public async Task<string> SaveWithServerAsync(Send send, EncByteArray encryptedFileData)
{ {
var request = new SendRequest(send, encryptedFileData?.LongLength); var request = new SendRequest(send, encryptedFileData?.Buffer?.LongLength);
SendResponse response; SendResponse response = default;
if (send.Id == null) if (send.Id == null)
{ {
switch (send.Type) switch (send.Type)
@@ -204,13 +209,23 @@ namespace Bit.Core.Services
response = await _apiService.PostSendAsync(request); response = await _apiService.PostSendAsync(request);
break; break;
case SendType.File: case SendType.File:
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}") try{
{ var uploadDataResponse = await _apiService.PostFileTypeSendAsync(request);
{ new StringContent(JsonConvert.SerializeObject(request)), "model" }, response = uploadDataResponse.SendResponse;
{ new ByteArrayContent(encryptedFileData), "data", send.File.FileName.EncryptedString }
};
response = await _apiService.PostSendFileAsync(fd); await _fileUploadService.UploadSendFileAsync(uploadDataResponse, send.File.FileName, encryptedFileData);
}
catch (ApiException e) when (e.Error.StatusCode == HttpStatusCode.NotFound)
{
response = await LegacyServerSendFileUpload(request, send, encryptedFileData);
}
catch (Exception e)
{
if (response != default){
await _apiService.DeleteSendAsync(response.Id);
}
throw e;
}
break; break;
default: default:
throw new NotImplementedException($"Cannot save unknown Send type {send.Type}"); throw new NotImplementedException($"Cannot save unknown Send type {send.Type}");
@@ -227,6 +242,17 @@ namespace Bit.Core.Services
return response.Id; return response.Id;
} }
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
private async Task<SendResponse> LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) {
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
{
{ new StringContent(JsonConvert.SerializeObject(request)), "model" },
{ new ByteArrayContent(encryptedFileData.Buffer), "data", send.File.FileName.EncryptedString }
};
return await _apiService.PostSendFileAsync(fd);
}
public async Task UpsertAsync(params SendData[] sends) public async Task UpsertAsync(params SendData[] sends)
{ {
var userId = await _userService.GetUserIdAsync(); var userId = await _userService.GetUserIdAsync();

View File

@@ -327,6 +327,7 @@ namespace Bit.Core.Services
await _userService.SetSecurityStampAsync(response.SecurityStamp); await _userService.SetSecurityStampAsync(response.SecurityStamp);
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
await _userService.ReplaceOrganizationsAsync(organizations); await _userService.ReplaceOrganizationsAsync(organizations);
await _userService.SetEmailVerifiedAsync(response.EmailVerified);
} }
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response) private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)

View File

@@ -15,6 +15,7 @@ namespace Bit.Core.Services
private string _stamp; private string _stamp;
private KdfType? _kdf; private KdfType? _kdf;
private int? _kdfIterations; private int? _kdfIterations;
private bool? _emailVerified;
private const string Keys_UserId = "userId"; private const string Keys_UserId = "userId";
private const string Keys_UserEmail = "userEmail"; private const string Keys_UserEmail = "userEmail";
@@ -22,6 +23,7 @@ namespace Bit.Core.Services
private const string Keys_Kdf = "kdf"; private const string Keys_Kdf = "kdf";
private const string Keys_KdfIterations = "kdfIterations"; private const string Keys_KdfIterations = "kdfIterations";
private const string Keys_OrganizationsFormat = "organizations_{0}"; private const string Keys_OrganizationsFormat = "organizations_{0}";
private const string Keys_EmailVerified = "emailVerified";
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly ITokenService _tokenService; private readonly ITokenService _tokenService;
@@ -51,6 +53,12 @@ namespace Bit.Core.Services
await _storageService.SaveAsync(Keys_Stamp, stamp); await _storageService.SaveAsync(Keys_Stamp, stamp);
} }
public async Task SetEmailVerifiedAsync(bool emailVerified)
{
_emailVerified = emailVerified;
await _storageService.SaveAsync(Keys_EmailVerified, emailVerified);
}
public async Task<string> GetUserIdAsync() public async Task<string> GetUserIdAsync()
{ {
if (_userId == null) if (_userId == null)
@@ -78,6 +86,15 @@ namespace Bit.Core.Services
return _stamp; return _stamp;
} }
public async Task<bool> GetEmailVerifiedAsync()
{
if (_emailVerified == null)
{
_emailVerified = await _storageService.GetAsync<bool>(Keys_EmailVerified);
}
return _emailVerified.GetValueOrDefault();
}
public async Task<KdfType?> GetKdfAsync() public async Task<KdfType?> GetKdfAsync()
{ {
if (_kdf == null) if (_kdf == null)

View File

@@ -48,7 +48,7 @@ namespace Bit.Core.Services
_loggedOutCallback = loggedOutCallback; _loggedOutCallback = loggedOutCallback;
} }
public CipherString PinProtectedKey { get; set; } = null; public EncString PinProtectedKey { get; set; } = null;
public bool BiometricLocked { get; set; } = true; public bool BiometricLocked { get; set; } = true;
public async Task<bool> IsLockedAsync() public async Task<bool> IsLockedAsync()

View File

@@ -80,7 +80,7 @@ namespace Bit.Core.Utilities
return null; return null;
} }
private static Uri GetUri(string uriString) public static Uri GetUri(string uriString)
{ {
if (string.IsNullOrWhiteSpace(uriString)) if (string.IsNullOrWhiteSpace(uriString))
{ {

View File

@@ -40,13 +40,14 @@ namespace Bit.Core.Utilities
var appIdService = new AppIdService(storageService); var appIdService = new AppIdService(storageService);
var userService = new UserService(storageService, tokenService); var userService = new UserService(storageService, tokenService);
var settingsService = new SettingsService(userService, storageService); var settingsService = new SettingsService(userService, storageService);
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, var fileUploadService = new FileUploadService(apiService);
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService,
storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys); storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys);
var folderService = new FolderService(cryptoService, userService, apiService, storageService, var folderService = new FolderService(cryptoService, userService, apiService, storageService,
i18nService, cipherService); i18nService, cipherService);
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
var sendService = new SendService(cryptoService, userService, apiService, storageService, i18nService, var sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
cryptoFunctionService); i18nService, cryptoFunctionService);
searchService = new SearchService(cipherService, sendService); searchService = new SearchService(cipherService, sendService);
var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService, var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService, storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string> <string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.9.1</string> <string>2.10.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>

View File

@@ -140,7 +140,7 @@ namespace Bit.iOS.Core.Controllers
_vaultTimeoutService.PinProtectedKey); _vaultTimeoutService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key); var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue; failed = decPin != inputtedValue;
if (!failed) if (!failed)
{ {
@@ -191,7 +191,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin); var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
var encKey = await _cryptoService.GetEncKeyAsync(key2); var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);

View File

@@ -39,6 +39,17 @@ namespace Bit.iOS.Core.Services
return keyBytes; return keyBytes;
} }
public byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key)
{
throw new NotImplementedException();
}
public byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key)
{
throw new NotImplementedException();
}
// ref: http://opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/CommonCrypto/CommonKeyDerivation.h // ref: http://opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/CommonCrypto/CommonKeyDerivation.h
[DllImport(ObjCRuntime.Constants.libSystemLibrary, EntryPoint = "CCKeyDerivationPBKDF")] [DllImport(ObjCRuntime.Constants.libSystemLibrary, EntryPoint = "CCKeyDerivationPBKDF")]
private extern static int CCKeyCerivationPBKDF(uint algorithm, IntPtr password, nuint passwordLen, private extern static int CCKeyCerivationPBKDF(uint algorithm, IntPtr password, nuint passwordLen,

View File

@@ -437,6 +437,11 @@ namespace Bit.iOS.Core.Services
return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
} }
public void CloseMainApp()
{
throw new NotImplementedException();
}
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e) private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{ {
if (sender is UIImagePickerController picker) if (sender is UIImagePickerController picker)

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string> <string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.9.1</string> <string>2.10.0</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>
<array> <array>
<string>en</string> <string>en</string>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string> <string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.9.1</string> <string>2.10.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>

View File

@@ -144,13 +144,27 @@
<ImageAsset Include="Resources\Assets.xcassets\AppIcons.appiconset\Contents.json"> <ImageAsset Include="Resources\Assets.xcassets\AppIcons.appiconset\Contents.json">
<Visible>false</Visible> <Visible>false</Visible>
</ImageAsset> </ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\Contents.json" /> <ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\Contents.json">
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo.png" /> <Visible>false</Visible>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%402x.png" /> </ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%403x.png" /> <ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo.png">
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white.png" /> <Visible>false</Visible>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%402x.png" /> </ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%403x.png" /> <ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%403x.png">
<Visible>false</Visible>
</ImageAsset>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<InterfaceDefinition Include="LaunchScreen.storyboard" /> <InterfaceDefinition Include="LaunchScreen.storyboard" />

View File

@@ -0,0 +1,62 @@
using System;
using AutoFixture;
using Bit.Core.Models.Domain;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture
{
internal class OrganizationCipher : ICustomization
{
public string OrganizationId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Cipher>(composer => composer
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid().ToString()));
}
}
internal class UserCipher : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Cipher>(composer => composer
.Without(c => c.OrganizationId));
}
}
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
{
public UserCipherAutoDataAttribute() : base(new SutProviderCustomization(),
new UserCipher())
{ }
}
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(UserCipher) }, values)
{ }
}
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new SutProviderCustomization(), new UserCipher() }, values)
{ }
}
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationCipherAutoDataAttribute(string organizationId = null) : base(new SutProviderCustomization(),
new OrganizationCipher { OrganizationId = organizationId ?? null })
{ }
}
internal class InlineOrganizationCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(OrganizationCipher) }, values)
{ }
}
}

View File

@@ -40,17 +40,17 @@ namespace Bit.Core.Test.Models.Domain
var prefix = "decrypted_"; var prefix = "decrypted_";
var prefixBytes = Encoding.UTF8.GetBytes(prefix); var prefixBytes = Encoding.UTF8.GetBytes(prefix);
cryptoService.DecryptToBytesAsync(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>()) cryptoService.DecryptToBytesAsync(Arg.Any<EncString>(), Arg.Any<SymmetricCryptoKey>())
.Returns(info => prefixBytes.Concat(Encoding.UTF8.GetBytes(((CipherString)info[0]).EncryptedString)).ToArray()); .Returns(info => prefixBytes.Concat(Encoding.UTF8.GetBytes(((EncString)info[0]).EncryptedString)).ToArray());
cryptoService.DecryptFromBytesAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>()) cryptoService.DecryptFromBytesAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>())
.Returns(info => prefixBytes.Concat((byte[])info[0]).ToArray()); .Returns(info => prefixBytes.Concat((byte[])info[0]).ToArray());
cryptoService.DecryptToUtf8Async(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>()) cryptoService.DecryptToUtf8Async(Arg.Any<EncString>(), Arg.Any<SymmetricCryptoKey>())
.Returns(info => $"{prefix}{((CipherString)info[0]).EncryptedString}"); .Returns(info => $"{prefix}{((EncString)info[0]).EncryptedString}");
ServiceContainer.Register("cryptoService", cryptoService); ServiceContainer.Register("cryptoService", cryptoService);
var view = await send.DecryptAsync(); var view = await send.DecryptAsync();
string expectedDecryptionString(CipherString encryptedString) => string expectedDecryptionString(EncString encryptedString) =>
encryptedString?.EncryptedString == null ? null : $"{prefix}{encryptedString.EncryptedString}"; encryptedString?.EncryptedString == null ? null : $"{prefix}{encryptedString.EncryptedString}";
TestHelper.AssertPropertyEqual(send, view, "Name", "Notes", "File", "Text", "Key", "UserId"); TestHelper.AssertPropertyEqual(send, view, "Name", "Notes", "File", "Text", "Key", "UserId");

View File

@@ -0,0 +1,96 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
namespace Bit.Core.Test.Services
{
public class CipherServiceTests
{
[Theory, UserCipherAutoData]
public async Task SaveWithServerAsync_PrefersFileUploadService(SutProvider<CipherService> sutProvider,
Cipher cipher, string fileName, EncByteArray data, AttachmentUploadDataResponse uploadDataResponse, EncString encKey)
{
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
.Returns(new EncString(fileName));
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data);
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
.Returns(uploadDataResponse);
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
await sutProvider.GetDependency<IFileUploadService>().Received(1)
.UploadCipherAttachmentFileAsync(uploadDataResponse, fileName, data);
}
[Theory]
[InlineUserCipherAutoData(HttpStatusCode.NotFound)]
[InlineUserCipherAutoData(HttpStatusCode.MethodNotAllowed)]
public async Task SaveWithServerAsync_FallsBackToLegacyFormData(HttpStatusCode statusCode,
SutProvider<CipherService> sutProvider, Cipher cipher, string fileName, EncByteArray data,
CipherResponse response, EncString encKey)
{
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
.Returns(new EncString(fileName));
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data);
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
.Throws(new ApiException(new ErrorResponse {StatusCode = statusCode}));
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
.Returns(response);
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
await sutProvider.GetDependency<IApiService>().Received(1)
.PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>());
}
[Theory, UserCipherAutoData]
public async Task SaveWithServerAsync_ThrowsOnBadRequestApiException(SutProvider<CipherService> sutProvider,
Cipher cipher, string fileName, EncByteArray data, EncString encKey)
{
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
.Returns(new EncString(fileName));
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data);
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>())
.Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest });
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
.Throws(expectedException);
var actualException = await Assert.ThrowsAsync<ApiException>(async () =>
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer));
Assert.Equal(expectedException.Error.StatusCode, actualException.Error.StatusCode);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization), typeof(SymmetricCryptoKeyCustomization))]
public async Task DownloadAndDecryptAttachmentAsync_RequestsTimeLimitedUrl(SutProvider<CipherService> sutProvider,
string cipherId, AttachmentView attachment, AttachmentResponse response)
{
sutProvider.GetDependency<IApiService>().GetAttachmentData(cipherId, attachment.Id)
.Returns(response);
await sutProvider.Sut.DownloadAndDecryptAttachmentAsync(cipherId, attachment, null);
await sutProvider.GetDependency<IApiService>().Received(1).GetAttachmentData(cipherId, attachment.Id);
}
}
}

View File

@@ -21,6 +21,8 @@ using Bit.Core.Models.Request;
using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture;
using System.Linq.Expressions; using System.Linq.Expressions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Exceptions;
using NSubstitute.ExceptionExtensions;
namespace Bit.Core.Test.Services namespace Bit.Core.Test.Services
{ {
@@ -172,16 +174,15 @@ namespace Bit.Core.Test.Services
send.Id = null; send.Id = null;
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId); sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response); sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content"); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes); await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
Predicate<SendRequest> sendRequestPredicate = r => Predicate<SendRequest> sendRequestPredicate = r =>
{ {
// Note Send -> SendRequest tested in SendRequestTests // Note Send -> SendRequest tested in SendRequestTests
TestHelper.AssertPropertyEqual(new SendRequest(send, fileContentBytes?.LongLength), r); TestHelper.AssertPropertyEqual(new SendRequest(send, fileContentBytes.Buffer?.LongLength), r);
return true; return true;
}; };
@@ -200,35 +201,21 @@ namespace Bit.Core.Test.Services
[Theory] [Theory]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
public async Task SaveWithServerAsync_NewFileSend_Success(SutProvider<SendService> sutProvider, string userId, SendResponse response, Send send) public async Task SaveWithServerAsync_NewFileSend_AzureUpload_Success(SutProvider<SendService> sutProvider, string userId, SendFileUploadDataResponse response, Send send)
{ {
send.Id = null; send.Id = null;
response.FileUploadType = FileUploadType.Azure;
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId); sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response); sutProvider.GetDependency<IApiService>().PostFileTypeSendAsync(Arg.Any<SendRequest>()).Returns(response);
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content"); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes); await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
Predicate<MultipartFormDataContent> formDataPredicate = fd =>
{
Assert.Equal(2, fd.Count()); // expect a request and file content
var expectedRequest = JsonConvert.SerializeObject(new SendRequest(send, fileContentBytes?.LongLength));
var actualRequest = fd.First().ReadAsStringAsync().GetAwaiter().GetResult();
Assert.Equal(expectedRequest, actualRequest);
var actualFileContent = fd.Skip(1).First().ReadAsByteArrayAsync().GetAwaiter().GetResult();
Assert.Equal(fileContentBytes, actualFileContent);
return true;
};
switch (send.Type) switch (send.Type)
{ {
case SendType.File: case SendType.File:
await sutProvider.GetDependency<IApiService>().Received(1) await sutProvider.GetDependency<IFileUploadService>().Received(1).UploadSendFileAsync(response, send.File.FileName, fileContentBytes);
.PostSendFileAsync(Arg.Is<MultipartFormDataContent>(f => formDataPredicate(f)));
break; break;
case SendType.Text: case SendType.Text:
default: default:
@@ -236,6 +223,23 @@ namespace Bit.Core.Test.Services
} }
} }
[Theory]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
public async Task SaveWithServerAsync_NewFileSend_LegacyFallback_Success(SutProvider<SendService> sutProvider, string userId, Send send, SendResponse response)
{
send.Id = null;
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
var error = new ErrorResponse(null, System.Net.HttpStatusCode.NotFound);
sutProvider.GetDependency<IApiService>().PostFileTypeSendAsync(Arg.Any<SendRequest>()).Throws(new ApiException(error));
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
await sutProvider.GetDependency<IApiService>().Received(1).PostSendFileAsync(Arg.Any<MultipartFormDataContent>());
}
[Theory] [Theory]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
@@ -335,12 +339,12 @@ namespace Bit.Core.Test.Services
byte[] getPbkdf(string password, byte[] key) => byte[] getPbkdf(string password, byte[] key) =>
prefixBytes.Concat(Encoding.UTF8.GetBytes(password)).Concat(key).ToArray(); prefixBytes.Concat(Encoding.UTF8.GetBytes(password)).Concat(key).ToArray();
CipherString encryptBytes(byte[] secret, SymmetricCryptoKey key) => EncString encryptBytes(byte[] secret, SymmetricCryptoKey key) =>
new CipherString($"{prefix}{Convert.ToBase64String(secret)}{Convert.ToBase64String(key.Key)}"); new EncString($"{prefix}{Convert.ToBase64String(secret)}{Convert.ToBase64String(key.Key)}");
CipherString encrypt(string secret, SymmetricCryptoKey key) => EncString encrypt(string secret, SymmetricCryptoKey key) =>
new CipherString($"{prefix}{secret}{Convert.ToBase64String(key.Key)}"); new EncString($"{prefix}{secret}{Convert.ToBase64String(key.Key)}");
byte[] encryptFileBytes(byte[] secret, SymmetricCryptoKey key) => EncByteArray encryptFileBytes(byte[] secret, SymmetricCryptoKey key) =>
secret.Concat(key.Key).ToArray(); new EncByteArray(secret.Concat(key.Key).ToArray());
sutProvider.GetDependency<ICryptoFunctionService>().Pbkdf2Async(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CryptoHashAlgorithm>(), Arg.Any<int>()) sutProvider.GetDependency<ICryptoFunctionService>().Pbkdf2Async(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CryptoHashAlgorithm>(), Arg.Any<int>())
.Returns(info => getPbkdf((string)info[0], (byte[])info[1])); .Returns(info => getPbkdf((string)info[0], (byte[])info[1]));
@@ -370,7 +374,7 @@ namespace Bit.Core.Test.Services
case SendType.File: case SendType.File:
// Only set filename // Only set filename
TestHelper.AssertPropertyEqual(encrypt(view.File.FileName, view.CryptoKey), send.File.FileName); TestHelper.AssertPropertyEqual(encrypt(view.File.FileName, view.CryptoKey), send.File.FileName);
Assert.Equal(encryptFileBytes(fileData, view.CryptoKey), encryptedFileData); Assert.Equal(encryptFileBytes(fileData, view.CryptoKey).Buffer, encryptedFileData.Buffer);
break; break;
default: default:
throw new Exception("Untested send type"); throw new Exception("Untested send type");