Improved Certificate Handling (fixed problem with Certificate validation)

This commit is contained in:
Philipp Crocoll 2013-12-12 10:24:24 +01:00
parent ee8348e688
commit b320477a64
22 changed files with 1265 additions and 869 deletions

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE</DefineConstants>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>

View File

@ -317,7 +317,7 @@ namespace KeePassLib.Serialization
if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized))
return CreateWebClient(ioc, true).OpenRead(new Uri(ioc.Path));
else
throw ex;
throw;
}
}
@ -369,7 +369,7 @@ namespace KeePassLib.Serialization
if ((ex.Response is HttpWebResponse) && (((HttpWebResponse) ex.Response).StatusCode == HttpStatusCode.Unauthorized))
uploadData(IOConnection.CreateWebClient(ioc, true));
else
throw ex;
throw;
}
}

View File

@ -78,5 +78,11 @@ namespace keepass2android
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
void TriggerReload(Context context);
/// <summary>
/// Handles a failed certificate validation. Returns true if the users wants to continue, false otherwise.
/// see http://msdn.microsoft.com/en-us/library/system.net.icertificatepolicy(v=vs.110).aspx
/// </summary>
bool OnServerCertificateError(int certificateProblem);
}
}

View File

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Security;
using Android.Content;
using Android.OS;
using Java.Security.Cert;
using KeePassLib.Serialization;
using KeePassLib.Utility;
@ -12,6 +14,56 @@ namespace keepass2android.Io
{
public class BuiltInFileStorage: IFileStorage
{
public enum CertificateProblem :long
{
CertEXPIRED = 0x800B0101,
CertVALIDITYPERIODNESTING = 0x800B0102,
CertROLE = 0x800B0103,
CertPATHLENCONST = 0x800B0104,
CertCRITICAL = 0x800B0105,
CertPURPOSE = 0x800B0106,
CertISSUERCHAINING = 0x800B0107,
CertMALFORMED = 0x800B0108,
CertUNTRUSTEDROOT = 0x800B0109,
CertCHAINING = 0x800B010A,
CertREVOKED = 0x800B010C,
CertUNTRUSTEDTESTROOT = 0x800B010D,
CertREVOCATION_FAILURE = 0x800B010E,
CertCN_NO_MATCH = 0x800B010F,
CertWRONG_USAGE = 0x800B0110,
CertUNTRUSTEDCA = 0x800B0112
}
private readonly IKp2aApp _app;
class CertificatePolicity: ICertificatePolicy
{
private readonly IKp2aApp _app;
public CertificatePolicity(IKp2aApp app)
{
_app = app;
}
public bool CheckValidationResult(ServicePoint srvPoint, System.Security.Cryptography.X509Certificates.X509Certificate certificate, WebRequest request,
int certificateProblem)
{
if (certificateProblem == 0) //ok
return true;
return _app.OnServerCertificateError(certificateProblem);
}
}
public BuiltInFileStorage(IKp2aApp app)
{
_app = app;
//use the obsolute CertificatePolicy because the ServerCertificateValidationCallback isn't called in Mono for Android (?)
ServicePointManager.CertificatePolicy = new CertificatePolicity(app);
}
public IEnumerable<string> SupportedProtocols
{
get
@ -68,26 +120,51 @@ namespace keepass2android.Io
}
catch (WebException ex)
{
if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound))
{
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
}
ConvertException(ioc, ex);
throw;
}
}
private void ConvertException(IOConnectionInfo ioc, WebException ex)
{
if ((ex.Response is HttpWebResponse) && (((HttpWebResponse) ex.Response).StatusCode == HttpStatusCode.NotFound))
{
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
}
if (ex.Status == WebExceptionStatus.TrustFailure)
{
throw new Exception(_app.GetResourceString(UiStringKey.CertificateFailure), ex);
}
var inner1 = ex.InnerException as IOException;
if (inner1 != null)
{
var inner2 = inner1.InnerException;
if (inner2 != null)
{
if (inner2.Message.Contains("Invalid certificate received from server."))
{
throw new Exception(_app.GetResourceString(UiStringKey.CertificateFailure), ex);
}
}
}
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new BuiltInFileTransaction(ioc, useFileTransaction);
return new BuiltInFileTransaction(ioc, useFileTransaction, this);
}
public class BuiltInFileTransaction : IWriteTransaction
{
private readonly IOConnectionInfo _ioc;
private readonly BuiltInFileStorage _fileStorage;
private readonly FileTransactionEx _transaction;
public BuiltInFileTransaction(IOConnectionInfo ioc, bool useFileTransaction)
public BuiltInFileTransaction(IOConnectionInfo ioc, bool useFileTransaction, BuiltInFileStorage fileStorage)
{
_ioc = ioc;
_fileStorage = fileStorage;
_transaction = new FileTransactionEx(ioc, useFileTransaction);
}
@ -98,12 +175,30 @@ namespace keepass2android.Io
public Stream OpenFile()
{
return _transaction.OpenWrite();
try
{
return _transaction.OpenWrite();
}
catch (WebException ex)
{
_fileStorage.ConvertException(_ioc, ex);
throw;
}
}
public void CommitWrite()
{
_transaction.CommitWrite();
try
{
_transaction.CommitWrite();
}
catch (WebException ex)
{
_fileStorage.ConvertException(_ioc, ex);
throw;
}
}
}

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE</DefineConstants>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
@ -44,6 +44,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Security" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />

View File

@ -46,6 +46,7 @@ namespace keepass2android
CannotMoveGroupHere,
ErrorOcurred,
SynchronizingOtpAuxFile,
SavingOtpAuxFile
SavingOtpAuxFile,
CertificateFailure
}
}

View File

@ -18,9 +18,9 @@ namespace Kp2aUnitTests
{
TestRunner runner = new TestRunner();
// Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) });
//runner.AddTests(typeof(TestSaveDbCached).GetMethod("TestLoadEditSaveWhenModified"));
//runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase)});
runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//runner.AddTests(new List<Type> { typeof(TestSaveDb) });
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });

View File

@ -2,7 +2,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.18051
// Laufzeitversion:4.0.30319.34003
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.

View File

@ -66,7 +66,7 @@ namespace Kp2aUnitTests
var app = CreateTestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, null, password, keyfile, new ActionOnFinish((success, message) =>
LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, null, CreateKey(password, keyfile), keyfile, new ActionOnFinish((success, message) =>
{
if (!success)
Kp2aLog.Log(message);
@ -80,7 +80,20 @@ namespace Kp2aUnitTests
Assert.IsTrue(loadSuccesful);
return app;
}
protected static CompositeKey CreateKey(string password, string keyfile)
{
CompositeKey key = new CompositeKey();
key.AddUserKey(new KcpPassword(password));
if (!String.IsNullOrEmpty(keyfile))
key.AddUserKey(new KcpKeyFile(keyfile));
return key;
}
protected static CompositeKey CreateKey(string password)
{
CompositeKey key = new CompositeKey();
key.AddUserKey(new KcpPassword(password));
return key;
}
protected virtual TestKp2aApp CreateTestKp2aApp()
{
TestKp2aApp app = new TestKp2aApp();

View File

@ -14,6 +14,12 @@ namespace Kp2aUnitTests
[TestClass]
class TestCachingFileStorage: TestBase
{
[TestInitialize]
public void InitTests()
{
TestFileStorage.Offline = false;
}
private TestFileStorage _testFileStorage;
private CachingFileStorage _fileStorage;
private static readonly string CachingTestFile = DefaultDirectory + "cachingTestFile.txt";
@ -37,7 +43,7 @@ namespace Kp2aUnitTests
Assert.AreEqual(MemoryStreamToString(fileContents), _defaultCacheFileContents);
//let the base file storage go offline:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
//now try to read the file again:
MemoryStream fileContents2 = ReadToMemoryStream(_fileStorage, CachingTestFile);
@ -98,7 +104,7 @@ namespace Kp2aUnitTests
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedCachedFileOnLoadId);
//let the base file storage go offline:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
//write something to the cache:
string newContent = "new content";
@ -116,7 +122,7 @@ namespace Kp2aUnitTests
Assert.AreEqual(MemoryStreamToString(fileContents2), newContent);
//now go online and read again. This should trigger a sync and the modified data must be returned
_testFileStorage.Offline = false;
TestFileStorage.Offline = false;
MemoryStream fileContents3 = ReadToMemoryStream(_fileStorage, CachingTestFile);
Assert.AreEqual(MemoryStreamToString(fileContents3), newContent);
@ -141,7 +147,7 @@ namespace Kp2aUnitTests
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedCachedFileOnLoadId);
//let the base file storage go offline:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
//write something to the cache:
string newLocalContent = "new local content";
@ -153,7 +159,7 @@ namespace Kp2aUnitTests
File.WriteAllText(CachingTestFile, "new remote content");
//go online again:
_testFileStorage.Offline = false;
TestFileStorage.Offline = false;
//now try to read the file again:
MemoryStream fileContents2 = ReadToMemoryStream(_fileStorage, CachingTestFile);
@ -231,7 +237,7 @@ namespace Kp2aUnitTests
private void SetupFileStorage()
{
_testFileStorage = new TestFileStorage();
_testFileStorage = new TestFileStorage(new TestKp2aApp());
_testCacheSupervisor = new TestCacheSupervisor();
//_fileStorage = new CachingFileStorage(_testFileStorage, Application.Context.CacheDir.Path, _testCacheSupervisor);
_fileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest_cache", _testCacheSupervisor);

View File

@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.IO;
using Android.Content;
using Android.OS;
using KeePassLib.Serialization;
using keepass2android;
using keepass2android.Io;
namespace Kp2aUnitTests
{
internal class TestFileStorage: IFileStorage
{
private BuiltInFileStorage _builtIn = new BuiltInFileStorage();
public TestFileStorage(IKp2aApp app)
{
_builtIn = new BuiltInFileStorage(app);
}
private BuiltInFileStorage _builtIn;
public bool Offline { get; set; }
public static bool Offline { get; set; }
public IEnumerable<string> SupportedProtocols { get { yield return "test"; } }
@ -96,6 +103,11 @@ namespace Kp2aUnitTests
return _builtIn.RequiresCredentials(ioc);
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
public void CreateDirectory(IOConnectionInfo ioc)
{
throw new NotImplementedException();
@ -110,5 +122,66 @@ namespace Kp2aUnitTests
{
throw new NotImplementedException();
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
throw new NotImplementedException();
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
throw new NotImplementedException();
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
throw new NotImplementedException();
}
public void OnResume(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnStart(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
throw new NotImplementedException();
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return ioc.Path;
}
public string CreateFilePath(string parent, string newFilename)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Security;
using Android.App;
using Android.Content;
using Android.OS;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using keepass2android;
using keepass2android.Io;
@ -30,18 +32,27 @@ namespace Kp2aUnitTests
}
public virtual TestFileStorage TestFileStorage
{
get
{
if (_testFileStorage != null)
return _testFileStorage;
return (TestFileStorage) FileStorage;
}
set { _testFileStorage = value; }
}
public void LockDatabase(bool allowQuickUnlock = true)
{
throw new NotImplementedException();
}
public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string password, string keyFile,
public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey,
ProgressDialogStatusLogger statusLogger)
{
_db.LoadData(this, ioConnectionInfo, memoryStream, password, statusLogger);
_db.LoadData(this, ioConnectionInfo, memoryStream, compKey, statusLogger);
}
public Database GetDb()
{
return _db;
@ -128,10 +139,11 @@ namespace Kp2aUnitTests
public bool TriggerReloadCalled;
private TestFileStorage _testFileStorage;
public TestKp2aApp()
{
FileStorage = new BuiltInFileStorage();
FileStorage = new BuiltInFileStorage(this);
}
public void TriggerReload(Context ctx)
@ -139,6 +151,16 @@ namespace Kp2aUnitTests
TriggerReloadCalled = true;
}
public bool OnServerCertificateError(int sslPolicyErrors)
{
ServerCertificateErrorCalled = true;
return ServerCertificateErrorResponse;
}
public bool ServerCertificateErrorResponse { get; set; }
protected bool ServerCertificateErrorCalled { get; set; }
public void SetYesNoCancelResult(YesNoCancelResult yesNoCancelResult)
{
_yesNoCancelResult = yesNoCancelResult;

View File

@ -1,9 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Android.App;
using Android.OS;
using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android;
@ -21,8 +19,10 @@ namespace Kp2aUnitTests
IKp2aApp app = new TestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = TestDbDirectory+filenameWithoutDir }, null,
password, keyfile, new ActionOnFinish((success, message) =>
var key = CreateKey(password, keyfile);
LoadDb task = new LoadDb(app, new IOConnectionInfo { Path = TestDbDirectory+filenameWithoutDir }, null,
key, keyfile, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
@ -40,6 +40,8 @@ namespace Kp2aUnitTests
Assert.AreEqual(2,app.GetDb().KpDatabase.RootGroup.Entries.Count());
}
[TestMethod]
public void TestLoadWithPasswordOnly()
@ -74,11 +76,12 @@ namespace Kp2aUnitTests
public void LoadFromRemoteWithDomain()
{
var ioc = RemoteDomainIoc; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
var app = new TestKp2aApp();
app.ServerCertificateErrorResponse = true; //accept invalid cert
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, ioc, null, "a", null, new ActionOnFinish((success, message) =>
LoadDb task = new LoadDb(app, ioc, null, CreateKey("a"), null, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
@ -95,14 +98,69 @@ namespace Kp2aUnitTests
}
[TestMethod]
public void LoadFromRemote1and1()
public void LoadErrorWithCertificateTrustFailure()
{
var ioc = RemoteCertFailureIoc; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
var app = new TestKp2aApp();
app.ServerCertificateErrorResponse = false;
app.CreateNewDatabase();
bool loadSuccesful = false;
string theMessage = "";
LoadDb task = new LoadDb(app, ioc, null, CreateKey("test"), null, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
loadSuccesful = success;
theMessage = message;
})
);
ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run();
pt.JoinWorkerThread();
Android.Util.Log.Debug("KP2ATest", "PT.run finished");
Assert.IsFalse(loadSuccesful, "database should not be loaded because invalid certificates are not accepted");
Assert.AreEqual(theMessage, UiStringKey.ErrorOcurred +" "+UiStringKey.CertificateFailure);
}
[TestMethod]
public void LoadWithAcceptedCertificateTrustFailure()
{
var ioc = RemoteCertFailureIoc; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
var app = new TestKp2aApp();
app.ServerCertificateErrorResponse = true;
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, ioc, null, CreateKey("test"), null, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
loadSuccesful = success;
})
);
ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run();
pt.JoinWorkerThread();
Android.Util.Log.Debug("KP2ATest", "PT.run finished");
Assert.IsTrue(loadSuccesful, "database should be loaded because invalid certificates are accepted");
}
[TestMethod]
public void LoadFromRemote1And1()
{
var ioc = RemoteIoc1and1; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) =>
LoadDb task = new LoadDb(app, ioc, null, CreateKey("test"), null, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
@ -120,7 +178,7 @@ namespace Kp2aUnitTests
[TestMethod]
public void LoadFromRemote1and1NonExisting()
public void LoadFromRemote1And1NonExisting()
{
var ioc = RemoteIoc1and1NonExisting; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
@ -128,7 +186,7 @@ namespace Kp2aUnitTests
bool loadSuccesful = false;
bool gotError = false;
LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) =>
LoadDb task = new LoadDb(app, ioc, null, CreateKey("test"), null, new ActionOnFinish((success, message) =>
{
if (!success)
{
@ -148,7 +206,7 @@ namespace Kp2aUnitTests
}
[TestMethod]
public void LoadFromRemote1and1WrongCredentials()
public void LoadFromRemote1And1WrongCredentials()
{
var ioc = RemoteIoc1and1WrongCredentials; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
@ -156,7 +214,7 @@ namespace Kp2aUnitTests
bool loadSuccesful = false;
bool gotError = false;
LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) =>
LoadDb task = new LoadDb(app, ioc, null, CreateKey("test"), null, new ActionOnFinish((success, message) =>
{
if (!success)
{
@ -179,7 +237,7 @@ namespace Kp2aUnitTests
[TestMethod]
public void FileNotFoundExceptionWithWebDav()
{
var fileStorage = new BuiltInFileStorage();
var fileStorage = new BuiltInFileStorage(new TestKp2aApp());
//should work:
using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1))

View File

@ -22,12 +22,12 @@ namespace Kp2aUnitTests
class TestSaveDbCached: TestBase
{
private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor();
private TestFileStorage _testFileStorage = new TestFileStorage();
protected override TestKp2aApp CreateTestKp2aApp()
{
TestKp2aApp app = base.CreateTestKp2aApp();
app.FileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor);
app.FileStorage = new CachingFileStorage(new TestFileStorage(app), "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor);
return app;
}

View File

@ -19,13 +19,20 @@ namespace Kp2aUnitTests
[TestClass]
internal class TestSynchronizeCachedDatabase : TestBase
{
[TestInitialize]
public void InitTests()
{
TestFileStorage.Offline = false;
}
private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor();
private TestFileStorage _testFileStorage = new TestFileStorage();
protected override TestKp2aApp CreateTestKp2aApp()
{
TestKp2aApp app = base.CreateTestKp2aApp();
app.FileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor);
app.TestFileStorage = new TestFileStorage(app);
app.FileStorage = new CachingFileStorage(app.TestFileStorage, "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor);
return app;
}
@ -58,7 +65,7 @@ namespace Kp2aUnitTests
Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.FilesInSync));
//go offline:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
//sync when offline (->error)
Synchronize(app, out wasSuccessful, out resultMessage);
@ -73,7 +80,7 @@ namespace Kp2aUnitTests
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId);
//go online again:
_testFileStorage.Offline = false;
TestFileStorage.Offline = false;
//sync with local changes only (-> upload):
Synchronize(app, out wasSuccessful, out resultMessage);
@ -81,10 +88,10 @@ namespace Kp2aUnitTests
Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
//ensure both files are identical and up to date now:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
var appOfflineLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntOpenFromRemoteId);
_testFileStorage.Offline = false;
TestFileStorage.Offline = false;
var appRemoteLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId);
@ -92,6 +99,8 @@ namespace Kp2aUnitTests
AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase);
}
[TestMethod]
public void TestSyncWhenRemoteDeleted()
{
@ -133,11 +142,11 @@ namespace Kp2aUnitTests
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId);
var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
app2.FileStorage = _testFileStorage; //give app2 direct access to the remote file
app2.FileStorage = app.TestFileStorage; //give app2 direct access to the remote file
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId);
//go offline:
_testFileStorage.Offline = true;
TestFileStorage.Offline = true;
string resultMessage;
@ -153,7 +162,7 @@ namespace Kp2aUnitTests
_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId);
//go online again:
_testFileStorage.Offline = false;
TestFileStorage.Offline = false;
//...and remote only for "app2":
SaveDatabase(app2);

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="30" android:versionName="0.9.2" package="keepass2android.keepass2android" android:installLocation="auto">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="31" android:versionName="0.9.2-r2" package="keepass2android.keepass2android" android:installLocation="auto">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="14" />
<permission android:description="@string/permission_desc" android:icon="@drawable/ic_launcher" android:label="KP2A internal file browsing" android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" android:protectionLevel="signature" />
<application android:label="keepass2android" android:icon="@drawable/ic_launcher">

File diff suppressed because it is too large Load Diff

View File

@ -89,6 +89,7 @@
<string name="UseFileTransactions_key">UseFileTransactions</string>
<string name="LockWhenScreenOff_key">LockWhenScreenOff</string>
<string name="UseOfflineCache_key">UseOfflineCache</string>
<string name="AcceptAllServerCertificates_key">AcceptAllServerCertificates</string>
<string name="CheckForFileChangesOnSave_key">CheckForFileChangesOnSave</string>
<string name="MarketURL">market://details?id=</string>
@ -122,6 +123,13 @@
<item>28</item>
</string-array>
<string name="AcceptAllServerCertificates_default">WARN</string>
<string-array name="AcceptAllServerCertificates_values">
<item>IGNORE</item>
<item>WARN</item>
<item>ERROR</item>
</string-array>
<string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string>
<bool name="ShowUnlockedNotification_default">true</bool>

View File

@ -239,6 +239,11 @@
<string name="LockWhenScreenOff_summary">Lock the database when screen is switched off.</string>
<string name="UseOfflineCache_title">Database caching</string>
<string name="UseOfflineCache_summary">Keep a copy of remote database files in the application cache directory. This allows to use remote databases even when offline.</string>
<string name="AcceptAllServerCertificates_title">SSL certificates</string>
<string name="AcceptAllServerCertificates_summary">Define the behavior when certificate validation fails. Note: you can install certificates on your device if validation fails!</string>
<string name="ClearOfflineCache_title">Clear cache?</string>
<string name="ClearOfflineCache_question">This will delete all cached database files. Any changes you made while being offline which have not yet been synchronized will be lost! Continue?</string>
<string name="CheckForFileChangesOnSave_title">Check for modifications</string>
@ -372,16 +377,20 @@
<string name="SavingOtpAuxFile">Saving auxiliary OTP file…</string>
<string name="loading">Loading…</string>
<string name="CertificateWarning">Warning: Server certificate validation failed: %1$s. Install appropriate root certificate on your device or see settings!</string>
<string name="CertificateFailure">Error: Server certificate validation failed! Install appropriate root certificate on your device or see settings!</string>
<string name="ChangeLog_title">Change log</string>
<string name="ChangeLog_0_9_2">
<b>Version 0.9.2b (preview)</b>\n
<b>Version 0.9.2</b>\n
* Added OTP support (compatible with OtpKeyProv plugin)\n
* Integrated NFC support for OTPs from YubiKey NEO \n
* Several UI improvements\n
* Integrated Keepass 2.24 library\n
* Added option to kill the app process (see settings)\n
* Improved SSL certificate validation\n
* Bug fixes\n
</string>
@ -501,4 +510,10 @@ Initial public release
<item>Password + OTP</item>
<item>Password + OTP secret (recovery mode)</item>
</string-array>
<string-array name="AcceptAllServerCertificates_options">
<item>Ignore certificate validation failures</item>
<item>Warn when validation fails</item>
<item>Do not accept invalid certificates</item>
</string-array>
</resources>

View File

@ -204,6 +204,16 @@
android:defaultValue="true"
android:title="@string/UseOfflineCache_title"
android:key="@string/UseOfflineCache_key" />
<ListPreference
android:key="@string/AcceptAllServerCertificates_key"
android:title="@string/AcceptAllServerCertificates_title"
android:summary="@string/AcceptAllServerCertificates_summary"
android:entries="@array/AcceptAllServerCertificates_options"
android:entryValues="@array/AcceptAllServerCertificates_values"
android:dialogTitle="@string/AcceptAllServerCertificates_title"
android:defaultValue="@string/AcceptAllServerCertificates_default"/>
<CheckBoxPreference
android:enabled="true"
android:persistent="true"

View File

@ -353,7 +353,7 @@ namespace keepass2android
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{
if (iocInfo.IsLocalFile())
return new BuiltInFileStorage();
return new BuiltInFileStorage(this);
else
{
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
@ -399,7 +399,7 @@ namespace keepass2android
new GoogleDriveFileStorage(Application.Context, this),
new SkyDriveFileStorage(Application.Context, this),
#endif
new BuiltInFileStorage()
new BuiltInFileStorage(this)
};
}
return _fileStorages;
@ -415,6 +415,61 @@ namespace keepass2android
});
}
private String GetProblemMessage(BuiltInFileStorage.CertificateProblem problem)
{
String problemMessage;
const BuiltInFileStorage.CertificateProblem problemList = new BuiltInFileStorage.CertificateProblem();
string problemCodeName = Enum.GetName(typeof(BuiltInFileStorage.CertificateProblem), problem);
if (problemCodeName != null)
problemMessage = problemCodeName;
else
problemMessage = "Unknown Certificate Problem";
return problemMessage;
}
enum ValidationMode
{
Ignore, Warn, Error
}
public bool OnServerCertificateError(int certificateProblem)
{
var prefs = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
ValidationMode validationMode = ValidationMode.Warn;
string strValMode = prefs.GetString(Application.Context.Resources.GetString(Resource.String.AcceptAllServerCertificates_key),
Application.Context.Resources.GetString(Resource.String.AcceptAllServerCertificates_default));
if (strValMode == "IGNORE")
validationMode = ValidationMode.Ignore;
else if (strValMode == "ERROR")
validationMode = ValidationMode.Error;
;
switch (validationMode)
{
case ValidationMode.Ignore:
return true;
case ValidationMode.Warn:
ShowToast(Application.Context.GetString(Resource.String.CertificateWarning,
new Java.Lang.Object[]
{
GetProblemMessage(
(BuiltInFileStorage.CertificateProblem)
(System.UInt32) certificateProblem)
}));
return true;
case ValidationMode.Error:
return false;
default:
throw new ArgumentOutOfRangeException();
}
}
internal void OnTerminate()
{
@ -492,7 +547,7 @@ namespace keepass2android
public void ClearOfflineCache()
{
new CachingFileStorage(new BuiltInFileStorage(), Application.Context.CacheDir.Path, this).ClearCache();
new CachingFileStorage(new BuiltInFileStorage(this), Application.Context.CacheDir.Path, this).ClearCache();
}
public IFileStorage GetFileStorage(string protocolId)
@ -508,7 +563,7 @@ namespace keepass2android
{
if (iocInfo.IsLocalFile())
return new BuiltInFileStorage();
return new BuiltInFileStorage(this);
else
{
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);

View File

@ -29,7 +29,7 @@
<DebugType>full</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE</DefineConstants>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_KEYTRANSFORM;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>