diff --git a/.gitignore b/.gitignore index b170c7b1..8eaed0c7 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ src/java/KP2ASoftkeyboard_AS/build/generated/mockable-android-23.jar *.rawproto src/java/Keepass2AndroidPluginSDK2/build/generated/mockable-Google-Inc.-Google-APIs-23.jar /src/.vs +/src/JavaFileStorageBindings/Jars/JavaFileStorage-release.aar +/src/PluginSdkBinding/Jars/app-debug.aar diff --git a/.gitmodules b/.gitmodules index 997a697f..7240f889 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/PhilippC/Xamarin-Samsung-Pass.git [submodule "src/netftpandroid"] path = src/netftpandroid - url = https://git01.codeplex.com/forks/philippc/netftpandroid + url = https://github.com/PhilippC/netftpandroid.git diff --git a/docs/OreoAutoFill.md b/docs/OreoAutoFill.md index 75b4818a..49d230a0 100644 --- a/docs/OreoAutoFill.md +++ b/docs/OreoAutoFill.md @@ -12,7 +12,7 @@ As of January 2018, the following browsers are known to have Android Autofill su These browsers do not (yet) have autofill support: * Google Chrome -* Firefox for Android +* Firefox for Android ([bugzilla entry](https://bugzilla.mozilla.org/show_bug.cgi?id=1352011)) * Brave-Browser * Opera diff --git a/docs/README.md b/docs/README.md index 33bc2729..5fb3613b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,11 @@ - # What is Keepass2Android? Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database". This database is secured with a so-called master password. The master password typically is a strong password and can be complemented with a second factor for additional security. -The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KepassX on Linux. +The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KeepassX on Linux. # Where to get it? Regular stable releases of Keepass2Android are available on [Google Play](https://play.google.com/store/apps/details?id=keepass2android.keepass2android). -Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android). Please join the [Beta tester group](https://plus.google.com/communities/107293657110547776032) for news and discussions about the latest beta releases. +Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). Please join the [Beta tester group](https://plus.google.com/communities/107293657110547776032) for news and discussions about the latest beta releases. # How can I contribute? * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android) @@ -16,4 +15,4 @@ Beta-releases can be obtained by opting in to the [Beta testing channel](https:/ # How do I learn more? Please see the [documentation](Documentation.md). -[![Build Status](https://www.bitrise.io/app/43a23ab54dee9f7e/status.svg?token=2vryTsMQzTX3XRPikhgRwA&branch=nonet)](https://www.bitrise.io/app/43a23ab54dee9f7e) \ No newline at end of file +[![Build Status](https://www.bitrise.io/app/43a23ab54dee9f7e/status.svg?token=2vryTsMQzTX3XRPikhgRwA&branch=nonet)](https://www.bitrise.io/app/43a23ab54dee9f7e) diff --git a/graphics/launcher_icon/Logo-blue.svg b/graphics/launcher_icon/Logo-blue.svg new file mode 100644 index 00000000..e8111337 --- /dev/null +++ b/graphics/launcher_icon/Logo-blue.svg @@ -0,0 +1,3287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/launcher_icon/Logo-green-foreground.svg b/graphics/launcher_icon/Logo-green-foreground.svg new file mode 100644 index 00000000..c24cad1b --- /dev/null +++ b/graphics/launcher_icon/Logo-green-foreground.svg @@ -0,0 +1,47 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/graphics/launcher_icon/Logo-green.svg b/graphics/launcher_icon/Logo-green.svg new file mode 100644 index 00000000..1f684467 --- /dev/null +++ b/graphics/launcher_icon/Logo-green.svg @@ -0,0 +1,3279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj b/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj index c0c6d534..e0dcff19 100644 --- a/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj +++ b/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj @@ -10,7 +10,7 @@ AndroidFileChooserBinding AndroidFileChooserBinding 512 - v8.0 + v8.1 True diff --git a/src/JavaFileStorageBindings/Jars/adal-1.14.0.aar b/src/JavaFileStorageBindings/Jars/adal-1.14.0.aar new file mode 100644 index 00000000..e3e4c25e Binary files /dev/null and b/src/JavaFileStorageBindings/Jars/adal-1.14.0.aar differ diff --git a/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj b/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj index 7c7486b5..8646b9f2 100644 --- a/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj +++ b/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj @@ -11,7 +11,7 @@ JavaFileStorageBindings 512 True - v8.0 + v8.1 true @@ -60,6 +60,7 @@ + @@ -92,9 +93,6 @@ - - - diff --git a/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj b/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj index 3ff0ddff..885738cb 100644 --- a/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj +++ b/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj @@ -10,7 +10,7 @@ KP2AKdbLibraryBinding KP2AKdbLibraryBinding 512 - v8.0 + v8.1 True diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs index c7a66a58..cf7bc634 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs @@ -55,7 +55,10 @@ namespace KeePassLib.Cryptography.KeyDerivation get { return "AES-KDF"; } } - public AesKdf() + public override byte[] GetSeed(KdfParameters p) + { return p.GetByteArray(ParamSeed); } + + public AesKdf() { } diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs index bc858ca8..8663eb2c 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -68,7 +68,10 @@ namespace KeePassLib.Cryptography.KeyDerivation get { return "Argon2"; } } - public Argon2Kdf() + public override byte[] GetSeed(KdfParameters p) + { return p.GetByteArray(ParamSalt); } + + public Argon2Kdf() { } diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs index 994e0c7e..7e4f5b5c 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs @@ -36,7 +36,9 @@ namespace KeePassLib.Cryptography.KeyDerivation get; } - public virtual KdfParameters GetDefaultParameters() + public abstract byte[] GetSeed(KdfParameters p); + + public virtual KdfParameters GetDefaultParameters() { return new KdfParameters(this.Uuid); } diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj index d3029e5f..d5ba5757 100644 --- a/src/KeePassLib2Android/KeePassLib2Android.csproj +++ b/src/KeePassLib2Android/KeePassLib2Android.csproj @@ -12,7 +12,7 @@ Resources\Resource.designer.cs Resource KeePassLib2Android - v8.0 + v8.1 True 8482b288 diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index a347fc11..7e935df0 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -20,11 +20,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Text; - using KeePassLib.Cryptography; using KeePassLib.Cryptography.KeyDerivation; -using KeePassLib.Native; using KeePassLib.Resources; using KeePassLib.Security; using KeePassLib.Utility; @@ -168,7 +165,7 @@ namespace KeePassLib.Keys /// Creates the composite key from the supplied user key sources (password, /// key file, user account, computer ID, etc.). /// - private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed) + private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed, byte[] mPbKdfSeed) { ValidateUserKeys(); @@ -178,7 +175,7 @@ namespace KeePassLib.Keys foreach(IUserKey pKey in m_vUserKeys) { if (pKey is ISeedBasedUserKey) - ((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed); + ((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed, mPbKdfSeed); ProtectedBinary b = pKey.KeyData; if(b != null) { @@ -211,15 +208,17 @@ namespace KeePassLib.Keys { if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } - byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed); + + KdfEngine kdf = KdfPool.Get(p.KdfUuid); + if (kdf == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + p.KdfUuid.ToHexString() + "."); + + byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed, kdf.GetSeed(p)); if((pbRaw32 == null) || (pbRaw32.Length != 32)) { Debug.Assert(false); return null; } - KdfEngine kdf = KdfPool.Get(p.KdfUuid); - if(kdf == null) // CryptographicExceptions are translated to "file corrupted" - throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + - KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + - "UUID: " + p.KdfUuid.ToHexString() + "."); byte[] pbTrf32 = kdf.Transform(pbRaw32, p); if(pbTrf32 == null) { Debug.Assert(false); return null; } @@ -256,7 +255,7 @@ namespace KeePassLib.Keys public interface ISeedBasedUserKey { - void SetParams(byte[] masterSeed); + void SetParams(byte[] masterSeed, byte[] mPbKdfSeed); } public sealed class InvalidCompositeKeyException : Exception diff --git a/src/KeePassLib2Android/Kp2aLog.cs b/src/KeePassLib2Android/Kp2aLog.cs index b2a5a195..f364a377 100644 --- a/src/KeePassLib2Android/Kp2aLog.cs +++ b/src/KeePassLib2Android/Kp2aLog.cs @@ -100,7 +100,8 @@ namespace keepass2android int count = 0; while (File.Exists(LogFilename + "." + count)) count++; - File.Move(LogFilename, LogFilename + "." + count); + if (count > 0) + File.Move(LogFilename, LogFilename + "." + count); } diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs index de30aae6..1c81bc50 100644 --- a/src/Kp2aBusinessLogic/IKp2aApp.cs +++ b/src/Kp2aBusinessLogic/IKp2aApp.cs @@ -52,7 +52,7 @@ namespace keepass2android /// /// Tell the app that the file from ioc was opened with keyfile. /// - void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile); + void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile, string displayName = ""); /// /// Creates a new database and returns it diff --git a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs index 56cdfc1a..674cb28e 100644 --- a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs +++ b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs @@ -2,11 +2,15 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Serialization; +using Android; using Android.Content; using Android.Database; using Android.OS; using Android.Provider; +using Java.IO; using KeePassLib.Serialization; +using Console = System.Console; namespace keepass2android.Io { @@ -28,7 +32,12 @@ namespace keepass2android.Io get { yield return "content"; } } - public void Delete(IOConnectionInfo ioc) + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) { throw new NotImplementedException(); } @@ -45,7 +54,20 @@ namespace keepass2android.Io public Stream OpenFileForRead(IOConnectionInfo ioc) { - return _ctx.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(ioc.Path)); + try + { + return _ctx.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(ioc.Path)); + } + catch (Exception e) + { + if (e.Message.Contains("requires that you obtain access using ACTION_OPEN_DOCUMENT")) + { + //looks like permission was revoked. + throw new DocumentAccessRevokedException(); + } + throw; + } + } public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) @@ -261,7 +283,26 @@ namespace keepass2android.Io } - class AndroidContentWriteTransaction : IWriteTransaction + public class DocumentAccessRevokedException : Exception + { + public DocumentAccessRevokedException() + { + } + + public DocumentAccessRevokedException(string message) : base(message) + { + } + + public DocumentAccessRevokedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected DocumentAccessRevokedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + class AndroidContentWriteTransaction : IWriteTransaction { private readonly string _path; private readonly Context _ctx; @@ -286,11 +327,15 @@ namespace keepass2android.Io public void CommitWrite() { - using (Stream outputStream = _ctx.ContentResolver.OpenOutputStream(Android.Net.Uri.Parse(_path))) + ParcelFileDescriptor fileDescriptor = _ctx.ContentResolver.OpenFileDescriptor(Android.Net.Uri.Parse(_path), "w"); + + using (var outputStream = new FileOutputStream(fileDescriptor.FileDescriptor)) { byte[] data = _memoryStream.ToArray(); outputStream.Write(data, 0, data.Length); + outputStream.Close(); } + fileDescriptor.Close(); } diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs index 65525e5e..3de06f17 100644 --- a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Security; @@ -10,10 +11,14 @@ using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; +using Android.Preferences; +using Android.Support.V13.App; +using Android.Support.V4.App; using Java.IO; +using Java.Util; using KeePassLib.Serialization; using KeePassLib.Utility; - +using ActivityCompat = Android.Support.V13.App.ActivityCompat; using File = System.IO.File; using FileNotFoundException = System.IO.FileNotFoundException; using IOException = System.IO.IOException; @@ -59,7 +64,12 @@ namespace keepass2android.Io public abstract IEnumerable SupportedProtocols { get; } - public void Delete(IOConnectionInfo ioc) + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) { //todo check if directory IOConnection.DeleteFile(ioc); @@ -253,10 +263,14 @@ namespace keepass2android.Io if (requiresPermission && (Build.VERSION.SdkInt >= BuildVersionCodes.M)) { - if (activity.Activity.CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == + if ((activity.Activity.CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == Permission.Denied) - { - activity.StartFileUsageProcess(ioc, requestCode, alwaysReturnSuccess); + || + (activity.Activity.CheckSelfPermission(Manifest.Permission.ReadExternalStorage) == + Permission.Denied)) + + { + activity.StartFileUsageProcess(ioc, requestCode, alwaysReturnSuccess); return; } } @@ -274,7 +288,7 @@ namespace keepass2android.Io public void OnCreate(IFileStorageSetupActivity fileStorageSetupActivity, Bundle savedInstanceState) { - ((Activity)fileStorageSetupActivity).RequestPermissions(new[] { Manifest.Permission.WriteExternalStorage }, 0); + Android.Support.V4.App.ActivityCompat.RequestPermissions(((Activity)fileStorageSetupActivity), new[] { Manifest.Permission.WriteExternalStorage, Manifest.Permission.ReadExternalStorage }, 0); } public void OnResume(IFileStorageSetupActivity activity) @@ -367,7 +381,14 @@ namespace keepass2android.Io { if (ioc.IsLocalFile()) { - if (IsLocalFileFlaggedReadOnly(ioc)) + if (IsLocalBackup(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_LocalBackup; + return true; + } + + if (IsLocalFileFlaggedReadOnly(ioc)) { if (reason != null) reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyFlag; @@ -388,7 +409,22 @@ namespace keepass2android.Io return false; } - private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc) + private readonly Dictionary _isLocalBackupCache = new Dictionary(); + public bool IsLocalBackup(IOConnectionInfo ioc) + { + if (!ioc.IsLocalFile()) + return false; + bool result; + if (_isLocalBackupCache.TryGetValue(ioc.Path, out result)) + return result; + + result = (PreferenceManager.GetDefaultSharedPreferences(Application.Context) + .GetBoolean(IoUtil.GetIocPrefKey(ioc, "is_local_backup"), false)); + _isLocalBackupCache[ioc.Path] = result; + return result; + } + + private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc) { //see http://stackoverflow.com/a/33292700/292233 try @@ -412,7 +448,7 @@ namespace keepass2android.Io public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, string[] permissions, Permission[] grantResults) { - fileStorageSetupActivity.State.PutBoolean(PermissionGrantedKey, grantResults[0] == Permission.Granted); + fileStorageSetupActivity.State.PutBoolean(PermissionGrantedKey, grantResults.All(res => res == Permission.Granted)); } } diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs index 31d1d0bc..4ecc2584 100644 --- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs @@ -84,7 +84,12 @@ namespace keepass2android.Io public IEnumerable SupportedProtocols { get { return _cachedStorage.SupportedProtocols; } } - public void DeleteFile(IOConnectionInfo ioc) + public bool UserShouldBackup + { + get { return _cachedStorage.UserShouldBackup; } + } + + public void DeleteFile(IOConnectionInfo ioc) { if (IsCached(ioc)) { diff --git a/src/Kp2aBusinessLogic/Io/DropboxFileStorage.cs b/src/Kp2aBusinessLogic/Io/DropboxFileStorage.cs index e4b3e774..4ad64472 100644 --- a/src/Kp2aBusinessLogic/Io/DropboxFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/DropboxFileStorage.cs @@ -10,7 +10,11 @@ namespace keepass2android.Io { } - + + public override bool UserShouldBackup + { + get { return false; } + } } public partial class DropboxAppFolderFileStorage: JavaFileStorage @@ -20,8 +24,12 @@ namespace keepass2android.Io { } - - } + public override bool UserShouldBackup + { + get { return false; } + } + + } } #endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/GDriveFileStorage.cs b/src/Kp2aBusinessLogic/Io/GDriveFileStorage.cs index dbd5c975..1636e232 100644 --- a/src/Kp2aBusinessLogic/Io/GDriveFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/GDriveFileStorage.cs @@ -22,6 +22,10 @@ namespace keepass2android.Io } + public override bool UserShouldBackup + { + get { return false; } + } } } #endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/IFileStorage.cs b/src/Kp2aBusinessLogic/Io/IFileStorage.cs index 79cab4a2..33793ce5 100644 --- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs @@ -46,9 +46,14 @@ namespace keepass2android.Io /// /// returns the protocol ids supported by this FileStorage. Can return pseudo-protocols like "dropbox" or real protocols like "ftp" /// - IEnumerable SupportedProtocols { get; } + IEnumerable SupportedProtocols { get; } - /// + /// + /// returns true if users should backup files on this file storage (if the file is important). Can be false for cloud providers with built-in versioning or backups. + /// + bool UserShouldBackup { get; } + + /// /// Deletes the given file or directory. /// void Delete(IOConnectionInfo ioc); diff --git a/src/Kp2aBusinessLogic/Io/IoUtil.cs b/src/Kp2aBusinessLogic/Io/IoUtil.cs index ee1463f8..eeb8ba56 100644 --- a/src/Kp2aBusinessLogic/Io/IoUtil.cs +++ b/src/Kp2aBusinessLogic/Io/IoUtil.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Text; using Android.Content; using Android.OS; using Java.IO; using KeePassLib.Serialization; +using KeePassLib.Utility; namespace keepass2android.Io { @@ -125,5 +127,53 @@ namespace keepass2android.Io return ctx.FilesDir; } - } + //creates a local ioc where the sourceIoc can be stored to + public static IOConnectionInfo GetInternalIoc(IOConnectionInfo sourceIoc, Context ctx) + { + Java.IO.File internalDirectory = IoUtil.GetInternalDirectory(ctx); + string targetPath = UrlUtil.GetFileName(sourceIoc.Path); + targetPath = targetPath.Trim("|\\?*<\":>+[]/'".ToCharArray()); + if (targetPath == "") + targetPath = "internal"; + if (new File(internalDirectory, targetPath).Exists()) + { + int c = 1; + var ext = UrlUtil.GetExtension(targetPath); + var filenameWithoutExt = UrlUtil.StripExtension(targetPath); + do + { + c++; + targetPath = filenameWithoutExt + c; + if (!String.IsNullOrEmpty(ext)) + targetPath += "." + ext; + } while (new File(internalDirectory, targetPath).Exists()); + } + return IOConnectionInfo.FromPath(new File(internalDirectory, targetPath).CanonicalPath); + } + + public static IOConnectionInfo ImportFileToInternalDirectory(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app) + { + var targetIoc = GetInternalIoc(sourceIoc, ctx); + + + IoUtil.Copy(targetIoc, sourceIoc, app); + return targetIoc; + } + + public static string GetIocPrefKey(IOConnectionInfo ioc, string suffix) + { + var iocAsHexString = IocAsHexString(ioc); + + return "kp2a_ioc_key_" + iocAsHexString + suffix; + } + + + public static string IocAsHexString(IOConnectionInfo ioc) + { + SHA256Managed sha256 = new SHA256Managed(); + string iocAsHexString = + MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray()))); + return iocAsHexString; + } + } } diff --git a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs index a7173d26..0e6870e0 100644 --- a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs @@ -22,9 +22,10 @@ namespace keepass2android.Io protected string Protocol { get { return _jfs.ProtocolId; } } public virtual IEnumerable SupportedProtocols { get { yield return Protocol; } } + public abstract bool UserShouldBackup { get; } - private readonly IJavaFileStorage _jfs; + private readonly IJavaFileStorage _jfs; private readonly IKp2aApp _app; public JavaFileStorage(IJavaFileStorage jfs, IKp2aApp app) diff --git a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs index 5e19d66a..f975ac16 100644 --- a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs @@ -161,7 +161,12 @@ namespace keepass2android.Io } } - public void Delete(IOConnectionInfo ioc) + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) { try { @@ -226,7 +231,7 @@ namespace keepass2android.Io - internal Uri IocToUri(IOConnectionInfo ioc) + public static Uri IocToUri(IOConnectionInfo ioc) { if (!string.IsNullOrEmpty(ioc.UserName)) { @@ -570,7 +575,7 @@ namespace keepass2android.Io { _client = _fileStorage.GetClient(_ioc, false); - _stream = _client.OpenWrite(_fileStorage.IocToUri(_iocTemp).PathAndQuery); + _stream = _client.OpenWrite(NetFtpFileStorage.IocToUri(_iocTemp).PathAndQuery); return _stream; } catch (FtpCommandException ex) @@ -590,8 +595,8 @@ namespace keepass2android.Io //make sure target file does not exist: //try { - if (_client.FileExists(_fileStorage.IocToUri(_ioc).PathAndQuery)) - _client.DeleteFile(_fileStorage.IocToUri(_ioc).PathAndQuery); + if (_client.FileExists(NetFtpFileStorage.IocToUri(_ioc).PathAndQuery)) + _client.DeleteFile(NetFtpFileStorage.IocToUri(_ioc).PathAndQuery); } //catch (FtpCommandException) @@ -599,8 +604,8 @@ namespace keepass2android.Io //TODO get a new clien? might be stale } - _client.Rename(_fileStorage.IocToUri(_iocTemp).PathAndQuery, - _fileStorage.IocToUri(_ioc).PathAndQuery); + _client.Rename(NetFtpFileStorage.IocToUri(_iocTemp).PathAndQuery, + NetFtpFileStorage.IocToUri(_ioc).PathAndQuery); } catch (FtpCommandException ex) diff --git a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs index bf93653e..725b7f31 100644 --- a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs @@ -32,7 +32,12 @@ namespace keepass2android.Io get { return _baseStorage.SupportedProtocols; } } - public void Delete(IOConnectionInfo ioc) + public bool UserShouldBackup + { + get { return _baseStorage.UserShouldBackup; } + } + + public void Delete(IOConnectionInfo ioc) { _baseStorage.Delete(ioc); } diff --git a/src/Kp2aBusinessLogic/Io/OneDriveFileStorage.cs b/src/Kp2aBusinessLogic/Io/OneDriveFileStorage.cs index c5d522a8..25ff4d9b 100644 --- a/src/Kp2aBusinessLogic/Io/OneDriveFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OneDriveFileStorage.cs @@ -33,6 +33,11 @@ namespace keepass2android.Io yield return "onedrive"; } } + + public override bool UserShouldBackup + { + get { return false; } + } } } #endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/SftpFileStorage.cs b/src/Kp2aBusinessLogic/Io/SftpFileStorage.cs index ce2f3a9c..7f54a577 100644 --- a/src/Kp2aBusinessLogic/Io/SftpFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/SftpFileStorage.cs @@ -10,7 +10,11 @@ namespace keepass2android.Io { } - + + public override bool UserShouldBackup + { + get { return true; } + } } diff --git a/src/Kp2aBusinessLogic/Io/WebDavFileStorage.cs b/src/Kp2aBusinessLogic/Io/WebDavFileStorage.cs index 4459d4f8..cb4890b9 100644 --- a/src/Kp2aBusinessLogic/Io/WebDavFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/WebDavFileStorage.cs @@ -33,7 +33,12 @@ namespace keepass2android.Io } } - public static string Owncloud2Webdav(string owncloudUrl) + public override bool UserShouldBackup + { + get { return true; } + } + + public static string Owncloud2Webdav(string owncloudUrl) { string owncloudPrefix = "owncloud://"; if (owncloudUrl.StartsWith(owncloudPrefix)) diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj index 377cdcef..9dc78489 100644 --- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj +++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj @@ -12,9 +12,10 @@ 512 Resources\Resource.Designer.cs Off - v8.0 + v8.1 true - 06ffb71c + + true @@ -54,6 +55,36 @@ + + ..\packages\Xamarin.Android.Arch.Core.Common.1.0.0\lib\MonoAndroid80\Xamarin.Android.Arch.Core.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.0.1\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.0.0\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Runtime.dll + + + ..\packages\Xamarin.Android.Support.Annotations.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Annotations.dll + + + ..\packages\Xamarin.Android.Support.Compat.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Compat.dll + + + ..\packages\Xamarin.Android.Support.Core.UI.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Core.UI.dll + + + ..\packages\Xamarin.Android.Support.Core.Utils.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Core.Utils.dll + + + ..\packages\Xamarin.Android.Support.Fragment.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Fragment.dll + + + ..\packages\Xamarin.Android.Support.Media.Compat.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.Media.Compat.dll + + + ..\packages\Xamarin.Android.Support.v13.26.1.0.1\lib\MonoAndroid80\Xamarin.Android.Support.v13.dll + @@ -131,7 +162,35 @@ + + + + + + + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + + + + + + + + + + + + + + + + + + + + + 0){ length=(length<<8)+(buf[index++]&0xff); } + } + indexp[0]=index; + return length; + } + byte[] getContent() { + int[] indexp=new int[1]; + indexp[0]=start+1; + int length = getLength(indexp); + int index=indexp[0]; + byte[] tmp = new byte[length]; + System.arraycopy(buf, index, tmp, 0, tmp.length); + return tmp; + } + ASN1[] getContents() throws ASN1Exception { + int typ = buf[start]; + int[] indexp=new int[1]; + indexp[0]=start+1; + int length = getLength(indexp); + if(typ == 0x05){ + return new ASN1[0]; + } + int index=indexp[0]; + java.util.Vector values = new java.util.Vector(); + while(length>0) { + index++; length--; + int tmp=index; + indexp[0]=index; + int l=getLength(indexp); + index=indexp[0]; + length-=(index-tmp); + values.addElement(new ASN1(buf, tmp-1, 1+(index-tmp)+l)); + index+=l; + length-=l; + } + ASN1[] result = new ASN1[values.size()]; + for(int i = 0; i =64 ? 521 : + (prv_array.length>=48 ? 384 : 256); + } + + void generate(int key_size) throws JSchException{ + this.key_size=key_size; + try{ + Class c=Class.forName(jsch.getConfig("keypairgen.ecdsa")); + KeyPairGenECDSA keypairgen=(KeyPairGenECDSA)(c.newInstance()); + keypairgen.init(key_size); + prv_array=keypairgen.getD(); + r_array=keypairgen.getR(); + s_array=keypairgen.getS(); + name=Util.str2byte(names[prv_array.length>=64 ? 2 : + (prv_array.length>=48 ? 1 : 0)]); + keypairgen=null; + } + catch(Exception e){ + if(e instanceof Throwable) + throw new JSchException(e.toString(), (Throwable)e); + throw new JSchException(e.toString()); + } + } + + private static final byte[] begin = + Util.str2byte("-----BEGIN EC PRIVATE KEY-----"); + private static final byte[] end = + Util.str2byte("-----END EC PRIVATE KEY-----"); + + byte[] getBegin(){ return begin; } + byte[] getEnd(){ return end; } + + byte[] getPrivateKey(){ + + byte[] tmp = new byte[1]; tmp[0]=1; + + byte[] oid = oids[ + (r_array.length>=64) ? 2 : + ((r_array.length>=48) ? 1 : 0) + ]; + + byte[] point = toPoint(r_array, s_array); + + int bar = ((point.length+1)&0x80)==0 ? 3 : 4; + byte[] foo = new byte[point.length+bar]; + System.arraycopy(point, 0, foo, bar, point.length); + foo[0]=0x03; // BITSTRING + if(bar==3){ + foo[1]=(byte)(point.length+1); + } + else { + foo[1]=(byte)0x81; + foo[2]=(byte)(point.length+1); + } + point = foo; + + int content= + 1+countLength(tmp.length) + tmp.length + + 1+countLength(prv_array.length) + prv_array.length + + 1+countLength(oid.length) + oid.length + + 1+countLength(point.length) + point.length; + + int total= + 1+countLength(content)+content; // SEQUENCE + + byte[] plain=new byte[total]; + int index=0; + index=writeSEQUENCE(plain, index, content); + index=writeINTEGER(plain, index, tmp); + index=writeOCTETSTRING(plain, index, prv_array); + index=writeDATA(plain, (byte)0xa0, index, oid); + index=writeDATA(plain, (byte)0xa1, index, point); + + return plain; + } + + boolean parse(byte[] plain){ + try{ + + if(vendor==VENDOR_FSECURE){ + /* + if(plain[0]!=0x30){ // FSecure + return true; + } + return false; + */ + return false; + } + else if(vendor==VENDOR_PUTTY){ + /* + Buffer buf=new Buffer(plain); + buf.skip(plain.length); + + try { + byte[][] tmp = buf.getBytes(1, ""); + prv_array = tmp[0]; + } + catch(JSchException e){ + return false; + } + + return true; + */ + return false; + } + + int index=0; + int length=0; + + if(plain[index]!=0x30)return false; + index++; // SEQUENCE + length=plain[index++]&0xff; + if((length&0x80)!=0){ + int foo=length&0x7f; length=0; + while(foo-->0){ length=(length<<8)+(plain[index++]&0xff); } + } + + if(plain[index]!=0x02)return false; + index++; // INTEGER + + length=plain[index++]&0xff; + if((length&0x80)!=0){ + int foo=length&0x7f; length=0; + while(foo-->0){ length=(length<<8)+(plain[index++]&0xff); } + } + + index+=length; + index++; // 0x04 + + length=plain[index++]&0xff; + if((length&0x80)!=0){ + int foo=length&0x7f; length=0; + while(foo-->0){ length=(length<<8)+(plain[index++]&0xff); } + } + + prv_array=new byte[length]; + System.arraycopy(plain, index, prv_array, 0, length); + + index+=length; + + index++; // 0xa0 + + length=plain[index++]&0xff; + if((length&0x80)!=0){ + int foo=length&0x7f; length=0; + while(foo-->0){ length=(length<<8)+(plain[index++]&0xff); } + } + + byte[] oid_array=new byte[length]; + System.arraycopy(plain, index, oid_array, 0, length); + index+=length; + + for(int i = 0; i0){ length=(length<<8)+(plain[index++]&0xff); } + } + + byte[] Q_array=new byte[length]; + System.arraycopy(plain, index, Q_array, 0, length); + index+=length; + + byte[][] tmp = fromPoint(Q_array); + r_array = tmp[0]; + s_array = tmp[1]; + + if(prv_array!=null) + key_size = prv_array.length>=64 ? 521 : + (prv_array.length>=48 ? 384 : 256); + } + catch(Exception e){ + //System.err.println(e); + //e.printStackTrace(); + return false; + } + return true; + } + + public byte[] getPublicKeyBlob(){ + byte[] foo = super.getPublicKeyBlob(); + + if(foo!=null) return foo; + + if(r_array==null) return null; + + byte[][] tmp = new byte[3][]; + tmp[0] = Util.str2byte("ecdsa-sha2-"+new String(name)); + tmp[1] = name; + tmp[2] = new byte[1+r_array.length+s_array.length]; + tmp[2][0] = 4; // POINT_CONVERSION_UNCOMPRESSED + System.arraycopy(r_array, 0, tmp[2], 1, r_array.length); + System.arraycopy(s_array, 0, tmp[2], 1+r_array.length, s_array.length); + + return Buffer.fromBytes(tmp).buffer; + } + + byte[] getKeyTypeName(){ + return Util.str2byte("ecdsa-sha2-"+new String(name)); + } + public int getKeyType(){ + return ECDSA; + } + public int getKeySize(){ + return key_size; + } + + public byte[] getSignature(byte[] data){ + try{ + Class c=Class.forName((String)jsch.getConfig("signature.ecdsa")); + SignatureECDSA ecdsa=(SignatureECDSA)(c.newInstance()); + ecdsa.init(); + ecdsa.setPrvKey(prv_array); + + ecdsa.update(data); + byte[] sig = ecdsa.sign(); + + byte[][] tmp = new byte[2][]; + tmp[0] = Util.str2byte("ecdsa-sha2-"+new String(name)); + tmp[1] = sig; + return Buffer.fromBytes(tmp).buffer; + } + catch(Exception e){ + //System.err.println("e "+e); + } + return null; + } + + public Signature getVerifier(){ + try{ + Class c=Class.forName((String)jsch.getConfig("signature.ecdsa")); + final SignatureECDSA ecdsa=(SignatureECDSA)(c.newInstance()); + ecdsa.init(); + + if(r_array == null && s_array == null && getPublicKeyBlob()!=null){ + Buffer buf = new Buffer(getPublicKeyBlob()); + buf.getString(); // ecdsa-sha2-nistp256 + buf.getString(); // nistp256 + byte[][] tmp = fromPoint(buf.getString()); + r_array = tmp[0]; + s_array = tmp[1]; + } + ecdsa.setPubKey(r_array, s_array); + return ecdsa; + } + catch(Exception e){ + //System.err.println("e "+e); + } + return null; + } + + static KeyPair fromSSHAgent(JSch jsch, Buffer buf) throws JSchException { + + byte[][] tmp = buf.getBytes(5, "invalid key format"); + + byte[] name = tmp[1]; // nistp256 + byte[][] foo = fromPoint(tmp[2]); + byte[] r_array = foo[0]; + byte[] s_array = foo[1]; + + byte[] prv_array = tmp[3]; + KeyPairECDSA kpair = new KeyPairECDSA(jsch, + name, + r_array, s_array, + prv_array); + kpair.publicKeyComment = new String(tmp[4]); + kpair.vendor=VENDOR_OPENSSH; + return kpair; + } + + public byte[] forSSHAgent() throws JSchException { + if(isEncrypted()){ + throw new JSchException("key is encrypted."); + } + Buffer buf = new Buffer(); + buf.putString(Util.str2byte("ecdsa-sha2-"+new String(name))); + buf.putString(name); + buf.putString(toPoint(r_array, s_array)); + buf.putString(prv_array); + buf.putString(Util.str2byte(publicKeyComment)); + byte[] result = new byte[buf.getLength()]; + buf.getByte(result, 0, result.length); + return result; + } + + static byte[] toPoint(byte[] r_array, byte[] s_array) { + byte[] tmp = new byte[1+r_array.length+s_array.length]; + tmp[0]=0x04; + System.arraycopy(r_array, 0, tmp, 1, r_array.length); + System.arraycopy(s_array, 0, tmp, 1+r_array.length, s_array.length); + return tmp; + } + + static byte[][] fromPoint(byte[] point) { + int i = 0; + while(point[i]!=4) i++; + i++; + byte[][] tmp = new byte[2][]; + byte[] r_array = new byte[(point.length-i)/2]; + byte[] s_array = new byte[(point.length-i)/2]; + // point[0] == 0x04 == POINT_CONVERSION_UNCOMPRESSED + System.arraycopy(point, i, r_array, 0, r_array.length); + System.arraycopy(point, i+r_array.length, s_array, 0, s_array.length); + tmp[0] = r_array; + tmp[1] = s_array; + + return tmp; + } + + public void dispose(){ + super.dispose(); + Util.bzero(prv_array); + } +} diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenDSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenDSA.java index f6507f24..2bd78b21 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenDSA.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenDSA.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenECDSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenECDSA.java new file mode 100644 index 00000000..30f00c74 --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenECDSA.java @@ -0,0 +1,37 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2015-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +public interface KeyPairGenECDSA{ + void init(int key_size) throws Exception; + byte[] getD(); + byte[] getR(); + byte[] getS(); +} diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenRSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenRSA.java index 3a849074..0d00eef9 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenRSA.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairGenRSA.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairPKCS8.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairPKCS8.java new file mode 100644 index 00000000..d9a3da19 --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairPKCS8.java @@ -0,0 +1,363 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2013-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +import java.util.Vector; +import java.math.BigInteger; + +public class KeyPairPKCS8 extends KeyPair { + private static final byte[] rsaEncryption = { + (byte)0x2a, (byte)0x86, (byte)0x48, (byte)0x86, + (byte)0xf7, (byte)0x0d, (byte)0x01, (byte)0x01, (byte)0x01 + }; + + private static final byte[] dsaEncryption = { + (byte)0x2a, (byte)0x86, (byte)0x48, (byte)0xce, + (byte)0x38, (byte)0x04, (byte)0x1 + }; + + private static final byte[] pbes2 = { + (byte)0x2a, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xf7, + (byte)0x0d, (byte)0x01, (byte)0x05, (byte)0x0d + }; + + private static final byte[] pbkdf2 = { + (byte)0x2a, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xf7, + (byte)0x0d, (byte)0x01, (byte)0x05, (byte)0x0c + }; + + private static final byte[] aes128cbc = { + (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, + (byte)0x03, (byte)0x04, (byte)0x01, (byte)0x02 + }; + + private static final byte[] aes192cbc = { + (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, + (byte)0x03, (byte)0x04, (byte)0x01, (byte)0x16 + }; + + private static final byte[] aes256cbc = { + (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, + (byte)0x03, (byte)0x04, (byte)0x01, (byte)0x2a + }; + + private static final byte[] pbeWithMD5AndDESCBC = { + (byte)0x2a, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xf7, + (byte)0x0d, (byte)0x01, (byte)0x05, (byte)0x03 + }; + + private KeyPair kpair = null; + + public KeyPairPKCS8(JSch jsch){ + super(jsch); + } + + void generate(int key_size) throws JSchException{ + } + + private static final byte[] begin=Util.str2byte("-----BEGIN DSA PRIVATE KEY-----"); + private static final byte[] end=Util.str2byte("-----END DSA PRIVATE KEY-----"); + + byte[] getBegin(){ return begin; } + byte[] getEnd(){ return end; } + + byte[] getPrivateKey(){ + return null; + } + + boolean parse(byte[] plain){ + + /* from RFC5208 + PrivateKeyInfo ::= SEQUENCE { + version Version, + privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + privateKey PrivateKey, + attributes [0] IMPLICIT Attributes OPTIONAL + } + Version ::= INTEGER + PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier + PrivateKey ::= OCTET STRING + Attributes ::= SET OF Attribute + } + */ + + try{ + Vector values = new Vector(); + + ASN1[] contents = null; + ASN1 asn1 = new ASN1(plain); + contents = asn1.getContents(); + + ASN1 privateKeyAlgorithm = contents[1]; + ASN1 privateKey = contents[2]; + + contents = privateKeyAlgorithm.getContents(); + byte[] privateKeyAlgorithmID = contents[0].getContent(); + contents = contents[1].getContents(); + if(contents.length>0){ + for(int i = 0; i < contents.length; i++){ + values.addElement(contents[i].getContent()); + } + } + + byte[] _data = privateKey.getContent(); + + KeyPair _kpair = null; + if(Util.array_equals(privateKeyAlgorithmID, rsaEncryption)){ + _kpair = new KeyPairRSA(jsch); + _kpair.copy(this); + if(_kpair.parse(_data)){ + kpair = _kpair; + } + } + else if(Util.array_equals(privateKeyAlgorithmID, dsaEncryption)){ + asn1 = new ASN1(_data); + if(values.size() == 0) { // embedded DSA parameters format + /* + SEQUENCE + SEQUENCE + INTEGER // P_array + INTEGER // Q_array + INTEGER // G_array + INTEGER // prv_array + */ + contents = asn1.getContents(); + byte[] bar = contents[1].getContent(); + contents = contents[0].getContents(); + for(int i = 0; i < contents.length; i++){ + values.addElement(contents[i].getContent()); + } + values.addElement(bar); + } + else { + /* + INTEGER // prv_array + */ + values.addElement(asn1.getContent()); + } + + byte[] P_array = (byte[])values.elementAt(0); + byte[] Q_array = (byte[])values.elementAt(1); + byte[] G_array = (byte[])values.elementAt(2); + byte[] prv_array = (byte[])values.elementAt(3); + // Y = g^X mode p + byte[] pub_array = + (new BigInteger(G_array)). + modPow(new BigInteger(prv_array), new BigInteger(P_array)). + toByteArray(); + + KeyPairDSA _key = new KeyPairDSA(jsch, + P_array, Q_array, G_array, + pub_array, prv_array); + plain = _key.getPrivateKey(); + + _kpair = new KeyPairDSA(jsch); + _kpair.copy(this); + if(_kpair.parse(plain)){ + kpair = _kpair; + } + } + } + catch(ASN1Exception e){ + return false; + } + catch(Exception e){ + //System.err.println(e); + return false; + } + return kpair != null; + } + + public byte[] getPublicKeyBlob(){ + return kpair.getPublicKeyBlob(); + } + + byte[] getKeyTypeName(){ return kpair.getKeyTypeName();} + public int getKeyType(){return kpair.getKeyType();} + + public int getKeySize(){ + return kpair.getKeySize(); + } + + public byte[] getSignature(byte[] data){ + return kpair.getSignature(data); + } + + public Signature getVerifier(){ + return kpair.getVerifier(); + } + + public byte[] forSSHAgent() throws JSchException { + return kpair.forSSHAgent(); + } + + public boolean decrypt(byte[] _passphrase){ + if(!isEncrypted()){ + return true; + } + if(_passphrase==null){ + return !isEncrypted(); + } + + /* + SEQUENCE + SEQUENCE + OBJECT :PBES2 + SEQUENCE + SEQUENCE + OBJECT :PBKDF2 + SEQUENCE + OCTET STRING [HEX DUMP]:E4E24ADC9C00BD4D + INTEGER :0800 + SEQUENCE + OBJECT :aes-128-cbc + OCTET STRING [HEX DUMP]:5B66E6B3BF03944C92317BC370CC3AD0 + OCTET STRING [HEX DUMP]: + +or + + SEQUENCE + SEQUENCE + OBJECT :pbeWithMD5AndDES-CBC + SEQUENCE + OCTET STRING [HEX DUMP]:DBF75ECB69E3C0FC + INTEGER :0800 + OCTET STRING [HEX DUMP] + */ + + try{ + + ASN1[] contents = null; + ASN1 asn1 = new ASN1(data); + + contents = asn1.getContents(); + + byte[] _data = contents[1].getContent(); + + ASN1 pbes = contents[0]; + contents = pbes.getContents(); + byte[] pbesid = contents[0].getContent(); + ASN1 pbesparam = contents[1]; + + byte[] salt = null; + int iterations = 0; + byte[] iv = null; + byte[] encryptfuncid = null; + + if(Util.array_equals(pbesid, pbes2)){ + contents = pbesparam.getContents(); + ASN1 pbkdf = contents[0]; + ASN1 encryptfunc = contents[1]; + contents = pbkdf.getContents(); + byte[] pbkdfid = contents[0].getContent(); + ASN1 pbkdffunc = contents[1]; + contents = pbkdffunc.getContents(); + salt = contents[0].getContent(); + iterations = + Integer.parseInt((new BigInteger(contents[1].getContent())).toString()); + + contents = encryptfunc.getContents(); + encryptfuncid = contents[0].getContent(); + iv = contents[1].getContent(); + } + else if(Util.array_equals(pbesid, pbeWithMD5AndDESCBC)){ + // not supported + return false; + } + else { + return false; + } + + Cipher cipher=getCipher(encryptfuncid); + if(cipher==null) return false; + + byte[] key=null; + try{ + Class c=Class.forName((String)jsch.getConfig("pbkdf")); + PBKDF tmp=(PBKDF)(c.newInstance()); + key = tmp.getKey(_passphrase, salt, iterations, cipher.getBlockSize()); + } + catch(Exception ee){ + } + + if(key==null){ + return false; + } + + cipher.init(Cipher.DECRYPT_MODE, key, iv); + Util.bzero(key); + byte[] plain=new byte[_data.length]; + cipher.update(_data, 0, _data.length, plain, 0); + if(parse(plain)){ + encrypted=false; + return true; + } + } + catch(ASN1Exception e){ + // System.err.println(e); + } + catch(Exception e){ + // System.err.println(e); + } + + return false; + } + + Cipher getCipher(byte[] id){ + Cipher cipher=null; + String name = null; + try{ + if(Util.array_equals(id, aes128cbc)){ + name="aes128-cbc"; + } + else if(Util.array_equals(id, aes192cbc)){ + name="aes192-cbc"; + } + else if(Util.array_equals(id, aes256cbc)){ + name="aes256-cbc"; + } + Class c=Class.forName((String)jsch.getConfig(name)); + cipher=(Cipher)(c.newInstance()); + } + catch(Exception e){ + if(JSch.getLogger().isEnabled(Logger.FATAL)){ + String message=""; + if(name==null){ + message="unknown oid: "+Util.toHex(id); + } + else { + message="function "+name+" is not supported"; + } + JSch.getLogger().log(Logger.FATAL, "PKCS8: "+message); + } + } + return cipher; + } +} diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairRSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairRSA.java index 45fd70a5..2111cd3b 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairRSA.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KeyPairRSA.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KnownHosts.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KnownHosts.java index 6fd577ea..f9d1eae4 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KnownHosts.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/KnownHosts.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -35,12 +35,6 @@ public class KnownHosts implements HostKeyRepository{ private static final String _known_hosts="known_hosts"; - /* - static final int SSHDSS=0; - static final int SSHRSA=1; - static final int UNKNOWN=2; - */ - private JSch jsch=null; private String known_hosts=null; private java.util.Vector pool=null; @@ -50,26 +44,28 @@ class KnownHosts implements HostKeyRepository{ KnownHosts(JSch jsch){ super(); this.jsch=jsch; + this.hmacsha1 = getHMACSHA1(); pool=new java.util.Vector(); } - void setKnownHosts(String foo) throws JSchException{ + void setKnownHosts(String filename) throws JSchException{ try{ - known_hosts = foo; - FileInputStream fis=new FileInputStream(Util.checkTilde(foo)); + known_hosts = filename; + FileInputStream fis=new FileInputStream(Util.checkTilde(filename)); setKnownHosts(fis); } catch(FileNotFoundException e){ + // The non-existing file should be allowed. } } - void setKnownHosts(InputStream foo) throws JSchException{ + void setKnownHosts(InputStream input) throws JSchException{ pool.removeAllElements(); StringBuffer sb=new StringBuffer(); byte i; int j; boolean error=false; try{ - InputStream fis=foo; + InputStream fis=input; String host; String key=null; int type; @@ -158,8 +154,10 @@ loop: if(i==0x20 || i=='\t'){ break; } sb.append((char)i); } - if(sb.toString().equals("ssh-dss")){ type=HostKey.SSHDSS; } - else if(sb.toString().equals("ssh-rsa")){ type=HostKey.SSHRSA; } + String tmp = sb.toString(); + if(HostKey.name2type(tmp)!=HostKey.UNKNOWN){ + type=HostKey.name2type(tmp); + } else { j=bufl; } if(j>=bufl){ addInvalidLine(Util.byte2str(buf, 0, bufl)); @@ -223,7 +221,6 @@ loop: key.length()), comment); pool.addElement(hk); } - fis.close(); if(error){ throw new JSchException("KnownHosts: invalid format"); } @@ -235,6 +232,12 @@ loop: throw new JSchException(e.toString(), (Throwable)e); throw new JSchException(e.toString()); } + finally { + try{ input.close(); } + catch(IOException e){ + throw new JSchException(e.toString(), (Throwable)e); + } + } } private void addInvalidLine(String line) throws JSchException { HostKey hk = new HostKey(line, HostKey.UNKNOWN, null); @@ -249,14 +252,19 @@ loop: return result; } - int type=getType(key); - HostKey hk; + HostKey hk = null; + try { + hk = new HostKey(host, HostKey.GUESS, key); + } + catch(JSchException e){ // unsupported key + return result; + } synchronized(pool){ for(int i=0; i1){ + HostKey[] tmp = + getHostKey(host.substring(1, host.indexOf("]:")), type); + if(tmp.length > 0){ + HostKey[] bar = new HostKey[foo.length + tmp.length]; + System.arraycopy(foo, 0, bar, 0, foo.length); + System.arraycopy(tmp, 0, bar, foo.length, tmp.length); + foo = bar; + } } return foo; } @@ -452,11 +462,7 @@ loop: System.err.println(e); } } - private int getType(byte[] key){ - if(key[8]=='d') return HostKey.SSHDSS; - if(key[8]=='r') return HostKey.SSHRSA; - return HostKey.UNKNOWN; - } + private String deleteSubString(String hosts, String host){ int i=0; int hostlen=host.length(); @@ -477,7 +483,7 @@ loop: return hosts; } - private synchronized MAC getHMACSHA1(){ + private MAC getHMACSHA1(){ if(hmacsha1==null){ try{ Class c=Class.forName(jsch.getConfig("hmac-sha1")); @@ -503,7 +509,6 @@ loop: byte[] salt=null; byte[] hash=null; - HashedHostKey(String host, byte[] key) throws JSchException { this(host, GUESS, key); } diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/LocalIdentityRepository.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/LocalIdentityRepository.java index 3b83f2ef..01a37a4d 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/LocalIdentityRepository.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/LocalIdentityRepository.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2012-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -50,6 +50,7 @@ class LocalIdentityRepository implements IdentityRepository { } public synchronized Vector getIdentities() { + removeDupulicates(); Vector v = new Vector(); for(int i=0; i*/ kv = new Vector(); @@ -200,12 +198,13 @@ public class OpenSSHConfig implements ConfigRepository { if(keymap.get(key)!=null) { key = (String)keymap.get(key); } + key = key.toUpperCase(); String value = null; for(int i = 0; i < _configs.size(); i++) { Vector v = (Vector)_configs.elementAt(i); for(int j = 0; j < v.size(); j++) { String[] kv = (String[])v.elementAt(j); - if(kv[0].equals(key)) { + if(kv[0].toUpperCase().equals(key)) { value = kv[1]; break; } @@ -217,12 +216,13 @@ public class OpenSSHConfig implements ConfigRepository { } private String[] multiFind(String key) { + key = key.toUpperCase(); Vector value = new Vector(); for(int i = 0; i < _configs.size(); i++) { Vector v = (Vector)_configs.elementAt(i); for(int j = 0; j < v.size(); j++) { String[] kv = (String[])v.elementAt(j); - if(kv[0].equals(key)) { + if(kv[0].toUpperCase().equals(key)) { String foo = kv[1]; if(foo != null) { value.remove(foo); diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PBKDF.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PBKDF.java new file mode 100644 index 00000000..6910b568 --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PBKDF.java @@ -0,0 +1,34 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2013-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +public interface PBKDF { + byte[] getKey(byte[] pass, byte[] salt, int iteration, int size); +} diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Packet.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Packet.java index 9c441577..3b8c50de 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Packet.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Packet.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PortWatcher.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PortWatcher.java index 75ec2688..5f403fde 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PortWatcher.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/PortWatcher.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Proxy.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Proxy.java index 7d05caa8..39b4bcb7 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Proxy.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Proxy.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxyHTTP.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxyHTTP.java index df23114a..432e91ee 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxyHTTP.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxyHTTP.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS4.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS4.java index cb506165..f0b059fa 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS4.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS4.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS5.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS5.java index 7960135a..e37581b2 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS5.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ProxySOCKS5.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Random.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Random.java index 879b7770..9d08767f 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Random.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Random.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Request.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Request.java index 94f9b013..a158b769 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Request.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Request.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestAgentForwarding.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestAgentForwarding.java index 66d328cd..0c6edc7f 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestAgentForwarding.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestAgentForwarding.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestEnv.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestEnv.java index cccda4df..6925a04f 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestEnv.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestEnv.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestExec.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestExec.java index 318d99ad..3787edbe 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestExec.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestExec.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestPtyReq.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestPtyReq.java index 86bf4f13..b9a2622e 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestPtyReq.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestPtyReq.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSftp.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSftp.java index 483c296b..10eab938 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSftp.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSftp.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestShell.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestShell.java index e037ba9e..125b7fac 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestShell.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestShell.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSignal.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSignal.java index a6926177..84efa8a0 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSignal.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSignal.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSubsystem.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSubsystem.java index b6fee4f4..9926bbf8 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSubsystem.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestSubsystem.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2005-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2005-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestWindowChange.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestWindowChange.java index 43600ac4..131c2bfd 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestWindowChange.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestWindowChange.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestX11.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestX11.java index 3bdaca9f..03ea7ede 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestX11.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/RequestX11.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ServerSocketFactory.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ServerSocketFactory.java index 682b4c4f..4aaef2d8 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ServerSocketFactory.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/ServerSocketFactory.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Session.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Session.java index 8807f5f6..a5f751dc 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Session.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Session.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -123,7 +123,7 @@ public class Session implements Runnable{ SocketFactory socket_factory=null; static final int buffer_margin = 32 + // maximum padding length - 20 + // maximum mac length + 64 + // maximum mac length 32; // margin for deflater; deflater may inflate data private java.util.Hashtable config=null; @@ -339,9 +339,16 @@ public class Session implements Runnable{ } } - try{ checkHost(host, port, kex); } + try{ + long tmp=System.currentTimeMillis(); + in_prompt = true; + checkHost(host, port, kex); + in_prompt = false; + kex_start_time+=(System.currentTimeMillis()-tmp); + } catch(JSchException ee){ in_kex=false; + in_prompt = false; throw ee; } @@ -537,19 +544,20 @@ public class Session implements Runnable{ } catch(Exception e) { in_kex=false; - if(isConnected){ - try{ - packet.reset(); - buf.putByte((byte)SSH_MSG_DISCONNECT); - buf.putInt(3); - buf.putString(Util.str2byte(e.toString())); - buf.putString(Util.str2byte("en")); - write(packet); - disconnect(); - } - catch(Exception ee){ - } + try{ + if(isConnected){ + String message = e.toString(); + packet.reset(); + buf.checkFreeSize(1+4*3+message.length()+2+buffer_margin); + buf.putByte((byte)SSH_MSG_DISCONNECT); + buf.putInt(3); + buf.putString(Util.str2byte(message)); + buf.putString(Util.str2byte("en")); + write(packet); + } } + catch(Exception ee){} + try{ disconnect(); } catch(Exception ee){ } isConnected=false; //e.printStackTrace(); if(e instanceof RuntimeException) throw (RuntimeException)e; @@ -601,7 +609,8 @@ public class Session implements Runnable{ return kex; } - private boolean in_kex=false; + private volatile boolean in_kex=false; + private volatile boolean in_prompt=false; public void rekey() throws Exception { send_kexinit(); } @@ -630,6 +639,16 @@ public class Session implements Runnable{ } } + String server_host_key = getConfig("server_host_key"); + String[] not_available_shks = + checkSignatures(getConfig("CheckSignatures")); + if(not_available_shks!=null && not_available_shks.length>0){ + server_host_key=Util.diffString(server_host_key, not_available_shks); + if(server_host_key==null){ + throw new JSchException("There are not any available sig algorithm."); + } + } + in_kex=true; kex_start_time=System.currentTimeMillis(); @@ -653,7 +672,7 @@ public class Session implements Runnable{ random.fill(buf.buffer, buf.index, 16); buf.skip(16); } buf.putString(Util.str2byte(kex)); - buf.putString(Util.str2byte(getConfig("server_host_key"))); + buf.putString(Util.str2byte(server_host_key)); buf.putString(Util.str2byte(cipherc2s)); buf.putString(Util.str2byte(ciphers2c)); buf.putString(Util.str2byte(getConfig("mac.c2s"))); @@ -738,7 +757,7 @@ public class Session implements Runnable{ "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n"+ "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n"+ "It is also possible that the "+key_type+" host key has just been changed.\n"+ -"The fingerprint for the "+key_type+" key sent by the remote host is\n"+ +"The fingerprint for the "+key_type+" key sent by the remote host "+chost+" is\n"+ key_fprint+".\n"+ "Please contact your system administrator.\n"+ "Add correct host key in "+file+" to get rid of this message."; @@ -758,7 +777,7 @@ key_fprint+".\n"+ synchronized(hkr){ hkr.remove(chost, - (key_type.equals("DSA") ? "ssh-dss" : "ssh-rsa"), + kex.getKeyAlgorithName(), null); insert=true; } @@ -796,8 +815,7 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ if(i==HostKeyRepository.OK){ HostKey[] keys = - hkr.getHostKey(chost, - (key_type.equals("DSA") ? "ssh-dss" : "ssh-rsa")); + hkr.getHostKey(chost, kex.getKeyAlgorithName()); String _key= Util.byte2str(Util.toBase64(K_S, 0, K_S.length)); for(int j=0; j< keys.length; j++){ if(keys[i].getKey().equals(_key) && @@ -820,7 +838,7 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ if(i==HostKeyRepository.OK && JSch.getLogger().isEnabled(Logger.INFO)){ JSch.getLogger().log(Logger.INFO, - "Host '"+host+"' is known and mathces the "+key_type+" host key"); + "Host '"+host+"' is known and matches the "+key_type+" host key"); } if(insert && @@ -1019,7 +1037,7 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ if(c==null){ } else{ - c.addRemoteWindowSize(buf.getInt()); + c.addRemoteWindowSize(buf.getUInt()); } } else if(type==UserAuth.SSH_MSG_USERAUTH_SUCCESS){ @@ -1237,7 +1255,7 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ while(true){ if(in_kex){ if(t>0L && (System.currentTimeMillis()-kex_start_time)>t){ - throw new JSchException("timeout in wating for rekeying process."); + throw new JSchException("timeout in waiting for rekeying process."); } try{Thread.sleep(10);} catch(java.lang.InterruptedException e){}; @@ -1257,6 +1275,10 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ } } + if(in_kex){ + continue; + } + if(c.rwsize>=length){ c.rwsize-=length; break; @@ -1325,8 +1347,11 @@ key_type+" key fingerprint is "+key_fprint+".\n"+ // System.err.println("in_kex="+in_kex+" "+(packet.buffer.getCommand())); long t = getTimeout(); while(in_kex){ - if(t>0L && (System.currentTimeMillis()-kex_start_time)>t){ - throw new JSchException("timeout in wating for rekeying process."); + if(t>0L && + (System.currentTimeMillis()-kex_start_time)>t && + !in_prompt + ){ + throw new JSchException("timeout in waiting for rekeying process."); } byte command=packet.buffer.getCommand(); //System.err.println("command: "+command); @@ -1494,7 +1519,7 @@ break; if(channel==null){ break; } - channel.addRemoteWindowSize(buf.getInt()); + channel.addRemoteWindowSize(buf.getUInt()); break; case SSH_MSG_CHANNEL_EOF: @@ -1534,33 +1559,30 @@ break; buf.getShort(); i=buf.getInt(); channel=Channel.getChannel(i, this); - if(channel==null){ - //break; - } int r=buf.getInt(); long rws=buf.getUInt(); int rps=buf.getInt(); - - channel.setRemoteWindowSize(rws); - channel.setRemotePacketSize(rps); - channel.open_confirmation=true; - channel.setRecipient(r); + if(channel!=null){ + channel.setRemoteWindowSize(rws); + channel.setRemotePacketSize(rps); + channel.open_confirmation=true; + channel.setRecipient(r); + } break; case SSH_MSG_CHANNEL_OPEN_FAILURE: buf.getInt(); buf.getShort(); i=buf.getInt(); channel=Channel.getChannel(i, this); - if(channel==null){ - //break; - } - int reason_code=buf.getInt(); - //foo=buf.getString(); // additional textual information - //foo=buf.getString(); // language tag - channel.setExitStatus(reason_code); - channel.close=true; - channel.eof_remote=true; - channel.setRecipient(0); + if(channel!=null){ + int reason_code=buf.getInt(); + //foo=buf.getString(); // additional textual information + //foo=buf.getString(); // language tag + channel.setExitStatus(reason_code); + channel.close=true; + channel.eof_remote=true; + channel.setRecipient(0); + } break; case SSH_MSG_CHANNEL_REQUEST: buf.getInt(); @@ -1616,8 +1638,8 @@ break; tmp.setDaemon(daemon_thread); } tmp.start(); - break; } + break; case SSH_MSG_CHANNEL_SUCCESS: buf.getInt(); buf.getShort(); @@ -2440,11 +2462,17 @@ break; "CheckCiphers: "+ciphers); } + String cipherc2s=getConfig("cipher.c2s"); + String ciphers2c=getConfig("cipher.s2c"); + Vector result=new Vector(); String[] _ciphers=Util.split(ciphers, ","); for(int i=0; i<_ciphers.length; i++){ - if(!checkCipher(getConfig(_ciphers[i]))){ - result.addElement(_ciphers[i]); + String cipher=_ciphers[i]; + if(ciphers2c.indexOf(cipher) == -1 && cipherc2s.indexOf(cipher) == -1) + continue; + if(!checkCipher(getConfig(cipher))){ + result.addElement(cipher); } } if(result.size()==0) @@ -2517,6 +2545,40 @@ break; catch(Exception e){ return false; } } + private String[] checkSignatures(String sigs){ + if(sigs==null || sigs.length()==0) + return null; + + if(JSch.getLogger().isEnabled(Logger.INFO)){ + JSch.getLogger().log(Logger.INFO, + "CheckSignatures: "+sigs); + } + + java.util.Vector result=new java.util.Vector(); + String[] _sigs=Util.split(sigs, ","); + for(int i=0; i<_sigs.length; i++){ + try{ + Class c=Class.forName((String)jsch.getConfig(_sigs[i])); + final Signature sig=(Signature)(c.newInstance()); + sig.init(); + } + catch(Exception e){ + result.addElement(_sigs[i]); + } + } + if(result.size()==0) + return null; + String[] foo=new String[result.size()]; + System.arraycopy(result.toArray(), 0, foo, 0, result.size()); + if(JSch.getLogger().isEnabled(Logger.INFO)){ + for(int i=0; inull. @@ -2542,8 +2604,7 @@ break; } /** - * Sets the hostkeyRepository, which will be referred - * in the host key checking. + * Sets the hostkeyRepository, which will be referred in checking host keys. * * @param hostkeyRepository * @see #getHostKeyRepository() diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpATTRS.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpATTRS.java index b3a86ba0..96165d50 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpATTRS.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpATTRS.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -108,12 +108,12 @@ public class SftpATTRS { } public String getAtimeString(){ - SimpleDateFormat locale=new SimpleDateFormat(); - return (locale.format(new Date(atime))); + Date date= new Date(((long)atime)*1000L); + return (date.toString()); } public String getMtimeString(){ - Date date= new Date(((long)mtime)*1000); + Date date= new Date(((long)mtime)*1000L); return (date.toString()); } diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpException.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpException.java index 6a6c1ff8..6f644750 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpException.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpException.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpProgressMonitor.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpProgressMonitor.java index bd91688d..0fd124ad 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpProgressMonitor.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpProgressMonitor.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpStatVFS.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpStatVFS.java index 4433bf6a..04dcd402 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpStatVFS.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SftpStatVFS.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -35,20 +35,8 @@ import java.util.Date; public class SftpStatVFS { /* - from "man statvfs" - struct statvfs { - unsigned long f_bsize; // file system block size - unsigned long f_frsize; // fragment size - fsblkcnt_t f_blocks; // size of fs in f_frsize units - fsblkcnt_t f_bfree; // # free blocks - fsblkcnt_t f_bavail; // # free blocks for non-root - fsfilcnt_t f_files; // # inodes - fsfilcnt_t f_ffree; // # free inodes - fsfilcnt_t f_favail; // # free inodes for non-root - unsigned long f_fsid; // file system ID - unsigned long f_flag; // mount flags - unsigned long f_namemax; // maximum filename length - }; + It seems data is serializsed according to sys/statvfs.h; for example, + http://pubs.opengroup.org/onlinepubs/009604499/basedefs/sys/statvfs.h.html */ private long bsize; diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Signature.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Signature.java index 9b7d85d8..711f01d7 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Signature.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Signature.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2012-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2012-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureDSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureDSA.java index 5b253fdc..2cbe0acf 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureDSA.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureDSA.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureECDSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureECDSA.java new file mode 100644 index 00000000..7e14d5d5 --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureECDSA.java @@ -0,0 +1,35 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2015-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +public interface SignatureECDSA extends Signature { + void setPubKey(byte[] r, byte[] s) throws Exception; + void setPrvKey(byte[] s) throws Exception; +} diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureRSA.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureRSA.java index 715bc575..e51d8a6d 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureRSA.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SignatureRSA.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SocketFactory.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SocketFactory.java index aaac0dce..d0519851 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SocketFactory.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/SocketFactory.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UIKeyboardInteractive.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UIKeyboardInteractive.java index 23af9c31..8ada1a9a 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UIKeyboardInteractive.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UIKeyboardInteractive.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuth.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuth.java index 085a9508..26836317 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuth.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuth.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthGSSAPIWithMIC.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthGSSAPIWithMIC.java index 15856cbc..4325955b 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthGSSAPIWithMIC.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthGSSAPIWithMIC.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthKeyboardInteractive.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthKeyboardInteractive.java index 1274d78c..8331b17e 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthKeyboardInteractive.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthKeyboardInteractive.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthNone.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthNone.java index b3b0b077..a2bf63af 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthNone.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthNone.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPassword.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPassword.java index 9b5837f5..7bc1a283 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPassword.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPassword.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPublicKey.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPublicKey.java index a4d38be5..40c1fa0a 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPublicKey.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserAuthPublicKey.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -66,7 +66,8 @@ class UserAuthPublicKey extends UserAuth{ // string service name ("ssh-connection") // string "publickey" // boolen FALSE - // string plaintext password (ISO-10646 UTF-8) + // string public key algorithm name + // string public key blob packet.reset(); buf.putByte((byte)SSH_MSG_USERAUTH_REQUEST); buf.putString(_username); @@ -154,13 +155,15 @@ class UserAuthPublicKey extends UserAuth{ if(pubkeyblob==null) continue; - // send - // byte SSH_MSG_USERAUTH_REQUEST(50) - // string user name - // string service name ("ssh-connection") - // string "publickey" - // boolen TRUE - // string plaintext password (ISO-10646 UTF-8) + // send + // byte SSH_MSG_USERAUTH_REQUEST(50) + // string user name + // string service name ("ssh-connection") + // string "publickey" + // boolen TRUE + // string public key algorithm name + // string public key blob + // string signature packet.reset(); buf.putByte((byte)SSH_MSG_USERAUTH_REQUEST); buf.putString(_username); diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserInfo.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserInfo.java index 22552ede..e1b2a32a 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserInfo.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/UserInfo.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Util.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Util.java index 6a5354b0..f990b5de 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Util.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/Util.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -43,20 +43,25 @@ class Util{ } return 0; } - static byte[] fromBase64(byte[] buf, int start, int length){ - byte[] foo=new byte[length]; - int j=0; - for (int i=start;i>>4)); - if(buf[i+2]==(byte)'='){ j++; break;} - foo[j+1]=(byte)(((val(buf[i+1])&0x0f)<<4)|((val(buf[i+2])&0x3c)>>>2)); - if(buf[i+3]==(byte)'='){ j+=2; break;} - foo[j+2]=(byte)(((val(buf[i+2])&0x03)<<6)|(val(buf[i+3])&0x3f)); - j+=3; + static byte[] fromBase64(byte[] buf, int start, int length) throws JSchException { + try { + byte[] foo=new byte[length]; + int j=0; + for (int i=start;i>>4)); + if(buf[i+2]==(byte)'='){ j++; break;} + foo[j+1]=(byte)(((val(buf[i+1])&0x0f)<<4)|((val(buf[i+2])&0x3c)>>>2)); + if(buf[i+3]==(byte)'='){ j+=2; break;} + foo[j+2]=(byte)(((val(buf[i+2])&0x03)<<6)|(val(buf[i+3])&0x3f)); + j+=3; + } + byte[] bar=new byte[j]; + System.arraycopy(foo, 0, bar, 0, j); + return bar; + } + catch(ArrayIndexOutOfBoundsException e) { + throw new JSchException("fromBase64: invalid base64 data", e); } - byte[] bar=new byte[j]; - System.arraycopy(foo, 0, bar, 0, j); - return bar; } static byte[] toBase64(byte[] buf, int start, int length){ @@ -386,7 +391,7 @@ class Util{ } tmp.interrupt(); tmp=null; - throw new JSchException(message); + throw new JSchException(message, ee[0]); } return socket; } @@ -423,6 +428,17 @@ class Util{ return byte2str(str, s, l, "UTF-8"); } + static String toHex(byte[] str){ + StringBuffer sb = new StringBuffer(); + for(int i = 0; i=64) name="secp521r1"; + else if(r.length>=48) name="secp384r1"; + + AlgorithmParameters param = AlgorithmParameters.getInstance("EC"); + param.init(new ECGenParameterSpec(name)); + ECParameterSpec ecparam = + (ECParameterSpec)param.getParameterSpec(ECParameterSpec.class); + ECPoint w = new ECPoint(new BigInteger(1, r), new BigInteger(1, s)); + PublicKey pubKey = + keyFactory.generatePublic(new ECPublicKeySpec(w, ecparam)); + signature.initVerify(pubKey); + } + + public void setPrvKey(byte[] d) throws Exception{ + + // d must be unsigned value. + d=insert0(d); + + String name="secp256r1"; + if(d.length>=64) name="secp521r1"; + else if(d.length>=48) name="secp384r1"; + + AlgorithmParameters param = AlgorithmParameters.getInstance("EC"); + param.init(new ECGenParameterSpec(name)); + ECParameterSpec ecparam = + (ECParameterSpec)param.getParameterSpec(ECParameterSpec.class); + BigInteger _d = new BigInteger(1, d); + PrivateKey prvKey = + keyFactory.generatePrivate(new ECPrivateKeySpec(_d, ecparam)); + signature.initSign(prvKey); + } + public byte[] sign() throws Exception{ + byte[] sig=signature.sign(); + + // It seems that the output from SunEC is in ASN.1, + // so we have to convert it. + if(sig[0]==0x30 && // in ASN.1 + ((sig[1]+2 == sig.length) || + ((sig[1]&0x80)!=0 && (sig[2]&0xff)+3==sig.length))){// 2bytes for len + + int index=3; + if((sig[1]&0x80)!=0 && (sig[2]&0xff)+3==sig.length) + index=4; + + byte[] r = new byte[sig[index]]; + byte[] s = new byte[sig[index+2+sig[index]]]; + System.arraycopy(sig, index+1, r, 0, r.length); + System.arraycopy(sig, index+3+sig[index], s, 0, s.length); + + r = chop0(r); + s = chop0(s); + + Buffer buf = new Buffer(); + buf.putMPInt(r); + buf.putMPInt(s); + + sig=new byte[buf.getLength()]; + buf.setOffSet(0); + buf.getByte(sig); + } + + return sig; + } + public void update(byte[] foo) throws Exception{ + signature.update(foo); + } + public boolean verify(byte[] sig) throws Exception{ + + // It seems that SunEC expects ASN.1 data, + // so we have to convert it. + if(!(sig[0]==0x30 && // not in ASN.1 + ((sig[1]+2 == sig.length) || + ((sig[1]&0x80)!=0 && (sig[2]&0xff)+3==sig.length)))) { + Buffer b = new Buffer(sig); + + b.getString(); // ecdsa-sha2-nistp256 + b.getInt(); + + byte[] r = b.getMPInt(); + byte[] s = b.getMPInt(); + + r=insert0(r); + s=insert0(s); + + byte[] asn1 = null; + if(r.length<64){ + asn1 = new byte[6+r.length+s.length]; + asn1[0] = (byte)0x30; + asn1[1] = (byte)(4+r.length+s.length); + asn1[2] = (byte)0x02; + asn1[3] = (byte)r.length; + System.arraycopy(r, 0, asn1, 4, r.length); + asn1[r.length+4] = (byte)0x02; + asn1[r.length+5] = (byte)s.length; + System.arraycopy(s, 0, asn1, (6+r.length), s.length); + } + else { + asn1 = new byte[6+r.length+s.length+1]; + asn1[0] = (byte)0x30; + asn1[1] = (byte)0x81; + asn1[2] = (byte)(4+r.length+s.length); + asn1[3] = (byte)0x02; + asn1[4] = (byte)r.length; + System.arraycopy(r, 0, asn1, 5, r.length); + asn1[r.length+5] = (byte)0x02; + asn1[r.length+6] = (byte)s.length; + System.arraycopy(s, 0, asn1, (7+r.length), s.length); + } + sig=asn1; + } + + return signature.verify(sig); + } + + private byte[] insert0(byte[] buf){ + if ((buf[0] & 0x80) == 0) return buf; + byte[] tmp = new byte[buf.length+1]; + System.arraycopy(buf, 0, tmp, 1, buf.length); + bzero(buf); + return tmp; + } + private byte[] chop0(byte[] buf){ + if(buf[0]!=0) return buf; + byte[] tmp = new byte[buf.length-1]; + System.arraycopy(buf, 1, tmp, 0, tmp.length); + bzero(buf); + return tmp; + } + + private void bzero(byte[] buf){ + for(int i = 0; ibsize){ byte[] tmp=new byte[bsize]; System.arraycopy(key, 0, tmp, 0, bsize); diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD5.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD5.java index 90960113..c4fcb48f 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD5.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD5.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD596.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD596.java index 95c6f60d..f806962e 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD596.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACMD596.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA1.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA1.java index ea9eccf1..869c13c4 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA1.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA1.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA196.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA196.java index 86a81b5b..8f8e3276 100644 --- a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA196.java +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jcraft/HMACSHA196.java @@ -1,6 +1,6 @@ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -Copyright (c) 2006-2012 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jgss/GSSContextKrb5.java b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jgss/GSSContextKrb5.java new file mode 100644 index 00000000..cf3ca48f --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/com/jcraft/jsch/jgss/GSSContextKrb5.java @@ -0,0 +1,177 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch.jgss; + +import com.jcraft.jsch.JSchException; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.MessageProp; +import org.ietf.jgss.Oid; + +public class GSSContextKrb5 implements com.jcraft.jsch.GSSContext{ + + private static final String pUseSubjectCredsOnly = + "javax.security.auth.useSubjectCredsOnly"; + private static String useSubjectCredsOnly = + getSystemProperty(pUseSubjectCredsOnly); + + private GSSContext context=null; + public void create(String user, String host) throws JSchException{ + try{ + // RFC 1964 + Oid krb5=new Oid("1.2.840.113554.1.2.2"); + // Kerberos Principal Name Form + Oid principalName=new Oid("1.2.840.113554.1.2.2.1"); + + GSSManager mgr=GSSManager.getInstance(); + + GSSCredential crd=null; + /* + try{ + GSSName _user=mgr.createName(user, principalName); + crd=mgr.createCredential(_user, + GSSCredential.DEFAULT_LIFETIME, + krb5, + GSSCredential.INITIATE_ONLY); + } + catch(GSSException crdex){ + } + */ + + String cname=host; + try{ + cname=InetAddress.getByName(cname).getCanonicalHostName(); + } + catch(UnknownHostException e){ + } + GSSName _host=mgr.createName("host/"+cname, principalName); + + context=mgr.createContext(_host, + krb5, + crd, + GSSContext.DEFAULT_LIFETIME); + + // RFC4462 3.4. GSS-API Session + // + // When calling GSS_Init_sec_context(), the client MUST set + // integ_req_flag to "true" to request that per-message integrity + // protection be supported for this context. In addition, + // deleg_req_flag MAY be set to "true" to request access delegation, if + // requested by the user. + // + // Since the user authentication process by its nature authenticates + // only the client, the setting of mutual_req_flag is not needed for + // this process. This flag SHOULD be set to "false". + + // TODO: OpenSSH's sshd does accepts 'false' for mutual_req_flag + //context.requestMutualAuth(false); + context.requestMutualAuth(true); + context.requestConf(true); + context.requestInteg(true); // for MIC + context.requestCredDeleg(true); + context.requestAnonymity(false); + + return; + } + catch(GSSException ex){ + throw new JSchException(ex.toString()); + } + } + + public boolean isEstablished(){ + return context.isEstablished(); + } + + public byte[] init(byte[] token, int s, int l) throws JSchException { + try{ + // Without setting "javax.security.auth.useSubjectCredsOnly" to "false", + // Sun's JVM for Un*x will show messages to stderr in + // processing context.initSecContext(). + // This hack is not thread safe ;-<. + // If that property is explicitly given as "true" or "false", + // this hack must not be invoked. + if(useSubjectCredsOnly==null){ + setSystemProperty(pUseSubjectCredsOnly, "false"); + } + return context.initSecContext(token, 0, l); + } + catch(GSSException ex){ + throw new JSchException(ex.toString()); + } + catch(java.lang.SecurityException ex){ + throw new JSchException(ex.toString()); + } + finally{ + if(useSubjectCredsOnly==null){ + // By the default, it must be "true". + setSystemProperty(pUseSubjectCredsOnly, "true"); + } + } + } + + public byte[] getMIC(byte[] message, int s, int l){ + try{ + MessageProp prop = new MessageProp(0, true); + return context.getMIC(message, s, l, prop); + } + catch(GSSException ex){ + return null; + } + } + + public void dispose(){ + try{ + context.dispose(); + } + catch(GSSException ex){ + } + } + + private static String getSystemProperty(String key){ + try{ return System.getProperty(key); } + catch(Exception e){ + // We are not allowed to get the System properties. + return null; + } + } + + private static void setSystemProperty(String key, String value){ + try{ System.setProperty(key, value); } + catch(Exception e){ + // We are not allowed to set the System properties. + } + } +} diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/webdav/ConnectionInfo.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/ConnectionInfo.java similarity index 76% rename from src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/webdav/ConnectionInfo.java rename to src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/ConnectionInfo.java index b682a104..a00ea62e 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/webdav/ConnectionInfo.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/ConnectionInfo.java @@ -1,4 +1,4 @@ -package keepass2android.javafilestorage.webdav; +package keepass2android.javafilestorage; /** * Created by Philipp on 22.11.2016. diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/GoogleDriveFileStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/GoogleDriveFileStorage.java index bb95c329..8395d7f6 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/GoogleDriveFileStorage.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/GoogleDriveFileStorage.java @@ -788,7 +788,7 @@ public class GoogleDriveFileStorage extends JavaFileStorageBase { else { logDebug("denied"); - finishWithError(setupAct, new Exception("You must grant the requested permissions to continue.")); + finishWithError(setupAct, new Exception("Please grant the requested permissions. Access to your accounts is required to let you choose from the available Google accounts on this device.")); } } @@ -847,13 +847,21 @@ public class GoogleDriveFileStorage extends JavaFileStorageBase { if (Build.VERSION.SDK_INT >= 23) { Activity act = (Activity)activity; - int permissionRes = act.checkSelfPermission(Manifest.permission.GET_ACCOUNTS); - logDebug("permissionRes="+permissionRes); - if (permissionRes == PackageManager.PERMISSION_DENIED) + + String[] permissions = new String[] {Manifest.permission.GET_ACCOUNTS, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}; + boolean allOk = true; + for (String s: permissions) + { + int permissionRes = act.checkSelfPermission(Manifest.permission.GET_ACCOUNTS); + logDebug("permissionRes="+permissionRes); + allOk = false; + } + + if (!allOk) { logDebug("requestPermissions"); mRequiresRuntimePermissions = true; - act.requestPermissions(new String[] {Manifest.permission.GET_ACCOUNTS}, 0); + act.requestPermissions(permissions, 0); } } diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java index 142db95e..30285b4e 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java @@ -26,14 +26,14 @@ public class SftpStorage extends JavaFileStorageBase { public static final int DEFAULT_SFTP_PORT = 22; JSch jsch; - - class ConnectionInfo + + public class ConnectionInfo { - String host; - String username; - String password; - String localPath; - int port; + public String host; + public String username; + public String password; + public String localPath; + public int port; } public SftpStorage() { @@ -333,7 +333,7 @@ public class SftpStorage extends JavaFileStorageBase { } - private ConnectionInfo splitStringToConnectionInfo(String filename) + public ConnectionInfo splitStringToConnectionInfo(String filename) throws UnsupportedEncodingException { ConnectionInfo ci = new ConnectionInfo(); ci.host = extractUserPwdHost(filename); @@ -348,6 +348,7 @@ public class SftpStorage extends JavaFileStorageBase { ci.port = Integer.parseInt(ci.host.substring(portSeparatorIndex+1)); ci.host = ci.host.substring(0, portSeparatorIndex); } + ci.localPath = extractSessionPath(filename); return ci; } @@ -384,7 +385,7 @@ public class SftpStorage extends JavaFileStorageBase { try { ConnectionInfo ci = splitStringToConnectionInfo(path); - return getProtocolPrefix()+ci.username+"@"+ci.host+extractSessionPath(path); + return getProtocolPrefix()+ci.username+"@"+ci.host+ci.localPath; } catch (Exception e) { @@ -414,7 +415,7 @@ public class SftpStorage extends JavaFileStorageBase { } - + public String buildFullPath( String host, int port, String localPath, String username, String password) throws UnsupportedEncodingException { if (port != DEFAULT_SFTP_PORT) @@ -423,6 +424,7 @@ public class SftpStorage extends JavaFileStorageBase { } + @Override public void prepareFileUsage(Context appContext, String path) { //nothing to do diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/WebDavStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/WebDavStorage.java index 026ab698..a9b45528 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/WebDavStorage.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/WebDavStorage.java @@ -36,7 +36,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import keepass2android.javafilestorage.webdav.ConnectionInfo; +import keepass2android.javafilestorage.ConnectionInfo; import keepass2android.javafilestorage.webdav.DecoratedHostnameVerifier; import keepass2android.javafilestorage.webdav.DecoratedTrustManager; import keepass2android.javafilestorage.webdav.PropfindXmlParser; @@ -64,7 +64,7 @@ public class WebDavStorage extends JavaFileStorageBase { return scheme + "://" + encode(username)+":"+encode(password)+"@"+url; } - private ConnectionInfo splitStringToConnectionInfo(String filename) + public ConnectionInfo splitStringToConnectionInfo(String filename) throws UnsupportedEncodingException { ConnectionInfo ci = new ConnectionInfo(); diff --git a/src/java/JavaFileStorageTest-AS/app/build.gradle b/src/java/JavaFileStorageTest-AS/app/build.gradle index 2d8e6d03..27e3252e 100644 --- a/src/java/JavaFileStorageTest-AS/app/build.gradle +++ b/src/java/JavaFileStorageTest-AS/app/build.gradle @@ -11,9 +11,6 @@ android { versionCode 1 versionName "1.0" - jackOptions { - enabled true - } } buildTypes { release { diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/java/keepass2android/softkeyboard/KP2AKeyboard.java b/src/java/KP2ASoftkeyboard_AS/app/src/main/java/keepass2android/softkeyboard/KP2AKeyboard.java index f83c425f..97cf2c3f 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/java/keepass2android/softkeyboard/KP2AKeyboard.java +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/java/keepass2android/softkeyboard/KP2AKeyboard.java @@ -1325,7 +1325,9 @@ public class KP2AKeyboard extends InputMethodService String action = getPackageName()+".lock_database"; android.util.Log.i("KP2A", "sending broadcast with action "+action); - sendBroadcast(new Intent(action)); + Intent intent = new Intent(action); + intent.setPackage(getPackageName()); + sendBroadcast(intent); } diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-cs/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-cs/strings_kp2a.xml index 95b5ec40..6eff88e1 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-cs/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-cs/strings_kp2a.xml @@ -10,7 +10,7 @@ Automatické vyplňování povoleno PÅ™i vstupu do prázdného pole automaticky doplnit text, pokud je pro klávesnici dostupná položka v Keepass2Android a obsahuje hodnotu odpovídající nápovÄ›dÄ› pro toto pole. Pamatovat si nápovÄ›dy pole - Je-li pole ruÄnÄ› vyplnÄ›no výbÄ›rem položky z Keepass2Android, pamatovat si, která položka byla do pole zadána. Pole je pozdÄ›ji znovu rozpoznáno podle jeho nápovÄ›dy. + Je-li pole ruÄnÄ› vyplnÄ›no výbÄ›rem položky z Keepass2Android, pamatovat si, které položka byla do pole zadána. Pole je pozdÄ›ji znovu rozpoznáno podle jeho nápovÄ›dy. Jednoduchá klávesnice Zobrazit jednoduchou jednořádkovou klávesnici, je-li položka k dispozici. Je-li klávesnice zakázána, dialogové okno se zobrazí pÅ™i stisknutí klávesy Keepass2Android. Uzamknout databázi po dokonÄení diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fa-rIR/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fa-rIR/strings_kp2a.xml index a934ff65..bc336dc6 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fa-rIR/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fa-rIR/strings_kp2a.xml @@ -1,8 +1,20 @@ - ورودی را انتخاب کنید + انتخاب مدخل دیگر + مدخل را انتخاب کنید + جست‌وجوی مدخل با «%1$s» کاربر - کلمه عبور - پر کردن خودکار ÙØ¹Ø§Ù„ شد + گذرواژه + تنظیمات ورود اطلاعات اعتبارنامه + پرکردن خودکار ÙØ¹Ø§Ù„ شد + با ÙØ¹Ø§Ù„‌شدن یک Ùیلد خالی، به‌صورت خودکار پر شود. این در صورتی است Ú©Ù‡ یک مدخل در دسترس ØµÙØ­Ù‡â€ŒÚ©Ù„ید باشد Ùˆ مقداری هم مطابق نشانهٔ Ùیلد وجود داشته باشد. + متن‌های نشانهٔ Ùیلد را به‌خاطر بسپار + اگر مقدار یک Ùیلد متنی به‌صورت دستی انتخاب شد، مقدار واردشده در Ùیلد متنی را به‌خاطر بسپار. در آینده آن Ùیلد متنی به‌صورت خودکار با توجه به نشانه‌اش تشخیص داده می‌شود. + ØµÙØ­Ù‡â€ŒÚ©Ù„ید ساده + اگر مدخلی در دسترس ØµÙØ­Ù‡â€ŒÚ©Ù„ید بود ØµÙØ­Ù‡â€ŒÚ©Ù„ید سادهٔ تک‌ردیÙÙ‡ را نشان بده. اگر ØºÛŒØ±ÙØ¹Ø§Ù„ باشد، با زدن کلید Keepass2Android کادری نشان داده می‌شود. + Ù‚Ùل‌کردن پایگاه داده در پایان + پس از اینکه دکمهٔ تمام/ارسال/برو بر روی ØµÙØ­Ù‡â€ŒÚ©Ù„ید سادهٔ تک‌ردیÙÙ‡ انتخاب شد به‌صورت خودکار پایگاه داده Ù‚ÙÙ„ شود. + تعویض ØµÙØ­Ù‡â€ŒÚ©Ù„ید در پایان + پس از انتخاب دکمهٔ تمام/ارسال/برو بر روی ØµÙØ­Ù‡â€ŒÚ©Ù„ید سادهٔ تک‌ردیÙه، ØµÙØ­Ù‡â€ŒÚ©Ù„ید عوض شود. diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fi/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fi/strings_kp2a.xml index 2d3b75fa..9efbc661 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fi/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-fi/strings_kp2a.xml @@ -8,13 +8,13 @@ Salasana Tietojen syötön asetukset Automaattinen täyttö - Täytä kenttä automaattiseti kun siirrytään tyhjään kenttään, jos löytyy sekä Keepass2Android näppäimistölle merkintä että kentän vihjetekstiä vastaava arvo. + Kun siirrytään tyhjään kenttään, täytä se automaattiseti kenttään mikäli Keepass2Android näppäimistölle löytyy merkintä, jonka vihjeteksti vastaa kenttää. Muista kenttien vihjetekstit - Jos tekstikenttä täytetään manuaalisesti valitsemalla Keepass2Androidin tarjoama arvo, muista mikä arvo kenttään syötettiin. Kenttä tunnistetaan uudelleen myöhemmin vihjetekstin perusteella. + Jos tekstikenttä täytetään manuaalisesti valitsemalla Keepass2Androidin tarjoama arvo, muista mikä arvo kenttään syötettiin. Kenttä voidaan sitten tunnistetaa myöhemmin uudelleen tämän vihjetekstin perusteella. Yksirivinen näppäimistö Näytä yksirivinen näppäimistö jos näppäimistölle on saatavilla merkintä. Jos asetus ei ole käytössä, näytetään valintaruutu Keepass2Android näppäimistön kuvaketta painettaessa. Lukitse tietokanta kun valmista Lukitse tietokanta automaattisesti kun painetaan yksinkertaisen 1-rivisen näppäimistön Valmis/Lähetä/Mene näppäintä. - Näppäimistön vaihto + Näppäimistön vaihtaminen kun valmista Vaihda näppäimistö kun painetaan yksinkertaisen yksirivisen näppäimistön Valmis/Lähetä/Siirry näppäintä. diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-iw/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-iw/strings_kp2a.xml index dc007631..68ae56be 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-iw/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-iw/strings_kp2a.xml @@ -10,7 +10,11 @@ השלמה-×וטומ×טית מ×ופשרת מילוי ×וטומטי בטקסט בעת הזנת שדה ריק, ×× ×¢×¨×š Keepass2Android ×ינו זמין עבור לוח המקשי×, יש ערך ×שר תו×× ×ת טקסט הרמז של השדה. זכור ×ת שדה טקסט ×”×¨×ž×–×™× + ×× ×©×“×” טקסט ×ž×ž×•×œ× ×¢×œ-ידי בחירה ידנית בערך מתוך Keepass2Adnroid, זכור ×ת הערך שהוכנס לשדה הטקסט. שדה טקסט ×–×” ×™×ותר ×וטומטית בהמשך על-פי שמו. מקלדת פשוטה + הצג מקלדת בעלת שורה יחידה ×× ×§×™×™×ž×ª רשומה זמינה עבורה. ×× ×פשרות זו כבויה, יוצג די×לוג בעת לחיצה על מקש Keepass2Android. נעל מסד ×”× ×ª×•× ×™× ×‘×¡×™×•× + בלחיצה על מקש הסיו×/שליחה/×ישור במקלדת בתצורת שורה יחידה, נעל ×וטומטית ×ת מסד הנתוני×. החלף מקלדת ×‘×¡×™×•× + בלחיצה על מקש הסיו×/שליחה/×ישור במקלדת בתצורת שורה יחידה, החלף בחזרה למקלדת ברירת המחדל. diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-nl/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-nl/strings_kp2a.xml index ee0b2120..5890fb2a 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-nl/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-nl/strings_kp2a.xml @@ -2,17 +2,17 @@ Kies een andere regel - Kies regel - Zoek voor regel met \"%1$s\" + Kies item + Zoek voor item met \"%1$s\" Gebruiker Wachtwoord Instellingen voor invoer van logingegevens Automatisch-vullen ingeschakeld - Vult automatisch tekst in een leeg tekstveld in, als een Keepass2Android regel beschikbaar is voor het toetsenbord en als het veld overeenkomt met de opgeslagen veld hint-tekst. + Vult automatisch tekst in een leeg tekstveld, als een Keepass2Android item beschikbaar is voor het toetsenbord en het veld overeenkomt met de opgeslagen veld hint-tekst. Onthoud veld hint-teksten Als een tekstveld gevuld is door handmatig een Keepass2Android waarde te kiezen, onthoud welke KP2A waarde was gebruikt voor het tekstveld. Het tekstveld wordt later herkend d.m.v. de hint-tekst. Eenvoudig toetsenbord - Toon het eenvoudige toetsenbord als een KP2A regel beschikbaar is voor het toetsenbord. Wanneer uitgeschakeld, een venster word getoond als de Keepass2Android toets is ingedrukt. + Toon het eenvoudige 1-rij toetsenbord als een item beschikbaar is voor het toetsenbord. Wanneer uitgeschakeld wordt een venster getoond als de Keepass2Android toets is ingedrukt. Vergrendel de database na voltooiing Als de Gedaan/Verzenden/Gaan toets op het eenvoudige toetsenbord is ingedrukt, vergrendel dan automatisch de database. Wissel toetsenbord na voltooiing diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-zh-rCN/strings_kp2a.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-zh-rCN/strings_kp2a.xml index a08771ef..95075817 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-zh-rCN/strings_kp2a.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/values-zh-rCN/strings_kp2a.xml @@ -12,7 +12,7 @@ 记忆字段æç¤ºæ–‡æœ¬ 如果通过手动选择 Keepass2Android 填充字段,请记ä½å“ªä¸ªå­—æ®µåœ¨å¯¹åº”å­—æ®µä¸­è¾“å…¥ã€‚è¯¥å­—æ®µæ˜¯åŽæ¥ç”±å…¶æç¤ºæ–‡æœ¬é‡æ–°æ£€æµ‹åˆ°ã€‚ 简易键盘 - 如果一个æ¡ç›®å¯ä»¥ä½¿ç”¨é”®ç›˜æ¥è¾“入,软件将显示一个å•行的简易键盘。如果ä¸å¯ä»¥ï¼Œå½“按 Keepass2Android é”®æ—¶åˆ™ä¼šæ˜¾ç¤ºä¸€ä¸ªå¯¹è¯æ¡†ã€‚ + 当存在待输入æ¡ç›®æ—¶æ˜¾ç¤ºä¸€ä¸ªå•行的简易键盘。若ç¦ç”¨ï¼Œåˆ™åœ¨æŒ‰ Keepass2Android é”®æ—¶æ˜¾ç¤ºä¸€ä¸ªå¯¹è¯æ¡†ã€‚ å®Œæˆæ—¶é”定数æ®åº“ 当点击å•è¡Œé”®ç›˜ä¸Šçš„å®Œæˆæˆ–å‘é€é”®æ—¶ï¼Œè‡ªåЍé”定数æ®åº“。 完æˆåŽåˆ‡æ¢é”®ç›˜ diff --git a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/xml/method.xml b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/xml/method.xml index 3cf8d2dd..a6b718bc 100644 --- a/src/java/KP2ASoftkeyboard_AS/app/src/main/res/xml/method.xml +++ b/src/java/KP2ASoftkeyboard_AS/app/src/main/res/xml/method.xml @@ -22,5 +22,10 @@ + android:isDefault="@bool/im_is_default"> + + + diff --git a/src/java/Keepass2AndroidPluginSDK2/app/src/main/java/keepass2android/pluginsdk/PluginActionBroadcastReceiver.java b/src/java/Keepass2AndroidPluginSDK2/app/src/main/java/keepass2android/pluginsdk/PluginActionBroadcastReceiver.java index 3d924ad8..484083d7 100644 --- a/src/java/Keepass2AndroidPluginSDK2/app/src/main/java/keepass2android/pluginsdk/PluginActionBroadcastReceiver.java +++ b/src/java/Keepass2AndroidPluginSDK2/app/src/main/java/keepass2android/pluginsdk/PluginActionBroadcastReceiver.java @@ -54,7 +54,6 @@ public abstract class PluginActionBroadcastReceiver extends BroadcastReceiver { for(Iterator iter = json.keys();iter.hasNext();) { String key = iter.next(); String value = json.get(key).toString(); - Log.d("KP2APluginSDK", "received " + key+"/"+value); res.put(key, value); } diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-cs/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-cs/strings.xml index 21b44cb0..a3662cb3 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-cs/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-cs/strings.xml @@ -24,7 +24,7 @@ název souboru hledat Tato aplikace nemá oprávnÄ›ní pro vytváření souborů/složek - Tato aplikace nemá oprávnÄ›ní pro odstraňování souborů/složek + Tato aplikace nemá oprávnÄ›ní odstraňovat soubory/složky ZruÅ¡eno Nelze se pÅ™ipojit ke službÄ› poskytovatele souborů Novou složku zde nelze vytvoÅ™it @@ -58,17 +58,20 @@ VÄera Vybrat složku … - Vyberte složku … - Vyberte složky … + Vyberte složku… + Vyberte složky… + Vyberte složky… Vyberte soubor… Vyberte soubor… + Vyberte soubory… Vyberte soubory… - Vyberte soubor/ složku … - Vyberte soubor/ složku … - Vyberte soubory/ složky … + Vyberte soubor/složku… + Vyberte soubor/složku… + Vyberte soubory/složky… + Vyberte soubory/složky… diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-fa-rIR/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-fa-rIR/strings.xml index 102937fd..3d812f5a 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-fa-rIR/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-fa-rIR/strings.xml @@ -8,62 +8,65 @@ --> همه - انتخاب معکوس + برعکس‌کردن انتخاب هیچکدام نمای شبکه‌ای خانه - نمای لیست - پوشه جدید... - انتخاب همه ÙØ§ÛŒÙ„‌ها - انتخاب همه پوشه‌ها - مرتب کردن… + نمای لیستی + پوشهٔ جدید… + انتخاب همهٔ ÙØ§ÛŒÙ„‌ها + انتخاب همهٔ پوشه‌ها + مرتب‌کردن… ÙØ§ÛŒÙ„ پوشه - پاک کردن + پاک‌کردن نام پوشه - نام پرونده - جستجو - این برنامه اجازه ساختن ÙØ§ÛŒÙ„‌ها یا پوشه‌ها را ندارد - این برنامه اجازه حذÙ‌کردن ÙØ§ÛŒÙ„‌ها یا پوشه‌ها را ندارد + نام ÙØ§ÛŒÙ„ + جست‌وجو + این برنامه اجازهٔ ساختن ÙØ§ÛŒÙ„‌ها/پوشه‌ها را ندارد + این برنامه اجازهٔ حذÙ‌کردن ÙØ§ÛŒÙ„‌ها/پوشه‌ها را ندارد لغو شد - اینجا نمی‌توان پوشه جدید ساخت - نمی‌توان ÙØ§ÛŒÙ„ÛŒ را اینجا ذخیره کرد + اتصال به سرویس‌دهندهٔ ÙØ§ÛŒÙ„ ناموÙÙ‚ بود + در اینجا نمی‌توان پوشهٔ جدید ساخت + در اینجا نمی‌توان ÙØ§ÛŒÙ„ÛŒ را ذخیره کرد ïºï»§ïº ïºŽï»¡ شد خالی - ناموÙÙ‚. Ù„Ø·ÙØ§ دوباره سعی کنید. + ناموÙÙ‚. Ù„Ø·ÙØ§Ù‹ دوباره سعی کنید. در حال بارگیری… تلÙÙ† - \"%1$s\" قابل دسترسی نیست - ایجاد پوشه \"%1$s\" ممکن نبود + پوشهٔ «%1$s» قابل‌دسترسی نیست + ایجاد پوشهٔ «%1$s» ناموÙÙ‚ بود نمی‌توان %1$s را حذ٠کرد «%2$s» - ÙØ§ÛŒÙ„ «%1$s» در حال حاضر موجود است.\n\n آیا میخواهید آن را جایگزین کنید؟ + از حذ٠این مورد مطمئن هستید؟ +%1$s «%2$s» + ÙØ§ÛŒÙ„ «%1$s» وجود دارد.\n\n آن را جایگزین می‌کنید؟ در حال حذÙ‌کردن %1$s «%2$s»… - %1$s «%2$s» حذ٠شد - \"%1$s\" یک پوشه است - نام ÙØ§ÛŒÙ„ \"%1$s\" معتبر نیست + ÙØ§ÛŒÙ„ %1$s «%2$s» حذ٠شد + â€Â«%1$s» یک پوشه است + نام ÙØ§ÛŒÙ„ «%1$s» معتبر نیست …ÙØ§ÛŒÙ„‌های بیشتری دارد، حداکثر تعداد مجاز: %1$,d خطای ناشناخته: %1$s ریشه انتخاب... - تایید + تأیید تاریخ خطا اطلاعات نام - ذخیره به عنوان... + ذخیره به‌عنوان… اندازه‌ - مرتب کردن بر اساس… + مرتب‌کردن بر اساس… دیروز - پوشه را انتخاب کنید... - پوشه را انتخاب کنید... + انتخاب پوشه… + انتخاب پوشه‌ها… انتخاب ÙØ§ÛŒÙ„... - انتخاب ÙØ§ÛŒÙ„... + انتخاب ÙØ§ÛŒÙ„‌ها… انتخاب ÙØ§ÛŒÙ„ / پوشه... - انتخاب ÙØ§ÛŒÙ„ / پوشه... + انتخاب ÙØ§ÛŒÙ„ / پوشه‌ها… diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-fi/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-fi/strings.xml index 9f15c1ea..85f94d18 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-fi/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-fi/strings.xml @@ -14,9 +14,9 @@ Etusivu Luettelonäkymä Uusi kansio… - Valitse kaikki + Valitse kaikki tiedostot Valitse kaikki kansiot - Lajittele + Lajittele… tiedosto kansio tyhjennä @@ -26,7 +26,7 @@ Sovelluksella ei ole oikeutta luoda tiedostoja/kansioita Sovelluksella ei ole oikeutta poistaa tiedostoja/kansioita Peruutettu - Ei voida yhdistää tiedoston palveluntarjoajaan + Ei voida yhdistää tiedoston tarjoavaan palveluun Uutta kansiota ei voi luoda tänne Tiedostoa ei voi tallentaa tänne Valmis diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-pl/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-pl/strings.xml index a88ebe71..4b154bb8 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-pl/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-pl/strings.xml @@ -56,19 +56,4 @@ Rozmiar Sortuj wedÅ‚ug… Wczoraj - - Wybierz folder… - Wybierz foldery… - Wybierz… - - - Wybierz plik… - Wybierz pliki… - Wybierz… - - - Wybierz plik/folder… - Wybierz pliki/foldery… - Wybierz… - diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-ru/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-ru/strings.xml index 80121921..45cda5d2 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-ru/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-ru/strings.xml @@ -56,19 +56,4 @@ Размер Сортировать по… Вчера - - Выберите папку… - Выберите папку… - Выберите папки… - - - Выберите файл… - Выберите файл… - Выберите файлы… - - - Выберите файл/папку… - Выберите файл/папку… - Выберите файлы/папки… - diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-sk/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-sk/strings.xml index 3e2e22f8..34ea4a70 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-sk/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-sk/strings.xml @@ -56,19 +56,4 @@ VeľkosÅ¥ ZoradiÅ¥ podľa… VÄera - - Vyberte prieÄinok… - Vyberte prieÄinok… - Vyberte prieÄinky… - - - Vyberte súbor… - Vyberte súbor… - Vyberte súbory… - - - Vyberte súbor/prieÄinok… - Vyberte súbor/prieÄinok… - Vyberte súbory/prieÄinky… - diff --git a/src/java/android-filechooser-AS/app/src/main/res/values-uk/strings.xml b/src/java/android-filechooser-AS/app/src/main/res/values-uk/strings.xml index 3934a57d..9a26cad1 100644 --- a/src/java/android-filechooser-AS/app/src/main/res/values-uk/strings.xml +++ b/src/java/android-filechooser-AS/app/src/main/res/values-uk/strings.xml @@ -56,19 +56,4 @@ Розмір Сортувати за… Вчора - - Виберіть теку… - Виберіть теку… - Виберіть теки… - - - Виберіть файл… - Виберіть файл… - Виберіть файли… - - - Виберіть файл/теку… - Виберіть файл/теку… - Виберіть файли/теки… - diff --git a/src/keepass2android/ChallengeXCKey.cs b/src/keepass2android/ChallengeXCKey.cs new file mode 100644 index 00000000..ab3dfb34 --- /dev/null +++ b/src/keepass2android/ChallengeXCKey.cs @@ -0,0 +1,90 @@ +using Java.Lang; +using KeePassLib.Cryptography; +using KeePassLib.Keys; +using KeePassLib.Security; +using Exception = System.Exception; + +namespace keepass2android +{ + public class ChallengeXCKey : IUserKey, ISeedBasedUserKey + { + private readonly int _requestCode; + + public ProtectedBinary KeyData + { + get + { + if (Activity == null) + throw new Exception("Need an active Keepass2Android activity to challenge Yubikey!"); + Activity.RunOnUiThread( + () => + { + byte[] challenge = _kdfSeed; + byte[] challenge64 = new byte[64]; + for (int i = 0; i < 64; i++) + { + if (i < challenge.Length) + { + challenge64[i] = challenge[i]; + } + else + { + challenge64[i] = (byte)(challenge64.Length - challenge.Length); + } + + } + var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(challenge64, true); + + if (chalIntent == null) + throw new Exception("YubiChallenge not installed."); + + Activity.StartActivityForResult(chalIntent, _requestCode); + + + + + + + }); + while ((Response == null) && (Error == null)) + { + Thread.Sleep(50); + } + if (Error != null) + { + var error = Error; + Error = null; + throw new Exception("YubiChallenge failed: " + error); + } + + var result = CryptoUtil.HashSha256(Response); + Response = null; + return new ProtectedBinary(true, result); + } + } + + private byte[] _kdfSeed; + + public ChallengeXCKey(LockingActivity activity, int requestCode) + { + this.Activity = activity; + _requestCode = requestCode; + Response = null; + } + + public void SetParams(byte[] masterSeed, byte[] mPbKdfSeed) + { + _kdfSeed = mPbKdfSeed; + } + + public byte[] Response { get; set; } + + public string Error { get; set; } + + public LockingActivity Activity + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/keepass2android/ChangeLog.cs b/src/keepass2android/ChangeLog.cs index 197cbfa8..e318a82c 100644 --- a/src/keepass2android/ChangeLog.cs +++ b/src/keepass2android/ChangeLog.cs @@ -26,7 +26,9 @@ namespace keepass2android AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeHoloLightDialog)); builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title)); List changeLog = new List{ - ctx.GetString(Resource.String.ChangeLog_1_04b), + + ctx.GetString(Resource.String.ChangeLog_1_05), + ctx.GetString(Resource.String.ChangeLog_1_04b), ctx.GetString(Resource.String.ChangeLog_1_04), ctx.GetString(Resource.String.ChangeLog_1_03), ctx.GetString(Resource.String.ChangeLog_1_02), @@ -176,7 +178,7 @@ namespace keepass2android else { w = w.Replace("\\n", "
"); - if (w.StartsWith("*")) + if ((w.StartsWith("*") || (w.StartsWith("•")))) { if (!inList) { diff --git a/src/keepass2android/EntryActivity.cs b/src/keepass2android/EntryActivity.cs index a3c1a621..fcefa04f 100644 --- a/src/keepass2android/EntryActivity.cs +++ b/src/keepass2android/EntryActivity.cs @@ -91,7 +91,14 @@ namespace keepass2android private int _pos; AppTask _appTask; - private List _protectedTextViews; + + struct ProtectedTextviewGroup + { + public TextView ProtectedField; + public TextView VisibleProtectedField; + } + + private List _protectedTextViews; private IMenu _menu; private readonly Dictionary> _popupMenuItems = @@ -476,17 +483,26 @@ namespace keepass2android RelativeLayout valueViewContainer = (RelativeLayout) LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null); var valueView = valueViewContainer.FindViewById(Resource.Id.entry_extra); - if (value != null) - valueView.Text = value; - SetPasswordTypeface(valueView); - if (isProtected) - { - RegisterProtectedTextView(valueView); - valueView.TransformationMethod = PasswordTransformationMethod.Instance; - } + var valueViewVisible = valueViewContainer.FindViewById(Resource.Id.entry_extra_visible); + if (value != null) + { + valueView.Text = value; + valueViewVisible.Text = value; + + } + SetPasswordTypeface(valueViewVisible); + if (isProtected) + { + RegisterProtectedTextView(valueView, valueViewVisible); + + } + else + { + valueView.Visibility = ViewStates.Gone; + } layout.AddView(valueViewContainer); - var stringView = new ExtraStringView(layout, valueView, keyView); + var stringView = new ExtraStringView(layout, valueView, valueViewVisible, keyView); _stringViews.Add(key, stringView); RegisterTextPopup(valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots), key, isProtected); @@ -599,9 +615,11 @@ namespace keepass2android - private void RegisterProtectedTextView(TextView protectedTextView) + private void RegisterProtectedTextView(TextView protectedTextView, TextView visibleTextView) { - _protectedTextViews.Add(protectedTextView); + var protectedTextviewGroup = new ProtectedTextviewGroup { ProtectedField = protectedTextView, VisibleProtectedField = visibleTextView}; + _protectedTextViews.Add(protectedTextviewGroup); + SetPasswordStyle(protectedTextviewGroup); } @@ -687,7 +705,7 @@ namespace keepass2android protected void FillData() { - _protectedTextViews = new List(); + _protectedTextViews = new List(); ImageView iv = (ImageView) FindViewById(Resource.Id.icon); if (iv != null) { @@ -704,9 +722,9 @@ namespace keepass2android PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField); PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField); - PopulateStandardText(Resource.Id.entry_password, Resource.Id.entryfield_container_password, PwDefs.PasswordField); - RegisterProtectedTextView(FindViewById(Resource.Id.entry_password)); - SetPasswordTypeface(FindViewById(Resource.Id.entry_password)); + PopulateStandardText(new List { Resource.Id.entry_password, Resource.Id.entry_password_visible}, Resource.Id.entryfield_container_password, PwDefs.PasswordField); + + RegisterProtectedTextView(FindViewById(Resource.Id.entry_password), FindViewById(Resource.Id.entry_password_visible)); RegisterTextPopup(FindViewById (Resource.Id.groupname_container), FindViewById (Resource.Id.entry_group_name), KeyGroupFullPath); @@ -820,28 +838,43 @@ namespace keepass2android textView.Typeface = _passwordFont; } - private void PopulateText(int viewId, int containerViewId, String text) + private void PopulateText(int viewId, int containerViewId, String text) + { + PopulateText(new List {viewId}, containerViewId, text); + } + + + private void PopulateText(List viewIds, int containerViewId, String text) { View container = FindViewById(containerViewId); - TextView tv = (TextView) FindViewById(viewId); - if (String.IsNullOrEmpty(text)) - { - container.Visibility = tv.Visibility = ViewStates.Gone; - } - else - { - container.Visibility = tv.Visibility = ViewStates.Visible; - tv.Text = text; + foreach (int viewId in viewIds) + { + TextView tv = (TextView) FindViewById(viewId); + if (String.IsNullOrEmpty(text)) + { + container.Visibility = tv.Visibility = ViewStates.Gone; + } + else + { + container.Visibility = tv.Visibility = ViewStates.Visible; + tv.Text = text; - } + } + } } - private void PopulateStandardText(int viewId, int containerViewId, String key) + private void PopulateStandardText(int viewId, int containerViewId, String key) + { + PopulateStandardText(new List {viewId}, containerViewId, key); + } + + + private void PopulateStandardText(List viewIds, int containerViewId, String key) { String value = Entry.Strings.ReadSafe(key); value = SprEngine.Compile(value, new SprContext(Entry, App.Kp2a.GetDb().KpDatabase, SprCompileFlags.All)); - PopulateText(viewId, containerViewId, value); - _stringViews.Add(key, new StandardStringView(viewId, containerViewId, this)); + PopulateText(viewIds, containerViewId, value); + _stringViews.Add(key, new StandardStringView(viewIds, containerViewId, this)); } private void PopulateGroupText(int viewId, int containerViewId, String key) @@ -853,7 +886,7 @@ namespace keepass2android groupName = Entry.ParentGroup.GetFullPath(); } PopulateText(viewId, containerViewId, groupName); - _stringViews.Add (key, new StandardStringView (viewId, containerViewId, this)); + _stringViews.Add (key, new StandardStringView (new List{viewId}, containerViewId, this)); } private void RequiresRefresh() @@ -932,24 +965,23 @@ namespace keepass2android private void SetPasswordStyle() { - foreach (TextView password in _protectedTextViews) - { + foreach (ProtectedTextviewGroup group in _protectedTextViews) + { + SetPasswordStyle(group); + } + } - if (_showPassword) - { - //password.TransformationMethod = null; - password.InputType = password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword; - SetPasswordTypeface(password); - } - else - { - //password.TransformationMethod = PasswordTransformationMethod.Instance; - password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword; - } - } - } + private void SetPasswordStyle(ProtectedTextviewGroup group) + { + group.VisibleProtectedField.Visibility = _showPassword ? ViewStates.Visible : ViewStates.Gone; + group.ProtectedField.Visibility = !_showPassword ? ViewStates.Visible : ViewStates.Gone; - protected override void OnResume() + SetPasswordTypeface(group.VisibleProtectedField); + + group.ProtectedField.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword; + } + + protected override void OnResume() { ClearCache(); base.OnResume(); diff --git a/src/keepass2android/EntryActivityClasses/ExtraStringView.cs b/src/keepass2android/EntryActivityClasses/ExtraStringView.cs index e65585fe..08f9561b 100644 --- a/src/keepass2android/EntryActivityClasses/ExtraStringView.cs +++ b/src/keepass2android/EntryActivityClasses/ExtraStringView.cs @@ -8,13 +8,15 @@ namespace keepass2android { private readonly View _container; private readonly TextView _valueView; - private readonly TextView _keyView; + private readonly TextView _visibleValueView; + private readonly TextView _keyView; - public ExtraStringView(LinearLayout container, TextView valueView, TextView keyView) + public ExtraStringView(LinearLayout container, TextView valueView, TextView visibleValueView, TextView keyView) { _container = container; _valueView = valueView; - _keyView = keyView; + _visibleValueView = visibleValueView; + _keyView = keyView; } public View View @@ -29,16 +31,15 @@ namespace keepass2android { if (String.IsNullOrEmpty(value)) { - _valueView.Visibility = ViewStates.Gone; - _keyView.Visibility = ViewStates.Gone; _container.Visibility = ViewStates.Gone; } else { - _valueView.Visibility = ViewStates.Visible; - _keyView.Visibility = ViewStates.Visible; _container.Visibility = ViewStates.Visible; _valueView.Text = value; + if (_visibleValueView != null) + _visibleValueView.Text = value; + } } } diff --git a/src/keepass2android/EntryActivityClasses/StandardStringView.cs b/src/keepass2android/EntryActivityClasses/StandardStringView.cs index bcbcb019..c307a3c2 100644 --- a/src/keepass2android/EntryActivityClasses/StandardStringView.cs +++ b/src/keepass2android/EntryActivityClasses/StandardStringView.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Android.App; using Android.Views; using Android.Widget; @@ -7,13 +9,13 @@ namespace keepass2android { internal class StandardStringView : IStringView { - private readonly int _viewId; + private readonly List _viewIds; private readonly int _containerViewId; private readonly Activity _activity; - public StandardStringView(int viewId, int containerViewId, Activity activity) + public StandardStringView(List viewIds, int containerViewId, Activity activity) { - _viewId = viewId; + _viewIds = viewIds; _containerViewId = containerViewId; _activity = activity; } @@ -23,20 +25,23 @@ namespace keepass2android set { View container = _activity.FindViewById(_containerViewId); - TextView tv = (TextView) _activity.FindViewById(_viewId); - if (String.IsNullOrEmpty(value)) - { - container.Visibility = tv.Visibility = ViewStates.Gone; - } - else - { - container.Visibility = tv.Visibility = ViewStates.Visible; - tv.Text = value; - } + foreach (int viewId in _viewIds) + { + TextView tv = (TextView) _activity.FindViewById(viewId); + if (String.IsNullOrEmpty(value)) + { + container.Visibility = tv.Visibility = ViewStates.Gone; + } + else + { + container.Visibility = tv.Visibility = ViewStates.Visible; + tv.Text = value; + } + } } get { - TextView tv = (TextView) _activity.FindViewById(_viewId); + TextView tv = (TextView) _activity.FindViewById(_viewIds.First()); return tv.Text; } } diff --git a/src/keepass2android/FileSelectHelper.cs b/src/keepass2android/FileSelectHelper.cs index 365f981d..496c295f 100644 --- a/src/keepass2android/FileSelectHelper.cs +++ b/src/keepass2android/FileSelectHelper.cs @@ -14,6 +14,7 @@ using Android.Views; using Android.Widget; using Java.IO; using keepass2android.Io; +using Keepass2android.Javafilestorage; using KeePassLib.Serialization; using KeePassLib.Utility; @@ -24,8 +25,9 @@ namespace keepass2android private readonly Activity _activity; private readonly bool _isForSave; private readonly int _requestCode; + private readonly string _schemeSeparator = "://"; - public string DefaultExtension { get; set; } + public string DefaultExtension { get; set; } public FileSelectHelper(Activity activity, bool isForSave, int requestCode) { @@ -34,11 +36,23 @@ namespace keepass2android _requestCode = requestCode; } - private void ShowSftpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel) + private void ShowSftpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) { #if !EXCLUDE_JAVAFILESTORAGE && !NoNet AlertDialog.Builder builder = new AlertDialog.Builder(activity); View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.sftpcredentials, null); + + if (!defaultPath.EndsWith(_schemeSeparator)) + { + var fileStorage = new Keepass2android.Javafilestorage.SftpStorage(); + SftpStorage.ConnectionInfo ci = fileStorage.SplitStringToConnectionInfo(defaultPath); + dlgContents.FindViewById(Resource.Id.sftp_host).Text = ci.Host; + dlgContents.FindViewById(Resource.Id.sftp_port).Text = ci.Port.ToString(); + dlgContents.FindViewById(Resource.Id.sftp_user).Text = ci.Username; + dlgContents.FindViewById(Resource.Id.sftp_password).Text = ci.Password; + dlgContents.FindViewById(Resource.Id.sftp_initial_dir).Text = ci.LocalPath; + } + builder.SetView(dlgContents); builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => @@ -70,6 +84,16 @@ namespace keepass2android #if !EXCLUDE_JAVAFILESTORAGE && !NoNet AlertDialog.Builder builder = new AlertDialog.Builder(activity); View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null); + if (!defaultPath.EndsWith(_schemeSeparator)) + { + var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler); + var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath); + dlgContents.FindViewById(Resource.Id.http_url).Text = connInfo.Url; + dlgContents.FindViewById(Resource.Id.http_user).Text = connInfo.Username; + dlgContents.FindViewById(Resource.Id.http_password).Text = connInfo.Password; + + + } builder.SetView(dlgContents); builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => @@ -79,9 +103,9 @@ namespace keepass2android string user = dlgContents.FindViewById(Resource.Id.http_user).Text; string password = dlgContents.FindViewById(Resource.Id.http_password).Text; - string scheme = defaultPath.Substring(0, defaultPath.IndexOf("://", StringComparison.Ordinal)); - if (host.Contains("://") == false) - host = scheme + "://" + host; + string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); + if (host.Contains(_schemeSeparator) == false) + host = scheme + _schemeSeparator + host; string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user, password); onStartBrowse(httpPath); @@ -96,12 +120,35 @@ namespace keepass2android #endif } - private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel) + private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) { #if !NoNet AlertDialog.Builder builder = new AlertDialog.Builder(activity); View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.ftpcredentials, null); - builder.SetView(dlgContents); + if (!defaultPath.EndsWith(_schemeSeparator)) + { + var connection = NetFtpFileStorage.ConnectionSettings.FromIoc(IOConnectionInfo.FromPath(defaultPath)); + dlgContents.FindViewById(Resource.Id.ftp_user).Text = connection.Username; + dlgContents.FindViewById(Resource.Id.ftp_password).Text = connection.Password; + dlgContents.FindViewById(Resource.Id.ftp_encryption).SetSelection((int)connection.EncryptionMode); + + var uri = NetFtpFileStorage.IocToUri(IOConnectionInfo.FromPath(defaultPath)); + string pathAndQuery = uri.PathAndQuery; + + var host = uri.Host; + var localPath = pathAndQuery; + + + if (!uri.IsDefaultPort) + { + dlgContents.FindViewById(Resource.Id.ftp_port).Text = uri.Port.ToString(); + } + dlgContents.FindViewById(Resource.Id.ftp_host).Text = host; + dlgContents.FindViewById(Resource.Id.ftp_initial_dir).Text = localPath; + + + } + builder.SetView(dlgContents); builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => { @@ -133,9 +180,9 @@ namespace keepass2android public void PerformManualFileSelect(string defaultPath) { if (defaultPath.StartsWith("sftp://")) - ShowSftpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel); + ShowSftpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); else if ((defaultPath.StartsWith("ftp://")) || (defaultPath.StartsWith("ftps://"))) - ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel); + ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://"))) ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); else if (defaultPath.StartsWith("owncloud://")) @@ -169,9 +216,9 @@ namespace keepass2android string user = dlgContents.FindViewById(Resource.Id.http_user).Text; string password = dlgContents.FindViewById(Resource.Id.http_password).Text; - string scheme = defaultPath.Substring(defaultPath.IndexOf("://", StringComparison.Ordinal)); - if (host.Contains("://") == false) - host = scheme + "://" + host; + string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); + if (host.Contains(_schemeSeparator) == false) + host = scheme + _schemeSeparator + host; string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host), user, password); onStartBrowse(httpPath); @@ -189,9 +236,17 @@ namespace keepass2android private bool ReturnFileOrStartFileChooser(string filename) { - int lastSlashPos = filename.LastIndexOf('/'); - int lastDotPos = filename.LastIndexOf('.'); - if (lastSlashPos >= lastDotPos) //no dot after last slash or == in case neither / nor . + string filenameWithoutProt = filename; + if (filenameWithoutProt.Contains(_schemeSeparator)) + { + filenameWithoutProt = + filenameWithoutProt.Substring(filenameWithoutProt.IndexOf(_schemeSeparator, StringComparison.Ordinal) + 3); + } + + int lastSlashPos = filenameWithoutProt.LastIndexOf('/'); + int lastDotPos = filenameWithoutProt.LastIndexOf('.'); + if ((lastSlashPos < 0 ) //no slash, probably only a server address (my.server.com) + || (lastSlashPos >= lastDotPos)) //no dot after last slash or == in case neither / nor . { //looks like a folder. return StartFileChooser(filename); @@ -357,5 +412,13 @@ namespace keepass2android public event EventHandler OnCancel; public event EventHandler OnOpen; + + public static bool CanEditIoc(IOConnectionInfo ioc) + { + return ioc.Path.StartsWith("http") + || ioc.Path.StartsWith("ftp") + || ioc.Path.StartsWith("sftp"); + + } } } \ No newline at end of file diff --git a/src/keepass2android/GroupActivity.cs b/src/keepass2android/GroupActivity.cs index d222f8bb..aa180a14 100644 --- a/src/keepass2android/GroupActivity.cs +++ b/src/keepass2android/GroupActivity.cs @@ -150,7 +150,7 @@ namespace keepass2android var bmp = Bitmap.CreateScaledBitmap( Util.DrawableToBitmap(App.Kp2a.GetDb() - .DrawableFactory.GetIconDrawable(Context, App.Kp2a.GetDb().KpDatabase, templateEntry.IconId, PwUuid.Zero, false)), + .DrawableFactory.GetIconDrawable(Context, App.Kp2a.GetDb().KpDatabase, templateEntry.IconId, PwUuid.Zero, false)), size, size, true); diff --git a/src/keepass2android/GroupBaseActivity.cs b/src/keepass2android/GroupBaseActivity.cs index a187624e..88035704 100644 --- a/src/keepass2android/GroupBaseActivity.cs +++ b/src/keepass2android/GroupBaseActivity.cs @@ -43,260 +43,385 @@ using Object = Java.Lang.Object; namespace keepass2android { - public abstract class GroupBaseActivity : LockCloseActivity - { - public const String KeyEntry = "entry"; - public const String KeyMode = "mode"; + public abstract class GroupBaseActivity : LockCloseActivity + { + public const String KeyEntry = "entry"; + public const String KeyMode = "mode"; static readonly Dictionary bottomBarElementsPriority = new Dictionary() { { Resource.Id.cancel_insert_element, 20 }, { Resource.Id.insert_element, 20 }, - { Resource.Id.autofill_infotext, 10 }, + //only use the same id if elements can be shown simultaneously! + { Resource.Id.autofill_infotext, 11 }, + { Resource.Id.notification_info_android8_infotext, 10 }, + { Resource.Id.infotext, 9 }, { Resource.Id.select_other_entry, 20}, { Resource.Id.add_url_entry, 20}, }; - private readonly HashSet showableBottomBarElements = new HashSet(); + private readonly HashSet showableBottomBarElements = new HashSet(); - private ActivityDesign _design; + private ActivityDesign _design; - public virtual void LaunchActivityForEntry(PwEntry pwEntry, int pos) - { - EntryActivity.Launch(this, pwEntry, pos, AppTask); - } + public virtual void LaunchActivityForEntry(PwEntry pwEntry, int pos) + { + EntryActivity.Launch(this, pwEntry, pos, AppTask); + } - protected GroupBaseActivity() - { - _design = new ActivityDesign(this); - } + protected GroupBaseActivity() + { + _design = new ActivityDesign(this); + } - protected GroupBaseActivity(IntPtr javaReference, JniHandleOwnership transfer) - : base(javaReference, transfer) - { + protected GroupBaseActivity(IntPtr javaReference, JniHandleOwnership transfer) + : base(javaReference, transfer) + { - } + } - protected override void OnSaveInstanceState(Bundle outState) - { - base.OnSaveInstanceState(outState); - AppTask.ToBundle(outState); - } + protected override void OnSaveInstanceState(Bundle outState) + { + base.OnSaveInstanceState(outState); + AppTask.ToBundle(outState); + } - public virtual void SetupNormalButtons() - { - SetNormalButtonVisibility(AddGroupEnabled, AddEntryEnabled); - } + public virtual void SetupNormalButtons() + { + SetNormalButtonVisibility(AddGroupEnabled, AddEntryEnabled); + } - protected virtual bool AddGroupEnabled - { - get { return App.Kp2a.GetDb().CanWrite; } - } - protected virtual bool AddEntryEnabled - { - get { return App.Kp2a.GetDb().CanWrite; } - } + protected virtual bool AddGroupEnabled + { + get { return App.Kp2a.GetDb().CanWrite; } + } + protected virtual bool AddEntryEnabled + { + get { return App.Kp2a.GetDb().CanWrite; } + } - public void SetNormalButtonVisibility(bool showAddGroup, bool showAddEntry) - { - if (FindViewById(Resource.Id.fabCancelAddNew) != null) - { - FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; + public void SetNormalButtonVisibility(bool showAddGroup, bool showAddEntry) + { + if (FindViewById(Resource.Id.fabCancelAddNew) != null) + { + FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNew).Visibility = (showAddGroup || showAddEntry) ? ViewStates.Visible : ViewStates.Gone; - } + FindViewById(Resource.Id.fabAddNew).Visibility = (showAddGroup || showAddEntry) ? ViewStates.Visible : ViewStates.Gone; + } - UpdateBottomBarElementVisibility(Resource.Id.insert_element, false); - UpdateBottomBarElementVisibility(Resource.Id.cancel_insert_element, false); + UpdateBottomBarElementVisibility(Resource.Id.insert_element, false); + UpdateBottomBarElementVisibility(Resource.Id.cancel_insert_element, false); } void UpdateBottomBarVisibility() - { - var bottomBar = FindViewById(Resource.Id.bottom_bar); - //check for null because the "empty" layouts may not have all views - int highestPrio = -1; - HashSet highestPrioElements = new HashSet(); + { + var bottomBar = FindViewById(Resource.Id.bottom_bar); + //check for null because the "empty" layouts may not have all views + int highestPrio = -1; + HashSet highestPrioElements = new HashSet(); if (bottomBar != null) - { - for (int i = 0; i < bottomBar.ChildCount; i++) - { - int id = bottomBar.GetChildAt(i).Id; - if (!showableBottomBarElements.Contains(id)) - continue; - int myPrio = bottomBarElementsPriority[id]; + { + for (int i = 0; i < bottomBar.ChildCount; i++) + { + int id = bottomBar.GetChildAt(i).Id; + if (!showableBottomBarElements.Contains(id)) + continue; + int myPrio = bottomBarElementsPriority[id]; if (!highestPrioElements.Any() || highestPrio < myPrio) - { - highestPrioElements.Clear(); - highestPrio = myPrio; - } - if (highestPrio == myPrio) - { - highestPrioElements.Add(id); + { + highestPrioElements.Clear(); + highestPrio = myPrio; + } + if (highestPrio == myPrio) + { + highestPrioElements.Add(id); } } - bottomBar.Visibility = highestPrioElements.Any() ? ViewStates.Visible : ViewStates.Gone; + bottomBar.Visibility = highestPrioElements.Any() ? ViewStates.Visible : ViewStates.Gone; - for (int i = 0; i < bottomBar.ChildCount; i++) - { - int id = bottomBar.GetChildAt(i).Id; - bottomBar.GetChildAt(i).Visibility = - highestPrioElements.Contains(id) ? ViewStates.Visible : ViewStates.Gone; - } + for (int i = 0; i < bottomBar.ChildCount; i++) + { + int id = bottomBar.GetChildAt(i).Id; + bottomBar.GetChildAt(i).Visibility = highestPrioElements.Contains(id) ? ViewStates.Visible : ViewStates.Gone; + } - if (FindViewById(Resource.Id.divider2) != null) - FindViewById(Resource.Id.divider2).Visibility = highestPrioElements.Any() ? ViewStates.Visible : ViewStates.Gone; - } - } + if (FindViewById(Resource.Id.divider2) != null) + FindViewById(Resource.Id.divider2).Visibility = highestPrioElements.Any() ? ViewStates.Visible : ViewStates.Gone; + } + } - protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) - { - base.OnActivityResult(requestCode, resultCode, data); + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + base.OnActivityResult(requestCode, resultCode, data); - if (AppTask.TryGetFromActivityResult(data, ref AppTask)) - { - //make sure the app task is passed to the calling activity - AppTask.SetActivityResult(this, KeePass.ExitNormal); + if (AppTask.TryGetFromActivityResult(data, ref AppTask)) + { + //make sure the app task is passed to the calling activity + AppTask.SetActivityResult(this, KeePass.ExitNormal); - } + } - if (resultCode == Result.Ok) - { - String groupName = data.Extras.GetString(GroupEditActivity.KeyName); - int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId); - PwUuid groupCustomIconId = - new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId))); - String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid); - GroupBaseActivity act = this; - Handler handler = new Handler(); - RunnableOnFinish task; - if (strGroupUuid == null) - { - task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false); - } - else - { - PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid)); - task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.GetDb().Groups[groupUuid], - new RefreshTask(handler, this)); - } - ProgressTask pt = new ProgressTask(App.Kp2a, act, task); - pt.Run(); - } + if ((GroupEditActivity.RequestCodeGroupEdit == requestCode) && (resultCode == Result.Ok)) + { + String groupName = data.Extras.GetString(GroupEditActivity.KeyName); + int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId); + PwUuid groupCustomIconId = + new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId))); + String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid); + GroupBaseActivity act = this; + Handler handler = new Handler(); + RunnableOnFinish task; + if (strGroupUuid == null) + { + task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false); + } + else + { + PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid)); + task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.GetDb().Groups[groupUuid], + new RefreshTask(handler, this)); + } + ProgressTask pt = new ProgressTask(App.Kp2a, act, task); + pt.Run(); + } - if (resultCode == KeePass.ExitCloseAfterTaskComplete) - { - AppTask.SetActivityResult(this, KeePass.ExitCloseAfterTaskComplete); - Finish(); - } + if (resultCode == KeePass.ExitCloseAfterTaskComplete) + { + AppTask.SetActivityResult(this, KeePass.ExitCloseAfterTaskComplete); + Finish(); + } - if (resultCode == KeePass.ExitReloadDb) - { - AppTask.SetActivityResult(this, KeePass.ExitReloadDb); - Finish(); - } + if (resultCode == KeePass.ExitReloadDb) + { + AppTask.SetActivityResult(this, KeePass.ExitReloadDb); + Finish(); + } - } + } - private ISharedPreferences _prefs; + private ISharedPreferences _prefs; - protected PwGroup Group; + protected PwGroup Group; - internal AppTask AppTask; + internal AppTask AppTask; - private String strCachedGroupUuid = null; - private IMenuItem _offlineItem; - private IMenuItem _onlineItem; - private IMenuItem _syncItem; + private String strCachedGroupUuid = null; + private IMenuItem _offlineItem; + private IMenuItem _onlineItem; + private IMenuItem _syncItem; + private Android.Support.V7.Widget.SearchView searchView; - public String UuidGroup - { - get - { - if (strCachedGroupUuid == null) - { - strCachedGroupUuid = MemUtil.ByteArrayToHexString(Group.Uuid.UuidBytes); - } - return strCachedGroupUuid; - } - } + public String UuidGroup + { + get + { + if (strCachedGroupUuid == null) + { + strCachedGroupUuid = MemUtil.ByteArrayToHexString(Group.Uuid.UuidBytes); + } + return strCachedGroupUuid; + } + } - protected override void OnResume() - { - base.OnResume(); - _design.ReapplyTheme(); - AppTask.StartInGroupActivity(this); - AppTask.SetupGroupBaseActivityButtons(this); + protected override void OnResume() + { + base.OnResume(); + _design.ReapplyTheme(); + AppTask.StartInGroupActivity(this); + AppTask.SetupGroupBaseActivityButtons(this); - UpdateAutofillInfo(); + UpdateAutofillInfo(); + UpdateAndroid8NotificationInfo(); + UpdateInfotexts(); RefreshIfDirty(); - } - public override bool OnSearchRequested() - { - Intent i = new Intent(this, typeof(SearchActivity)); - AppTask.ToIntent(i); - StartActivityForResult(i, 0); - return true; - } + + } - public void RefreshIfDirty() - { - Database db = App.Kp2a.GetDb(); - if (db.Dirty.Contains(Group)) - { - db.Dirty.Remove(Group); - ListAdapter.NotifyDataSetChanged(); + private void UpdateInfotexts() + { - } - } + string lastInfoText; + if (IsTimeForInfotext(out lastInfoText) && (FindViewById(Resource.Id.info_head) != null)) + { - public BaseAdapter ListAdapter - { - get { return (BaseAdapter)FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListAdapter; } - } + FingerprintUnlockMode um; + Enum.TryParse(_prefs.GetString(Database.GetFingerprintModePrefKey(App.Kp2a.GetDb().Ioc), ""), out um); + bool isFingerprintEnabled = (um == FingerprintUnlockMode.FullUnlock); - public virtual bool IsSearchResult - { - get { return false; } - } + string masterKeyKey = "MasterKey" + isFingerprintEnabled; + string emergencyKey = "Emergency"; + string backupKey = "Backup"; - protected override void OnCreate(Bundle savedInstanceState) - { - _design.ApplyTheme(); - base.OnCreate(savedInstanceState); + List applicableInfoTextKeys = new List { masterKeyKey }; - Android.Util.Log.Debug("KP2A", "Creating GBA"); + if (App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc).UserShouldBackup) + { + applicableInfoTextKeys.Add(backupKey); + } + if (App.Kp2a.GetDb().Entries.Count > 15) + { + applicableInfoTextKeys.Add(emergencyKey); + } - AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); + List enabledInfoTextKeys = new List(); + foreach (string key in applicableInfoTextKeys) + { + if (!InfoTextWasDisabled(key)) + enabledInfoTextKeys.Add(key); + } - // Likely the app has been killed exit the activity - if (!App.Kp2a.GetDb().Loaded) - { - Finish(); - return; - } + if (enabledInfoTextKeys.Any()) + { + string infoTextKey = "", infoHead = "", infoMain = "", infoNote = ""; + + if (enabledInfoTextKeys.Count > 1) + { + foreach (string key in enabledInfoTextKeys) + if (key == lastInfoText) + { + enabledInfoTextKeys.Remove(key); + break; + } + infoTextKey = enabledInfoTextKeys[new Random().Next(enabledInfoTextKeys.Count)]; + } + + if (infoTextKey == masterKeyKey) + { + infoHead = GetString(Resource.String.masterkey_infotext_head); + infoMain = GetString(Resource.String.masterkey_infotext_main); + if (isFingerprintEnabled) + infoNote = GetString(Resource.String.masterkey_infotext_fingerprint_note); + } + else if (infoTextKey == emergencyKey) + { + infoHead = GetString(Resource.String.emergency_infotext_head); + infoMain = GetString(Resource.String.emergency_infotext_main); + } + else if (infoTextKey == backupKey) + { + infoHead = GetString(Resource.String.backup_infotext_head); + infoMain = GetString(Resource.String.backup_infotext_main); + infoNote = GetString(Resource.String.backup_infotext_note, GetString(Resource.String.menu_app_settings), GetString(Resource.String.menu_db_settings), GetString(Resource.String.export_prefs)); + } + + + + FindViewById(Resource.Id.info_head).Text = infoHead; + FindViewById(Resource.Id.info_main).Text = infoMain; + var additionalInfoText = FindViewById(Resource.Id.info_additional); + additionalInfoText.Text = infoNote; + additionalInfoText.Visibility = string.IsNullOrEmpty(infoNote) ? ViewStates.Gone : ViewStates.Visible; + + if (infoTextKey != "") + { + + RegisterInfoTextDisplay(infoTextKey); + FindViewById(Resource.Id.info_ok).Click += (sender, args) => + { + UpdateBottomBarElementVisibility(Resource.Id.infotext, false); + }; + FindViewById(Resource.Id.info_dont_show_again).Click += (sender, args) => + { + UpdateBottomBarElementVisibility(Resource.Id.infotext, false); + DisableInfoTextDisplay(infoTextKey); + }; + + UpdateBottomBarElementVisibility(Resource.Id.infotext, true); + } + + } + + + + } + } + + private void UpdateAndroid8NotificationInfo(bool hideForever = false) + { + const string prefsKey = "DidShowAndroid8NotificationInfo"; + + bool canShowNotificationInfo = (Build.VERSION.SdkInt >= BuildVersionCodes.O) && (!_prefs.GetBoolean(prefsKey, false)); + if ((canShowNotificationInfo) && hideForever) + { + _prefs.Edit().PutBoolean(prefsKey, true).Commit(); + canShowNotificationInfo = false; + } + if (canShowNotificationInfo) + { + RegisterInfoTextDisplay("Android8Notification"); //this ensures that we don't show the general info texts too soon + } + UpdateBottomBarElementVisibility(Resource.Id.notification_info_android8_infotext, canShowNotificationInfo); + + + } + + public override bool OnSearchRequested() + { + Intent i = new Intent(this, typeof(SearchActivity)); + AppTask.ToIntent(i); + StartActivityForResult(i, 0); + return true; + } + + public void RefreshIfDirty() + { + Database db = App.Kp2a.GetDb(); + if (db.Dirty.Contains(Group)) + { + db.Dirty.Remove(Group); + ListAdapter.NotifyDataSetChanged(); + + } + } + + public BaseAdapter ListAdapter + { + get { return (BaseAdapter)FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListAdapter; } + } + + public virtual bool IsSearchResult + { + get { return false; } + } + + protected override void OnCreate(Bundle savedInstanceState) + { + _design.ApplyTheme(); + base.OnCreate(savedInstanceState); + + Android.Util.Log.Debug("KP2A", "Creating GBA"); + + AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); + + // Likely the app has been killed exit the activity + if (!App.Kp2a.GetDb().Loaded) + { + Finish(); + return; + } + + _prefs = PreferenceManager.GetDefaultSharedPreferences(this); - _prefs = PreferenceManager.GetDefaultSharedPreferences(this); - SetContentView(ContentResourceId); - if (FindViewById(Resource.Id.enable_autofill) != null) - { - FindViewById(Resource.Id.enable_autofill).Click += (sender, args) => - { - var intent = new Intent(Settings.ActionRequestSetAutofillService); - intent.SetData(Android.Net.Uri.Parse("package:" + PackageName)); + if (FindViewById(Resource.Id.enable_autofill) != null) + { + FindViewById(Resource.Id.enable_autofill).Click += (sender, args) => + { + var intent = new Intent(Settings.ActionRequestSetAutofillService); + intent.SetData(Android.Net.Uri.Parse("package:" + PackageName)); try { StartActivity(intent); @@ -314,780 +439,888 @@ namespace keepass2android _prefs.Edit().PutBoolean(autofillservicewasenabled, true).Commit(); UpdateBottomBarElementVisibility(Resource.Id.autofill_infotext, false); } - }; - } + }; + } + + + if (FindViewById(Resource.Id.info_dont_show_autofill_again) != null) + { + FindViewById(Resource.Id.info_dont_show_autofill_again).Click += (sender, args) => + { + _prefs.Edit().PutBoolean(autofillservicewasenabled_prefskey, true).Commit(); + UpdateAutofillInfo(); + }; + } if (FindViewById(Resource.Id.fabCancelAddNew) != null) - { - FindViewById(Resource.Id.fabAddNew).Click += (sender, args) => - { - FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Visible; - FindViewById(Resource.Id.fabAddNewGroup).Visibility = AddGroupEnabled ? ViewStates.Visible : ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewEntry).Visibility = AddEntryEnabled ? ViewStates.Visible : ViewStates.Gone; - FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; - }; + { + FindViewById(Resource.Id.fabAddNew).Click += (sender, args) => + { + FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Visible; + FindViewById(Resource.Id.fabAddNewGroup).Visibility = AddGroupEnabled ? ViewStates.Visible : ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewEntry).Visibility = AddEntryEnabled ? ViewStates.Visible : ViewStates.Gone; + FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; + }; - FindViewById(Resource.Id.fabCancelAddNew).Click += (sender, args) => - { - FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Visible; - }; + FindViewById(Resource.Id.fabCancelAddNew).Click += (sender, args) => + { + FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Visible; + }; - } + } - if (FindViewById(Resource.Id.cancel_insert_element) != null) - { - FindViewById(Resource.Id.cancel_insert_element).Click += (sender, args) => StopMovingElements(); - FindViewById(Resource.Id.insert_element).Click += (sender, args) => InsertElements(); - Util.MoveBottomBarButtons(Resource.Id.cancel_insert_element, Resource.Id.insert_element, Resource.Id.bottom_bar, this); - } + if (FindViewById(Resource.Id.cancel_insert_element) != null) + { + FindViewById(Resource.Id.cancel_insert_element).Click += (sender, args) => StopMovingElements(); + FindViewById(Resource.Id.insert_element).Click += (sender, args) => InsertElements(); + Util.MoveBottomBarButtons(Resource.Id.cancel_insert_element, Resource.Id.insert_element, Resource.Id.bottom_bar, this); + } - if (FindViewById(Resource.Id.show_autofill_info) != null) - { - FindViewById(Resource.Id.show_autofill_info).Click += (sender, args) => Util.GotoUrl(this, "https://philippc.github.io/keepass2android/OreoAutoFill.html"); + if (FindViewById(Resource.Id.show_autofill_info) != null) + { + FindViewById(Resource.Id.show_autofill_info).Click += (sender, args) => Util.GotoUrl(this, "https://philippc.github.io/keepass2android/OreoAutoFill.html"); Util.MoveBottomBarButtons(Resource.Id.show_autofill_info, Resource.Id.enable_autofill, Resource.Id.autofill_buttons, this); - } + } + + if (FindViewById(Resource.Id.configure_notification_channels) != null) + { + FindViewById(Resource.Id.configure_notification_channels).Click += (sender, args) => + { + Intent intent = new Intent(Settings.ActionChannelNotificationSettings); + intent.PutExtra(Settings.ExtraChannelId, App.NotificationChannelIdQuicklocked); + intent.PutExtra(Settings.ExtraAppPackage, PackageName); + try + { + StartActivity(intent); + } + catch (Exception e) + { + new AlertDialog.Builder(this) + .SetTitle("Unexpected error") + .SetMessage( + "Opening the settings failed. Please report this to crocoapps@gmail.com including information about your device vendor and OS. Please try to configure the notifications by long pressing a KP2A notification. Details: " + e.ToString()) + .Show(); + } + UpdateAndroid8NotificationInfo(true); + }; + FindViewById(Resource.Id.ignore_notification_channel).Click += (sender, args) => + { + UpdateAndroid8NotificationInfo(true); + }; + + } - SetResult(KeePass.ExitNormal); - } + SetResult(KeePass.ExitNormal); - private void UpdateAutofillInfo() - { - bool canShowAutofillInfo = false; - - if (!((Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O) || - !((AutofillManager) GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)))) - .IsAutofillSupported)) - { - const string autofillservicewasenabled = "AutofillServiceWasEnabled"; - if (!((AutofillManager) GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)))) - .HasEnabledAutofillServices) - { - if (!_prefs.GetBoolean(autofillservicewasenabled, false)) - canShowAutofillInfo = true; - } - else - { - _prefs.Edit().PutBoolean(autofillservicewasenabled, true).Commit(); + - } - } - UpdateBottomBarElementVisibility(Resource.Id.autofill_infotext, canShowAutofillInfo); + + + } + + private bool IsTimeForInfotext(out string lastInfoText) + { + DateTime lastDisplayTime = new DateTime(_prefs.GetLong("LastInfoTextTime", 0)); + lastInfoText = _prefs.GetString("LastInfoTextKey", ""); +#if DEBUG + return DateTime.UtcNow - lastDisplayTime > TimeSpan.FromSeconds(10); +#else + return DateTime.UtcNow - lastDisplayTime > TimeSpan.FromDays(3); +#endif } - protected void UpdateBottomBarElementVisibility(int resourceId, bool canShow) - { + private void DisableInfoTextDisplay(string infoTextKey) + { + _prefs + .Edit() + .PutBoolean("InfoTextDisabled_" + infoTextKey, true) + .Commit(); + + } + + private void RegisterInfoTextDisplay(string infoTextKey) + { + _prefs + .Edit() + .PutLong("LastInfoTextTime", DateTime.UtcNow.Ticks) + .PutString("LastInfoTextKey", infoTextKey) + .Commit(); + + } + + private bool InfoTextWasDisabled(string infoTextKey) + { + return _prefs.GetBoolean("InfoTextDisabled_" + infoTextKey, false); + } + + const string autofillservicewasenabled_prefskey = "AutofillServiceWasEnabled"; + + private void UpdateAutofillInfo() + { + bool canShowAutofillInfo = false; + + if (!((Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O) || + !((AutofillManager)GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)))) + .IsAutofillSupported)) + { + if (!((AutofillManager)GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)))) + .HasEnabledAutofillServices) + { + if (!_prefs.GetBoolean(autofillservicewasenabled_prefskey, false)) + canShowAutofillInfo = true; + } + else + { + _prefs.Edit().PutBoolean(autofillservicewasenabled_prefskey, true).Commit(); + + } + } + if (canShowAutofillInfo) + { + RegisterInfoTextDisplay("AutofillSuggestion"); //this ensures that we don't show the general info texts too soon + + } + UpdateBottomBarElementVisibility(Resource.Id.autofill_infotext, canShowAutofillInfo); + } + + protected void UpdateBottomBarElementVisibility(int resourceId, bool canShow) + { if (canShow) showableBottomBarElements.Add(resourceId); else showableBottomBarElements.Remove(resourceId); UpdateBottomBarVisibility(); - } - - protected virtual int ContentResourceId - { - get { return Resource.Layout.group; } - } - - private void InsertElements() - { - MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; - IEnumerable elementsToMove = - moveElementsTask.Uuids.Select(uuid => App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null)); - - - - var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish((success, message) => { StopMovingElements(); if (!String.IsNullOrEmpty(message)) Toast.MakeText(this, message, ToastLength.Long).Show(); })); - var progressTask = new ProgressTask(App.Kp2a, this, moveElement); - progressTask.Run(); - - } - - - - protected void SetGroupTitle() - { - String name = Group.Name; - String titleText; - bool clickable = (Group != null) && (Group.IsVirtual == false) && (Group.ParentGroup != null); - if (!String.IsNullOrEmpty(name)) - { - titleText = name; - } - else - { - titleText = GetText(Resource.String.root); - } - - SupportActionBar.Title = titleText; - if (clickable) - { - SupportActionBar.SetHomeButtonEnabled(true); - SupportActionBar.SetDisplayHomeAsUpEnabled(true); - SupportActionBar.SetDisplayShowHomeEnabled(true); - } - - } - - - protected void SetGroupIcon() - { - if (Group != null) - { - Drawable drawable = App.Kp2a.GetDb().DrawableFactory.GetIconDrawable(this, App.Kp2a.GetDb().KpDatabase, Group.IconId, Group.CustomIconUuid, true); - SupportActionBar.SetDisplayShowHomeEnabled(true); - //SupportActionBar.SetIcon(drawable); - } - } - - class SuggestionListener : Java.Lang.Object, SearchView.IOnSuggestionListener, Android.Support.V7.Widget.SearchView.IOnSuggestionListener - { - private readonly CursorAdapter _suggestionsAdapter; - private readonly GroupBaseActivity _activity; - private readonly IMenuItem _searchItem; - - - public SuggestionListener(Android.Support.V4.Widget.CursorAdapter suggestionsAdapter, GroupBaseActivity activity, IMenuItem searchItem) - { - _suggestionsAdapter = suggestionsAdapter; - _activity = activity; - _searchItem = searchItem; - } - - public bool OnSuggestionClick(int position) - { - var cursor = _suggestionsAdapter.Cursor; - cursor.MoveToPosition(position); - string entryIdAsHexString = cursor.GetString(cursor.GetColumnIndexOrThrow(SearchManager.SuggestColumnIntentDataId)); - EntryActivity.Launch(_activity, App.Kp2a.GetDb().Entries[new PwUuid(MemUtil.HexStringToByteArray(entryIdAsHexString))], -1, _activity.AppTask); - return true; - } - - public bool OnSuggestionSelect(int position) - { - return false; - } - } - - class OnQueryTextListener : Java.Lang.Object, Android.Support.V7.Widget.SearchView.IOnQueryTextListener - { - private readonly GroupBaseActivity _activity; - - public OnQueryTextListener(GroupBaseActivity activity) - { - _activity = activity; - } - - public bool OnQueryTextChange(string newText) - { - return false; - } - - public bool OnQueryTextSubmit(string query) - { - if (String.IsNullOrEmpty(query)) - return false; //let the default happen - - Intent searchIntent = new Intent(_activity, typeof(search.SearchResults)); - searchIntent.SetAction(Intent.ActionSearch); //currently not necessary to set because SearchResults doesn't care, but let's be as close to the default as possible - searchIntent.PutExtra(SearchManager.Query, query); - //forward appTask: - _activity.AppTask.ToIntent(searchIntent); - - _activity.StartActivityForResult(searchIntent, 0); - - return true; - } - } - - public override bool OnCreateOptionsMenu(IMenu menu) - { - - MenuInflater inflater = MenuInflater; - inflater.Inflate(Resource.Menu.group, menu); - var searchManager = (SearchManager)GetSystemService(Context.SearchService); - IMenuItem searchItem = menu.FindItem(Resource.Id.menu_search); - var view = MenuItemCompat.GetActionView(searchItem); - var searchView = view.JavaCast(); - - searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName)); - searchView.SetOnSuggestionListener(new SuggestionListener(searchView.SuggestionsAdapter, this, searchItem)); - searchView.SetOnQueryTextListener(new OnQueryTextListener(this)); - - ActionBar.LayoutParams lparams = new ActionBar.LayoutParams(ActionBar.LayoutParams.MatchParent, - ActionBar.LayoutParams.MatchParent); - searchView.LayoutParameters = lparams; - - _syncItem = menu.FindItem(Resource.Id.menu_sync); - - - _offlineItem = menu.FindItem(Resource.Id.menu_work_offline); - _onlineItem = menu.FindItem(Resource.Id.menu_work_online); - - UpdateOfflineModeMenu(); - - - return base.OnCreateOptionsMenu(menu); - - } - - private void UpdateOfflineModeMenu() - { - try - { - if (_syncItem != null) - { - if (App.Kp2a.GetDb().Ioc.IsLocalFile()) - _syncItem.SetVisible(false); - else - _syncItem.SetVisible(!App.Kp2a.OfflineMode); - } - - if (App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc) is IOfflineSwitchable) - { - if (_offlineItem != null) - _offlineItem.SetVisible(App.Kp2a.OfflineMode == false); - if (_onlineItem != null) - _onlineItem.SetVisible(App.Kp2a.OfflineMode); - } - else - { - if (_offlineItem != null) - _offlineItem.SetVisible(false); - if (_onlineItem != null) - _onlineItem.SetVisible(false); - - } - } - catch (Exception e) - { - Kp2aLog.LogUnexpectedError(new Exception("Cannot UpdateOfflineModeMenu " + (App.Kp2a == null) + " " + ((App.Kp2a == null) || (App.Kp2a.GetDb() == null)) + " " + (((App.Kp2a == null) || (App.Kp2a.GetDb() == null) || (App.Kp2a.GetDb().Ioc == null)) + " " + (_syncItem != null) + " " + (_offlineItem != null) + " " + (_onlineItem != null)))); - } - - } - - - public override bool OnPrepareOptionsMenu(IMenu menu) - { - if (!base.OnPrepareOptionsMenu(menu)) - { - return false; - } - - Util.PrepareDonateOptionMenu(menu, this); - - - return true; - } - - public override bool OnOptionsItemSelected(IMenuItem item) - { - switch (item.ItemId) - { - case Resource.Id.menu_donate: - return Util.GotoDonateUrl(this); - case Resource.Id.menu_lock: - App.Kp2a.LockDatabase(); - return true; - - case Resource.Id.menu_search: - case Resource.Id.menu_search_advanced: - OnSearchRequested(); - return true; - - case Resource.Id.menu_app_settings: - DatabaseSettingsActivity.Launch(this); - return true; - - case Resource.Id.menu_sync: - Synchronize(); - return true; - - case Resource.Id.menu_work_offline: - App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = true; - UpdateOfflineModeMenu(); - return true; - - case Resource.Id.menu_work_online: - App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false; - UpdateOfflineModeMenu(); - Synchronize(); - return true; - - - case Resource.Id.menu_sort: - ChangeSort(); - return true; - case Android.Resource.Id.Home: - //Currently the action bar only displays the home button when we come from a previous activity. - //So we can simply Finish. See this page for information on how to do this in more general (future?) cases: - //http://developer.android.com/training/implementing-navigation/ancestral.html - AppTask.SetActivityResult(this, KeePass.ExitNormal); - Finish(); - //OverridePendingTransition(Resource.Animation.anim_enter_back, Resource.Animation.anim_leave_back); - - return true; - } - - return base.OnOptionsItemSelected(item); - } - - public class SyncOtpAuxFile : RunnableOnFinish - { - private readonly IOConnectionInfo _ioc; - - public SyncOtpAuxFile(IOConnectionInfo ioc) - : base(null) - { - _ioc = ioc; - } - - public override void Run() - { - StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile); - try - { - //simply open the file. The file storage does a complete sync. - using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc)) - { - } + } + + protected virtual int ContentResourceId + { + get { return Resource.Layout.group; } + } + + private void InsertElements() + { + MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; + IEnumerable elementsToMove = + moveElementsTask.Uuids.Select(uuid => App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null)); + + + + var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish((success, message) => { StopMovingElements(); if (!String.IsNullOrEmpty(message)) Toast.MakeText(this, message, ToastLength.Long).Show(); })); + var progressTask = new ProgressTask(App.Kp2a, this, moveElement); + progressTask.Run(); + + } + + + + protected void SetGroupTitle() + { + String name = Group.Name; + String titleText; + bool clickable = (Group != null) && (Group.IsVirtual == false) && (Group.ParentGroup != null); + if (!String.IsNullOrEmpty(name)) + { + titleText = name; + } + else + { + titleText = GetText(Resource.String.root); + } + + SupportActionBar.Title = titleText; + if (clickable) + { + SupportActionBar.SetHomeButtonEnabled(true); + SupportActionBar.SetDisplayHomeAsUpEnabled(true); + SupportActionBar.SetDisplayShowHomeEnabled(true); + } + + } + + + protected void SetGroupIcon() + { + if (Group != null) + { + Drawable drawable = App.Kp2a.GetDb().DrawableFactory.GetIconDrawable(this, App.Kp2a.GetDb().KpDatabase, Group.IconId, Group.CustomIconUuid, true); + SupportActionBar.SetDisplayShowHomeEnabled(true); + //SupportActionBar.SetIcon(drawable); + } + } + + class SuggestionListener : Java.Lang.Object, SearchView.IOnSuggestionListener, Android.Support.V7.Widget.SearchView.IOnSuggestionListener + { + private readonly CursorAdapter _suggestionsAdapter; + private readonly GroupBaseActivity _activity; + private readonly IMenuItem _searchItem; + + + public SuggestionListener(Android.Support.V4.Widget.CursorAdapter suggestionsAdapter, GroupBaseActivity activity, IMenuItem searchItem) + { + _suggestionsAdapter = suggestionsAdapter; + _activity = activity; + _searchItem = searchItem; + } + + public bool OnSuggestionClick(int position) + { + var cursor = _suggestionsAdapter.Cursor; + cursor.MoveToPosition(position); + string entryIdAsHexString = cursor.GetString(cursor.GetColumnIndexOrThrow(SearchManager.SuggestColumnIntentDataId)); + EntryActivity.Launch(_activity, App.Kp2a.GetDb().Entries[new PwUuid(MemUtil.HexStringToByteArray(entryIdAsHexString))], -1, _activity.AppTask); + return true; + } + + public bool OnSuggestionSelect(int position) + { + return false; + } + } + + class OnQueryTextListener : Java.Lang.Object, Android.Support.V7.Widget.SearchView.IOnQueryTextListener + { + private readonly GroupBaseActivity _activity; + + public OnQueryTextListener(GroupBaseActivity activity) + { + _activity = activity; + } + + public bool OnQueryTextChange(string newText) + { + return false; + } + + public bool OnQueryTextSubmit(string query) + { + if (String.IsNullOrEmpty(query)) + return false; //let the default happen + + Intent searchIntent = new Intent(_activity, typeof(search.SearchResults)); + searchIntent.SetAction(Intent.ActionSearch); //currently not necessary to set because SearchResults doesn't care, but let's be as close to the default as possible + searchIntent.PutExtra(SearchManager.Query, query); + //forward appTask: + _activity.AppTask.ToIntent(searchIntent); + + _activity.StartActivityForResult(searchIntent, 0); + + return true; + } + } + + public override bool OnCreateOptionsMenu(IMenu menu) + { + + MenuInflater inflater = MenuInflater; + inflater.Inflate(Resource.Menu.group, menu); + var searchManager = (SearchManager)GetSystemService(Context.SearchService); + IMenuItem searchItem = menu.FindItem(Resource.Id.menu_search); + var view = MenuItemCompat.GetActionView(searchItem); + + searchView = view.JavaCast(); + + searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName)); + searchView.SetOnSuggestionListener(new SuggestionListener(searchView.SuggestionsAdapter, this, searchItem)); + searchView.SetOnQueryTextListener(new OnQueryTextListener(this)); + + if (_prefs.GetBoolean("ActivateSearchView", false) && AppTask.CanActivateSearchViewOnStart) + { + //need to use PostDelayed, otherwise the menu_lock item completely disappears + searchView.PostDelayed(() => + { + searchView.Iconified = false; + AppTask.CanActivateSearchViewOnStart = false; + }, 500); + } + + ActionBar.LayoutParams lparams = new ActionBar.LayoutParams(ActionBar.LayoutParams.MatchParent, + ActionBar.LayoutParams.MatchParent); + searchView.LayoutParameters = lparams; + + + _syncItem = menu.FindItem(Resource.Id.menu_sync); + + + _offlineItem = menu.FindItem(Resource.Id.menu_work_offline); + _onlineItem = menu.FindItem(Resource.Id.menu_work_online); + + UpdateOfflineModeMenu(); + + + return base.OnCreateOptionsMenu(menu); + + } + + private void UpdateOfflineModeMenu() + { + try + { + if (_syncItem != null) + { + if (App.Kp2a.GetDb().Ioc.IsLocalFile()) + _syncItem.SetVisible(false); + else + _syncItem.SetVisible(!App.Kp2a.OfflineMode); + } + + if (App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc) is IOfflineSwitchable) + { + if (_offlineItem != null) + _offlineItem.SetVisible(App.Kp2a.OfflineMode == false); + if (_onlineItem != null) + _onlineItem.SetVisible(App.Kp2a.OfflineMode); + } + else + { + if (_offlineItem != null) + _offlineItem.SetVisible(false); + if (_onlineItem != null) + _onlineItem.SetVisible(false); + + } + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(new Exception("Cannot UpdateOfflineModeMenu " + (App.Kp2a == null) + " " + ((App.Kp2a == null) || (App.Kp2a.GetDb() == null)) + " " + (((App.Kp2a == null) || (App.Kp2a.GetDb() == null) || (App.Kp2a.GetDb().Ioc == null)) + " " + (_syncItem != null) + " " + (_offlineItem != null) + " " + (_onlineItem != null)))); + } + + } + + + public override bool OnPrepareOptionsMenu(IMenu menu) + { + if (!base.OnPrepareOptionsMenu(menu)) + { + return false; + } + + Util.PrepareDonateOptionMenu(menu, this); + + + return true; + } + + public override bool OnOptionsItemSelected(IMenuItem item) + { + switch (item.ItemId) + { + case Resource.Id.menu_donate: + return Util.GotoDonateUrl(this); + case Resource.Id.menu_lock: + App.Kp2a.LockDatabase(); + return true; + + case Resource.Id.menu_search: + case Resource.Id.menu_search_advanced: + OnSearchRequested(); + return true; + + case Resource.Id.menu_app_settings: + DatabaseSettingsActivity.Launch(this); + return true; + + case Resource.Id.menu_sync: + Synchronize(); + return true; + + case Resource.Id.menu_work_offline: + App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = true; + UpdateOfflineModeMenu(); + return true; + + case Resource.Id.menu_work_online: + App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false; + UpdateOfflineModeMenu(); + Synchronize(); + return true; + + + case Resource.Id.menu_sort: + ChangeSort(); + return true; + case Android.Resource.Id.Home: + //Currently the action bar only displays the home button when we come from a previous activity. + //So we can simply Finish. See this page for information on how to do this in more general (future?) cases: + //http://developer.android.com/training/implementing-navigation/ancestral.html + AppTask.SetActivityResult(this, KeePass.ExitNormal); + Finish(); + //OverridePendingTransition(Resource.Animation.anim_enter_back, Resource.Animation.anim_leave_back); + + return true; + } + + return base.OnOptionsItemSelected(item); + } + + public class SyncOtpAuxFile : RunnableOnFinish + { + private readonly IOConnectionInfo _ioc; + + public SyncOtpAuxFile(IOConnectionInfo ioc) + : base(null) + { + _ioc = ioc; + } + + public override void Run() + { + StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile); + try + { + //simply open the file. The file storage does a complete sync. + using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc)) + { + } - Finish(true); - } - catch (Exception e) - { + Finish(true); + } + catch (Exception e) + { - Finish(false, e.Message); - } + Finish(false, e.Message); + } - } + } - } + } - private void Synchronize() - { - var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc); - RunnableOnFinish task; - OnFinish onFinish = new ActionOnFinish((success, message) => - { - if (!String.IsNullOrEmpty(message)) - Toast.MakeText(this, message, ToastLength.Long).Show(); - - // Tell the adapter to refresh it's list - BaseAdapter adapter = (BaseAdapter)ListAdapter; - adapter.NotifyDataSetChanged(); - - if (App.Kp2a.GetDb().OtpAuxFileIoc != null) - { - var task2 = new SyncOtpAuxFile(App.Kp2a.GetDb().OtpAuxFileIoc); - new ProgressTask(App.Kp2a, this, task2).Run(); - } - }); - - if (filestorage is CachingFileStorage) - { - - task = new SynchronizeCachedDatabase(this, App.Kp2a, onFinish); - } - else - { - - task = new CheckDatabaseForChanges(this, App.Kp2a, onFinish); - } - - - - - var progressTask = new ProgressTask(App.Kp2a, this, task); - progressTask.Run(); - - } - - public override void OnBackPressed() - { - AppTask.SetActivityResult(this, KeePass.ExitNormal); - base.OnBackPressed(); - } - - private void ChangeSort() - { - var sortOrderManager = new GroupViewSortOrderManager(this); - IEnumerable sortOptions = sortOrderManager.SortOrders.Select( - o => GetString(o.ResourceId) - ); - - int selectedBefore = sortOrderManager.GetCurrentSortOrderIndex(); - - new AlertDialog.Builder(this) - .SetSingleChoiceItems(sortOptions.ToArray(), selectedBefore, (sender, args) => - { - int selectedAfter = args.Which; - - sortOrderManager.SetNewSortOrder(selectedAfter); - // Refresh menu titles - ActivityCompat.InvalidateOptionsMenu(this); - - // Mark all groups as dirty now to refresh them on load - Database db = App.Kp2a.GetDb(); - db.MarkAllGroupsAsDirty(); - // We'll manually refresh this group so we can remove it - db.Dirty.Remove(Group); - - // Tell the adapter to refresh it's list - - BaseAdapter adapter = (BaseAdapter)ListAdapter; - adapter.NotifyDataSetChanged(); - - - }) - .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) - .Show(); - - - - - } - - public class RefreshTask : OnFinish - { - readonly GroupBaseActivity _act; - public RefreshTask(Handler handler, GroupBaseActivity act) - : base(handler) - { - _act = act; - } - - public override void Run() - { - if (Success) - { - _act.RefreshIfDirty(); - } - else - { - DisplayMessage(_act); - } - } - } - public class AfterDeleteGroup : OnFinish - { - readonly GroupBaseActivity _act; - - public AfterDeleteGroup(Handler handler, GroupBaseActivity act) - : base(handler) - { - _act = act; - } - - - public override void Run() - { - if (Success) - { - _act.RefreshIfDirty(); - } - else - { - Handler.Post(() => - { - Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show(); - }); - - App.Kp2a.LockDatabase(false); - } - } - - } - - public bool IsBeingMoved(PwUuid uuid) - { - MoveElementsTask moveElementsTask = AppTask as MoveElementsTask; - if (moveElementsTask != null) - { - if (moveElementsTask.Uuids.Any(uuidMoved => uuidMoved.Equals(uuid))) - return true; - } - return false; - } - - public void StartTask(AppTask task) - { - AppTask = task; - task.StartInGroupActivity(this); - } - - - public void StartMovingElements() - { - - ShowInsertElementsButtons(); - BaseAdapter adapter = (BaseAdapter)ListAdapter; - adapter.NotifyDataSetChanged(); - } - - public void ShowInsertElementsButtons() - { - FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; - FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; - - UpdateBottomBarElementVisibility(Resource.Id.insert_element, true); - UpdateBottomBarElementVisibility(Resource.Id.cancel_insert_element, true); - - } - - public void StopMovingElements() - { - try - { - MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; - foreach (var uuid in moveElementsTask.Uuids) - { - IStructureItem elementToMove = App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null); - if (elementToMove.ParentGroup != Group) - App.Kp2a.GetDb().Dirty.Add(elementToMove.ParentGroup); - } - } - catch (Exception e) - { - //don't crash if adding to dirty fails but log the exception: - Kp2aLog.LogUnexpectedError(e); - } - - AppTask = new NullTask(); - AppTask.SetupGroupBaseActivityButtons(this); - BaseAdapter adapter = (BaseAdapter)ListAdapter; - adapter.NotifyDataSetChanged(); - } - - - public void EditGroup(PwGroup pwGroup) - { - GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup); - } - } - - public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener - { - private ActionMode _mode; - private int _statusBarColor; - - public override void OnActivityCreated(Bundle savedInstanceState) - { - base.OnActivityCreated(savedInstanceState); - if (App.Kp2a.GetDb().CanWrite) - { - ListView.ChoiceMode = ChoiceMode.MultipleModal; - ListView.SetMultiChoiceModeListener(this); - ListView.ItemLongClick += delegate(object sender, AdapterView.ItemLongClickEventArgs args) - { - ListView.SetItemChecked(args.Position, true); - }; - - } - - ListView.ItemClick += (sender, args) => ((GroupListItemView)args.View).OnClick(); - - StyleListView(); - - } - - protected void StyleListView() - { - ListView lv = ListView; - lv.ScrollBarStyle = ScrollbarStyles.InsideInset; - lv.TextFilterEnabled = true; - - lv.Divider = null; - } - - public bool OnActionItemClicked(ActionMode mode, IMenuItem item) - { - var listView = FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListView; - var checkedItemPositions = listView.CheckedItemPositions; - - List checkedItems = new List(); - for (int i = 0; i < checkedItemPositions.Size(); i++) - { - if (checkedItemPositions.ValueAt(i)) - { - checkedItems.Add(((PwGroupListAdapter)ListAdapter).GetItemAtPosition(checkedItemPositions.KeyAt(i))); - } - } - - //shouldn't happen, just in case... - if (!checkedItems.Any()) - { - return false; - } - Handler handler = new Handler(); - switch (item.ItemId) - { - - case Resource.Id.menu_delete: - - DeleteMultipleItems task = new DeleteMultipleItems((GroupBaseActivity)Activity, App.Kp2a.GetDb(), checkedItems, - new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a); - task.Start(); - break; - case Resource.Id.menu_move: - var navMove = new NavigateToFolderAndLaunchMoveElementTask(checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult); - ((GroupBaseActivity)Activity).StartTask(navMove); - break; - case Resource.Id.menu_copy: - - var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry) checkedItems.First(), - new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity))); - - ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask); - pt.Run(); - break; - - case Resource.Id.menu_navigate: - NavigateToFolder navNavigate = new NavigateToFolder(checkedItems.First().ParentGroup, true); - ((GroupBaseActivity)Activity).StartTask(navNavigate); - break; - case Resource.Id.menu_edit: - GroupEditActivity.Launch(Activity, checkedItems.First().ParentGroup, (PwGroup)checkedItems.First()); - break; - default: - return false; - - - } - listView.ClearChoices(); - ((BaseAdapter)ListAdapter).NotifyDataSetChanged(); - if (_mode != null) - mode.Finish(); - - return true; - } - - public bool OnCreateActionMode(ActionMode mode, IMenu menu) - { - MenuInflater inflater = Activity.MenuInflater; - inflater.Inflate(Resource.Menu.group_entriesselected, menu); - //mode.Title = "Select Items"; - Android.Util.Log.Debug("KP2A", "Create action mode" + mode); - ((PwGroupListAdapter)ListView.Adapter).InActionMode = true; - ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); - _mode = mode; - if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) - { - _statusBarColor = Activity.Window.StatusBarColor; - Activity.Window.SetStatusBarColor(Activity.Resources.GetColor(Resource.Color.appAccentColorDark)); - } - return true; - } - - public void OnDestroyActionMode(ActionMode mode) - { - Android.Util.Log.Debug("KP2A", "Destroy action mode" + mode); - ((PwGroupListAdapter)ListView.Adapter).InActionMode = false; - ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); - _mode = null; - if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) - { - Activity.Window.SetStatusBarColor(new Android.Graphics.Color(_statusBarColor)); - } - } - - public bool OnPrepareActionMode(ActionMode mode, IMenu menu) - { - Android.Util.Log.Debug("KP2A", "Prepare action mode" + mode); - ((PwGroupListAdapter)ListView.Adapter).InActionMode = mode != null; - ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); - return true; - } - - public void OnItemCheckedStateChanged(ActionMode mode, int position, long id, bool @checked) - { - var menuItem = mode.Menu.FindItem(Resource.Id.menu_edit); - if (menuItem != null) - { - menuItem.SetVisible(IsOnlyOneGroupChecked()); - } - - menuItem = mode.Menu.FindItem(Resource.Id.menu_navigate); - if (menuItem != null) - { - menuItem.SetVisible(((GroupBaseActivity)Activity).IsSearchResult && IsOnlyOneItemChecked()); - } - - menuItem = mode.Menu.FindItem(Resource.Id.menu_copy); - if (menuItem != null) - { - menuItem.SetVisible(IsOnlyOneEntryChecked()); - } - } - - private bool IsOnlyOneGroupChecked() - { - var checkedItems = ListView.CheckedItemPositions; - bool hadCheckedGroup = false; - if (checkedItems != null) - { - for (int i = 0; i < checkedItems.Size(); i++) - { - if (checkedItems.ValueAt(i)) - { - if (hadCheckedGroup) - { - return false; - } - - if (((PwGroupListAdapter)ListAdapter).IsGroupAtPosition(checkedItems.KeyAt(i))) - { - hadCheckedGroup = true; - } - else - { - return false; - } - } - } - } - return hadCheckedGroup; - } - - private bool IsOnlyOneItemChecked() - { - var checkedItems = ListView.CheckedItemPositions; - bool hadCheckedItem = false; - if (checkedItems != null) - { - for (int i = 0; i < checkedItems.Size(); i++) - { - if (checkedItems.ValueAt(i)) - { - if (hadCheckedItem) - { - return false; - } - - hadCheckedItem = true; - } - } - } - return hadCheckedItem; - } - - private bool IsOnlyOneEntryChecked() - { - return IsOnlyOneItemChecked() && !IsOnlyOneGroupChecked(); - } - } + private void Synchronize() + { + var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc); + RunnableOnFinish task; + OnFinish onFinish = new ActionOnFinish((success, message) => + { + if (!String.IsNullOrEmpty(message)) + Toast.MakeText(this, message, ToastLength.Long).Show(); + + // Tell the adapter to refresh it's list + BaseAdapter adapter = (BaseAdapter)ListAdapter; + adapter.NotifyDataSetChanged(); + + if (App.Kp2a.GetDb().OtpAuxFileIoc != null) + { + var task2 = new SyncOtpAuxFile(App.Kp2a.GetDb().OtpAuxFileIoc); + new ProgressTask(App.Kp2a, this, task2).Run(); + } + }); + + if (filestorage is CachingFileStorage) + { + + task = new SynchronizeCachedDatabase(this, App.Kp2a, onFinish); + } + else + { + + task = new CheckDatabaseForChanges(this, App.Kp2a, onFinish); + } + + + + + var progressTask = new ProgressTask(App.Kp2a, this, task); + progressTask.Run(); + + } + + public override void OnBackPressed() + { + AppTask.SetActivityResult(this, KeePass.ExitNormal); + base.OnBackPressed(); + } + + private void ChangeSort() + { + var sortOrderManager = new GroupViewSortOrderManager(this); + IEnumerable sortOptions = sortOrderManager.SortOrders.Select( + o => GetString(o.ResourceId) + ); + + int selectedBefore = sortOrderManager.GetCurrentSortOrderIndex(); + + new AlertDialog.Builder(this) + .SetSingleChoiceItems(sortOptions.ToArray(), selectedBefore, (sender, args) => + { + int selectedAfter = args.Which; + + sortOrderManager.SetNewSortOrder(selectedAfter); + // Refresh menu titles + ActivityCompat.InvalidateOptionsMenu(this); + + // Mark all groups as dirty now to refresh them on load + Database db = App.Kp2a.GetDb(); + db.MarkAllGroupsAsDirty(); + // We'll manually refresh this group so we can remove it + db.Dirty.Remove(Group); + + // Tell the adapter to refresh it's list + + BaseAdapter adapter = (BaseAdapter)ListAdapter; + adapter.NotifyDataSetChanged(); + + + }) + .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) + .Show(); + + + + + } + + public class RefreshTask : OnFinish + { + readonly GroupBaseActivity _act; + public RefreshTask(Handler handler, GroupBaseActivity act) + : base(handler) + { + _act = act; + } + + public override void Run() + { + if (Success) + { + _act.RefreshIfDirty(); + } + else + { + DisplayMessage(_act); + } + } + } + public class AfterDeleteGroup : OnFinish + { + readonly GroupBaseActivity _act; + + public AfterDeleteGroup(Handler handler, GroupBaseActivity act) + : base(handler) + { + _act = act; + } + + + public override void Run() + { + if (Success) + { + _act.RefreshIfDirty(); + } + else + { + Handler.Post(() => + { + Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show(); + }); + + App.Kp2a.LockDatabase(false); + } + } + + } + + public bool IsBeingMoved(PwUuid uuid) + { + MoveElementsTask moveElementsTask = AppTask as MoveElementsTask; + if (moveElementsTask != null) + { + if (moveElementsTask.Uuids.Any(uuidMoved => uuidMoved.Equals(uuid))) + return true; + } + return false; + } + + public void StartTask(AppTask task) + { + AppTask = task; + task.StartInGroupActivity(this); + } + + + public void StartMovingElements() + { + + ShowInsertElementsButtons(); + BaseAdapter adapter = (BaseAdapter)ListAdapter; + adapter.NotifyDataSetChanged(); + } + + public void ShowInsertElementsButtons() + { + FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; + FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; + + UpdateBottomBarElementVisibility(Resource.Id.insert_element, true); + UpdateBottomBarElementVisibility(Resource.Id.cancel_insert_element, true); + + } + + public void StopMovingElements() + { + try + { + MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; + foreach (var uuid in moveElementsTask.Uuids) + { + IStructureItem elementToMove = App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null); + if (elementToMove.ParentGroup != Group) + App.Kp2a.GetDb().Dirty.Add(elementToMove.ParentGroup); + } + } + catch (Exception e) + { + //don't crash if adding to dirty fails but log the exception: + Kp2aLog.LogUnexpectedError(e); + } + + AppTask = new NullTask(); + AppTask.SetupGroupBaseActivityButtons(this); + BaseAdapter adapter = (BaseAdapter)ListAdapter; + adapter.NotifyDataSetChanged(); + } + + + public void EditGroup(PwGroup pwGroup) + { + GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup); + } + } + + public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener + { + private ActionMode _mode; + private int _statusBarColor; + + public override void OnActivityCreated(Bundle savedInstanceState) + { + base.OnActivityCreated(savedInstanceState); + + ListView.SetMultiChoiceModeListener(this); + if (App.Kp2a.GetDb().CanWrite) + { + ListView.ChoiceMode = ChoiceMode.MultipleModal; + + ListView.ItemLongClick += delegate(object sender, AdapterView.ItemLongClickEventArgs args) + { + ListView.SetItemChecked(args.Position, true); + }; + + } + else + { + ListView.ChoiceMode = ChoiceMode.None; + } + + ListView.ItemClick += (sender, args) => ((GroupListItemView)args.View).OnClick(); + + StyleListView(); + + } + + protected void StyleListView() + { + ListView lv = ListView; + lv.ScrollBarStyle = ScrollbarStyles.InsideInset; + lv.TextFilterEnabled = true; + + lv.Divider = null; + } + + public bool OnActionItemClicked(ActionMode mode, IMenuItem item) + { + var listView = FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListView; + var checkedItemPositions = listView.CheckedItemPositions; + + List checkedItems = new List(); + for (int i = 0; i < checkedItemPositions.Size(); i++) + { + if (checkedItemPositions.ValueAt(i)) + { + checkedItems.Add(((PwGroupListAdapter)ListAdapter).GetItemAtPosition(checkedItemPositions.KeyAt(i))); + } + } + + //shouldn't happen, just in case... + if (!checkedItems.Any()) + { + return false; + } + Handler handler = new Handler(); + switch (item.ItemId) + { + + case Resource.Id.menu_delete: + + DeleteMultipleItems task = new DeleteMultipleItems((GroupBaseActivity)Activity, App.Kp2a.GetDb(), checkedItems, + new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a); + task.Start(); + break; + case Resource.Id.menu_move: + var navMove = new NavigateToFolderAndLaunchMoveElementTask(checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult); + ((GroupBaseActivity)Activity).StartTask(navMove); + break; + case Resource.Id.menu_copy: + + var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry)checkedItems.First(), + new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity))); + + ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask); + pt.Run(); + break; + + case Resource.Id.menu_navigate: + NavigateToFolder navNavigate = new NavigateToFolder(checkedItems.First().ParentGroup, true); + ((GroupBaseActivity)Activity).StartTask(navNavigate); + break; + case Resource.Id.menu_edit: + GroupEditActivity.Launch(Activity, checkedItems.First().ParentGroup, (PwGroup)checkedItems.First()); + break; + default: + return false; + + + } + listView.ClearChoices(); + ((BaseAdapter)ListAdapter).NotifyDataSetChanged(); + if (_mode != null) + mode.Finish(); + + return true; + } + + public bool OnCreateActionMode(ActionMode mode, IMenu menu) + { + MenuInflater inflater = Activity.MenuInflater; + inflater.Inflate(Resource.Menu.group_entriesselected, menu); + //mode.Title = "Select Items"; + Android.Util.Log.Debug("KP2A", "Create action mode" + mode); + ((PwGroupListAdapter)ListView.Adapter).InActionMode = true; + ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); + _mode = mode; + if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) + { + _statusBarColor = Activity.Window.StatusBarColor; + Activity.Window.SetStatusBarColor(Activity.Resources.GetColor(Resource.Color.appAccentColorDark)); + } + return true; + } + + public void OnDestroyActionMode(ActionMode mode) + { + Android.Util.Log.Debug("KP2A", "Destroy action mode" + mode); + ((PwGroupListAdapter)ListView.Adapter).InActionMode = false; + ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); + _mode = null; + if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) + { + Activity.Window.SetStatusBarColor(new Android.Graphics.Color(_statusBarColor)); + } + } + + public bool OnPrepareActionMode(ActionMode mode, IMenu menu) + { + Android.Util.Log.Debug("KP2A", "Prepare action mode" + mode); + ((PwGroupListAdapter)ListView.Adapter).InActionMode = mode != null; + ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); + UpdateMenuItemVisibilities(mode); + return true; + } + + public void OnItemCheckedStateChanged(ActionMode mode, int position, long id, bool @checked) + { + UpdateMenuItemVisibilities(mode); + } + + private void UpdateMenuItemVisibilities(ActionMode mode) + { + var menuItem = mode.Menu.FindItem(Resource.Id.menu_edit); + if (menuItem != null) + { + menuItem.SetVisible(IsOnlyOneGroupChecked()); + } + + menuItem = mode.Menu.FindItem(Resource.Id.menu_navigate); + if (menuItem != null) + { + menuItem.SetVisible(((GroupBaseActivity) Activity).IsSearchResult && IsOnlyOneItemChecked()); + } + + menuItem = mode.Menu.FindItem(Resource.Id.menu_copy); + if (menuItem != null) + { + menuItem.SetVisible(IsOnlyOneEntryChecked()); + } + } + + private bool IsOnlyOneGroupChecked() + { + var checkedItems = ListView.CheckedItemPositions; + bool hadCheckedGroup = false; + if (checkedItems != null) + { + for (int i = 0; i < checkedItems.Size(); i++) + { + if (checkedItems.ValueAt(i)) + { + if (hadCheckedGroup) + { + return false; + } + + if (((PwGroupListAdapter)ListAdapter).IsGroupAtPosition(checkedItems.KeyAt(i))) + { + hadCheckedGroup = true; + } + else + { + return false; + } + } + } + } + return hadCheckedGroup; + } + + private bool IsOnlyOneItemChecked() + { + var checkedItems = ListView.CheckedItemPositions; + bool hadCheckedItem = false; + if (checkedItems != null) + { + for (int i = 0; i < checkedItems.Size(); i++) + { + if (checkedItems.ValueAt(i)) + { + if (hadCheckedItem) + { + return false; + } + + hadCheckedItem = true; + } + } + } + return hadCheckedItem; + } + + private bool IsOnlyOneEntryChecked() + { + return IsOnlyOneItemChecked() && !IsOnlyOneGroupChecked(); + } + } } diff --git a/src/keepass2android/GroupEditActivity.cs b/src/keepass2android/GroupEditActivity.cs index 203e0971..3da41d72 100644 --- a/src/keepass2android/GroupEditActivity.cs +++ b/src/keepass2android/GroupEditActivity.cs @@ -52,6 +52,8 @@ namespace keepass2android } + public const int RequestCodeGroupEdit = 9713; + public static void Launch(Activity act, PwGroup parentGroup) { @@ -60,7 +62,7 @@ namespace keepass2android PwGroup parent = parentGroup; i.PutExtra(KeyParent, parent.Uuid.ToHexString()); - act.StartActivityForResult(i, 0); + act.StartActivityForResult(i, RequestCodeGroupEdit); } public static void Launch(Activity act, PwGroup parentGroup, PwGroup groupToEdit) diff --git a/src/keepass2android/LockingActivity.cs b/src/keepass2android/LockingActivity.cs index 7a588b14..d883120e 100644 --- a/src/keepass2android/LockingActivity.cs +++ b/src/keepass2android/LockingActivity.cs @@ -16,6 +16,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using System.Collections.Generic; +using Android.App; +using Android.Content; +using Android.Content.PM; using Android.Runtime; namespace keepass2android @@ -35,7 +39,41 @@ namespace keepass2android { } - protected override void OnPause() { + protected override void OnStart() + { + base.OnStart(); + + if (App.Kp2a.GetDb().Loaded) + { + var xcKey = App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(); + if (xcKey != null) + { + xcKey.Activity = this; + _currentlyWaitingKey = xcKey; + + } + + } + + } + + protected override void OnStop() + { + base.OnStop(); + if (App.Kp2a.GetDb().Loaded) + { + var xcKey = App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(); + if (xcKey != null) + { + //don't store a pointer to this activity in the static database object to avoid memory leak + if (xcKey.Activity == this) //don't reset if another activity has come to foreground already + xcKey.Activity = null; + } + + } + } + + protected override void OnPause() { base.OnPause(); TimeoutHelper.Pause(this); @@ -52,6 +90,61 @@ namespace keepass2android TimeoutHelper.Resume(this); } - } + + public const int RequestCodeChallengeYubikey = 793; + + protected ChallengeXCKey _currentlyWaitingKey; + + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + base.OnActivityResult(requestCode, resultCode, data); + if ((requestCode == RequestCodeChallengeYubikey) && (_currentlyWaitingKey != null)) + { + if (resultCode == Result.Ok) + { + byte[] challengeResponse = data.GetByteArrayExtra("response"); + if ((challengeResponse != null) && (challengeResponse.Length > 0)) + { + _currentlyWaitingKey.Response = challengeResponse; + } + else + _currentlyWaitingKey.Error = "Did not receive a valid response."; + + + } + else + { + _currentlyWaitingKey.Error = "Cancelled Yubichallenge."; + } + + } + + } + + + public Intent TryGetYubichallengeIntentOrPrompt(byte[] challenge, bool promptToInstall) + { + Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); + chalIntent.PutExtra("challenge", challenge); + chalIntent.PutExtra("slot", 2); + IList activities = PackageManager.QueryIntentActivities(chalIntent, 0); + bool isIntentSafe = activities.Count > 0; + if (isIntentSafe) + { + return chalIntent; + } + if (promptToInstall) + { + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.SetMessage(Resource.String.YubiChallengeNotInstalled); + b.SetPositiveButton(Android.Resource.String.Ok, + delegate { Util.GotoUrl(this, GetString(Resource.String.MarketURL) + "com.yubichallenge"); }); + b.SetNegativeButton(Resource.String.cancel, delegate { }); + b.Create().Show(); + } + return null; + } + } } diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs index 187e777c..e4be3cf1 100644 --- a/src/keepass2android/PasswordActivity.cs +++ b/src/keepass2android/PasswordActivity.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; @@ -60,7 +61,6 @@ using Process = Android.OS.Process; using KeeChallenge; using KeePassLib.Cryptography.KeyDerivation; -using KeePassLib.Security; using AlertDialog = Android.App.AlertDialog; using Enum = System.Enum; using Exception = System.Exception; @@ -69,72 +69,7 @@ using Toolbar = Android.Support.V7.Widget.Toolbar; namespace keepass2android { - class ChallengeXCKey : IUserKey, ISeedBasedUserKey - { - private readonly Activity _activity; - private readonly int _requestCode; - - public ProtectedBinary KeyData - { - get - { - - _activity.RunOnUiThread( - () => - { - //TODO refactor to use code from PasswordActivity including notice to install Yubichallenge - Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); - byte[] challenge = _masterSeed; - byte[] challenge64 = new byte[64]; - for (int i = 0; i < 64; i++) - { - if (i < challenge.Length) - { - challenge64[i] = challenge[i]; - } - else - { - challenge64[i] = (byte) (challenge64.Length - challenge.Length); - } - - } - - Kp2aLog.Log(MemUtil.ByteArrayToHexString(challenge64)); - - chalIntent.PutExtra("challenge", challenge64); - chalIntent.PutExtra("slot", 2); - IList activities = _activity.PackageManager.QueryIntentActivities(chalIntent, 0); - bool isIntentSafe = activities.Count > 0; - if (isIntentSafe) - { - _activity.StartActivityForResult(chalIntent, _requestCode); - } - else throw new Exception("TODO implement: you need YubiChallenge"); - }); - while (Response == null) - Thread.Sleep(100); - - return new ProtectedBinary(true, Response); - } - } - - private byte[] _masterSeed; - - public ChallengeXCKey(Activity activity, int requestCode) - { - this._activity = activity; - _requestCode = requestCode; - Response = null; - } - - public void SetParams(byte[] masterSeed) - { - _masterSeed = masterSeed; - } - - public byte[] Response { get; set; } - } - [Activity(Label = "@string/app_name", + [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation, LaunchMode = LaunchMode.SingleInstance, WindowSoftInputMode = SoftInput.AdjustResize, @@ -175,7 +110,7 @@ namespace keepass2android private const int RequestCodePrepareDbFile = 1000; private const int RequestCodePrepareOtpAuxFile = 1001; - private const int RequestCodeChallengeYubikey = 1002; + private const int RequestCodeSelectKeyfile = 1003; private const int RequestCodePrepareKeyFile = 1004; private const int RequestCodeSelectAuxFile = 1005; @@ -184,8 +119,6 @@ namespace keepass2android private Task _loadDbFileTask; private bool _loadDbTaskOffline; //indicate if preloading was started with offline mode - private ChallengeXCKey _currentlyWaitingKey; - private IOConnectionInfo _ioConnection; private String _keyFileOrProvider; bool _showPassword; @@ -375,7 +308,7 @@ namespace keepass2android // to retry with typing the full password, but that's intended to avoid showing the password to a // a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274) Handler handler = new Handler(); - OnFinish onFinish = new AfterLoad(handler, this); + OnFinish onFinish = new AfterLoad(handler, this, _ioConnection); _performingLoad = true; LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, _keyFileOrProvider, onFinish); _loadDbFileTask = null; // prevent accidental re-use @@ -434,69 +367,71 @@ namespace keepass2android GetAuxFileLoader().LoadAuxFile(false); } - if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok) - { - try - { - byte[] challengeResponse = data.GetByteArrayExtra("response"); - if (_currentlyWaitingKey != null) - { - _currentlyWaitingKey.Response = challengeResponse; - return; - } - else - { - _challengeProv = new KeeChallengeProv(); - _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); - Array.Clear(challengeResponse, 0, challengeResponse.Length); - } - - } - catch (Exception e) - { - Kp2aLog.Log(e.ToString()); - Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); - return; - } - - UpdateOkButtonState(); - FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; - - if (_challengeSecret != null) - { - new LoadingDialog(this, true, - //doInBackground - delegate - { - //save aux file - try - { - ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); - if (!temp.Save(_otpAuxIoc)) - { - Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long).Show(); - return false; - } - - } - catch (Exception e) - { - Kp2aLog.LogUnexpectedError(e); - } - return null; - } - , delegate - { - - }).Execute(); - - } - else - { - Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); - return; - } - } + if (requestCode == RequestCodeChallengeYubikey) + { + if (_currentlyWaitingKey != null) + { + //ActivityResult was handled in base class already + return; + } + + if (resultCode == Result.Ok) + { + + try + { + byte[] challengeResponse = data.GetByteArrayExtra("response"); + _challengeProv = new KeeChallengeProv(); + _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); + Array.Clear(challengeResponse, 0, challengeResponse.Length); + } + catch (Exception e) + { + Kp2aLog.Log(e.ToString()); + Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); + return; + } + + UpdateOkButtonState(); + FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; + + if (_challengeSecret != null) + { + new LoadingDialog(this, true, + //doInBackground + delegate + { + //save aux file + try + { + ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); + if (!temp.Save(_otpAuxIoc)) + { + Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long) + .Show(); + return false; + } + + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + } + return null; + } + , delegate + { + + }).Execute(); + + } + else + { + Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); + return; + } + } + } } private AuxFileLoader GetAuxFileLoader() @@ -690,27 +625,12 @@ namespace keepass2android protected override void HandleSuccess() { - Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); - chalIntent.PutExtra("challenge", Activity._chalInfo.Challenge); - chalIntent.PutExtra("slot", 2); - IList activities = Activity.PackageManager.QueryIntentActivities(chalIntent, 0); - bool isIntentSafe = activities.Count > 0; - if (isIntentSafe) - { - Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey); - } - else - { - AlertDialog.Builder b = new AlertDialog.Builder(Activity); - b.SetMessage(Resource.String.YubiChallengeNotInstalled); - b.SetPositiveButton(Android.Resource.String.Ok, - delegate - { - Util.GotoUrl(Activity, Activity.GetString(Resource.String.MarketURL) + "com.yubichallenge"); - }); - b.SetNegativeButton(Resource.String.cancel, delegate { }); - b.Create().Show(); - } + var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(Activity._chalInfo.Challenge, true); + + if (chalIntent != null) + { + Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey); + } } protected override string GetErrorMessage() @@ -746,7 +666,8 @@ namespace keepass2android } } - private void ShowOtpEntry(IList prefilledOtps) + + private void ShowOtpEntry(IList prefilledOtps) { FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible; @@ -1071,14 +992,11 @@ namespace keepass2android btn.PostDelayed(() => { - //re-init fingerprint unlock in case something goes wrong with opening the database - InitFingerprintUnlock(); //fire OnOk(true); - }, 1000); + }, 500); - - } + } private void InitializeNavDrawerButtons() { @@ -1152,12 +1070,15 @@ namespace keepass2android public override void OnBackPressed() { - if (_drawerLayout.IsDrawerOpen((int) GravityFlags.Start)) + if (_drawerLayout != null) { - _drawerLayout.CloseDrawer((int)GravityFlags.Start); - return; + if (_drawerLayout.IsDrawerOpen((int) GravityFlags.Start)) + { + _drawerLayout.CloseDrawer((int) GravityFlags.Start); + return; + } } - base.OnBackPressed(); + base.OnBackPressed(); } private void InitializeOtpSecretSpinner() @@ -1479,8 +1400,11 @@ namespace keepass2android private void PerformLoadDatabase() { _currentlyWaitingKey = null; - //put loading into background thread to allow loading the key file (potentially over network) - new SimpleLoadingDialog(this, GetString(Resource.String.loading), + if (_performingLoad) + return; + _performingLoad = true; + //put loading into background thread to allow loading the key file (potentially over network) + new SimpleLoadingDialog(this, GetString(Resource.String.loading), true, () => { CompositeKey compositeKey; @@ -1488,7 +1412,8 @@ namespace keepass2android if (!CreateCompositeKey(out compositeKey, out errorMessage)) return (() => { Toast.MakeText(this, errorMessage, ToastLength.Long).Show(); - }); + _performingLoad = false; + }); return () => { PerformLoadDatabaseWithCompositeKey(compositeKey); }; }).Execute(); @@ -1524,8 +1449,7 @@ namespace keepass2android MakePasswordMaskedOrVisible(); Handler handler = new Handler(); - OnFinish onFinish = new AfterLoad(handler, this); - _performingLoad = true; + OnFinish onFinish = new AfterLoad(handler, this, _ioConnection); LoadDb task = (KeyProviderType == KeyProviders.Otp) ? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, _keyFileOrProvider, onFinish, this) @@ -1700,7 +1624,8 @@ namespace keepass2android base.OnStart(); _starting = true; - DonateReminder.ShowDonateReminderIfAppropriate(this); + AppTask.CanActivateSearchViewOnStart = true; + DonateReminder.ShowDonateReminderIfAppropriate(this); } @@ -1769,10 +1694,9 @@ namespace keepass2android if ((intent != null) && (intent.HasExtra(Intents.OtpExtraKey))) { string otp = intent.GetStringExtra(Intents.OtpExtraKey); - + _keepPasswordInOnResume = true; if (this.KeyProviderType == KeyProviders.Otp) { - _keepPasswordInOnResume = true; if (_otpInfo == null) { @@ -2102,11 +2026,13 @@ namespace keepass2android private class AfterLoad : OnFinish { readonly PasswordActivity _act; + private readonly IOConnectionInfo _ioConnection; - public AfterLoad(Handler handler, PasswordActivity act):base(handler) - { - _act = act; - } + public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(handler) + { + _act = act; + _ioConnection = ioConnection; + } public override void Run() @@ -2117,41 +2043,63 @@ namespace keepass2android _act.ClearEnteredPassword(); _act.BroadcastOpenDatabase(); _act.InvalidCompositeKeyCount = 0; + _act.LoadingErrorCount = 0; - GC.Collect(); // Ensure temporary memory used while loading is collected + GC.Collect(); // Ensure temporary memory used while loading is collected } + if (Exception != null) + { + _act.LoadingErrorCount++; + } - if (Exception is InvalidCompositeKeyException) - { - _act.InvalidCompositeKeyCount++; - if (_act.UsedFingerprintUnlock) - { - //disable fingerprint unlock if master password changed - _act.ClearFingerprintUnlockData(); - _act.InitFingerprintUnlock(); + if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) + { + Message = _act.GetString(Resource.String.CorruptDatabaseHelp); + } - Message = _act.GetString(Resource.String.fingerprint_disabled_wrong_masterkey) + " " + _act.GetString(Resource.String.fingerprint_reenable2); - } - else - { - if (_act.InvalidCompositeKeyCount > 1) - { - Message = _act.GetString(Resource.String.RepeatedInvalidCompositeKeyHelp); - } - else - { - Message = _act.GetString(Resource.String.FirstInvalidCompositeKeyError); - } - } - + if (Exception is InvalidCompositeKeyException) + { + _act.InvalidCompositeKeyCount++; + if (_act.UsedFingerprintUnlock) + { + //disable fingerprint unlock if master password changed + _act.ClearFingerprintUnlockData(); + _act.InitFingerprintUnlock(); - } - if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) - { - Message = _act.GetString(Resource.String.CorruptDatabaseHelp); - } + Message = _act.GetString(Resource.String.fingerprint_disabled_wrong_masterkey) + " " + + _act.GetString(Resource.String.fingerprint_reenable2); + } + else + { + if (_act.InvalidCompositeKeyCount > 1) + { + Message = _act.GetString(Resource.String.RepeatedInvalidCompositeKeyHelp); + if (_act._prefs.GetBoolean(IoUtil.GetIocPrefKey(_ioConnection, "has_local_backup"), false)) + { + Java.Lang.Object changeDb = _act.GetString(Resource.String.menu_change_db); + Message += _act.GetString(Resource.String.HintLocalBackupInvalidCompositeKey, new Java.Lang.Object[] {changeDb}); + } + } + else + { + Message = _act.GetString(Resource.String.FirstInvalidCompositeKeyError); + } + } + + + } + else if (_act.LoadingErrorCount > 1) + { + if (_act._prefs.GetBoolean(IoUtil.GetIocPrefKey(_ioConnection, "has_local_backup"), false)) + { + Java.Lang.Object changeDb = _act.GetString(Resource.String.menu_change_db); + Message += _act.GetString(Resource.String.HintLocalBackupOtherError, new Java.Lang.Object[] { changeDb }); + } + + } + if ((Message != null) && (Message.Length > 150)) //show long messages as dialog @@ -2178,8 +2126,12 @@ namespace keepass2android } + //re-init fingerprint unlock in case something went wrong with opening the database + if (!Success) + _act.InitFingerprintUnlock(); - _act._performingLoad = false; + + _act._performingLoad = false; } } @@ -2188,8 +2140,12 @@ namespace keepass2android { get; set; } + public int LoadingErrorCount + { + get; set; + } - private void BroadcastOpenDatabase() + private void BroadcastOpenDatabase() { App.Kp2a.BroadcastDatabaseAction(this, Strings.ActionOpenDatabase); } diff --git a/src/keepass2android/Properties/AndroidManifest_debug.xml b/src/keepass2android/Properties/AndroidManifest_debug.xml index 41eebd8e..422418a3 100644 --- a/src/keepass2android/Properties/AndroidManifest_debug.xml +++ b/src/keepass2android/Properties/AndroidManifest_debug.xml @@ -1,9 +1,12 @@  - + - - + + @@ -99,6 +102,14 @@ + + + + + diff --git a/src/keepass2android/Properties/AndroidManifest_light.xml b/src/keepass2android/Properties/AndroidManifest_light.xml index 0e57ca33..40938f31 100644 --- a/src/keepass2android/Properties/AndroidManifest_light.xml +++ b/src/keepass2android/Properties/AndroidManifest_light.xml @@ -40,6 +40,14 @@ + + + + + diff --git a/src/keepass2android/Properties/AndroidManifest_net.xml b/src/keepass2android/Properties/AndroidManifest_net.xml index 9c3820a1..41ce9575 100644 --- a/src/keepass2android/Properties/AndroidManifest_net.xml +++ b/src/keepass2android/Properties/AndroidManifest_net.xml @@ -1,14 +1,14 @@  - - - + + + - + @@ -122,6 +122,14 @@ + + + + + diff --git a/src/keepass2android/Properties/AndroidManifest_nonet.xml b/src/keepass2android/Properties/AndroidManifest_nonet.xml index daa24374..b62671b7 100644 --- a/src/keepass2android/Properties/AndroidManifest_nonet.xml +++ b/src/keepass2android/Properties/AndroidManifest_nonet.xml @@ -7,7 +7,7 @@ - + @@ -105,6 +105,14 @@ + + + + + diff --git a/src/keepass2android/QuickUnlock.cs b/src/keepass2android/QuickUnlock.cs index 45000438..a04543d6 100644 --- a/src/keepass2android/QuickUnlock.cs +++ b/src/keepass2android/QuickUnlock.cs @@ -107,7 +107,16 @@ namespace keepass2android _quickUnlockLength = App.Kp2a.QuickUnlockKeyLength; - txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[] {_quickUnlockLength}); + if (PreferenceManager.GetDefaultSharedPreferences(this) + .GetBoolean(GetString(Resource.String.QuickUnlockHideLength_key), false)) + { + txtLabel.Text = GetString(Resource.String.QuickUnlock_label_secure); + } + else + { + txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[] { _quickUnlockLength }); + } + EditText pwd = (EditText) FindViewById(Resource.Id.QuickUnlock_password); pwd.SetEms(_quickUnlockLength); @@ -340,8 +349,13 @@ namespace keepass2android { KcpPassword kcpPassword = (KcpPassword) App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof (KcpPassword)); String password = kcpPassword.Password.ReadString(); - String expectedPasswordPart = password.Substring(Math.Max(0, password.Length - _quickUnlockLength), - Math.Min(password.Length, _quickUnlockLength)); + + var passwordStringInfo = new System.Globalization.StringInfo(password); + + int passwordLength = passwordStringInfo.LengthInTextElements; + + String expectedPasswordPart = passwordStringInfo.SubstringByTextElements(Math.Max(0, passwordLength - _quickUnlockLength), + Math.Min(passwordLength, _quickUnlockLength)); return expectedPasswordPart; } } diff --git a/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_offline.png b/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_offline.png deleted file mode 100644 index 9a62b449..00000000 Binary files a/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_offline.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_online.png b/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_online.png deleted file mode 100644 index 83b00633..00000000 Binary files a/src/keepass2android/Resources/drawable-hdpi-v4/ic_launcher_online.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_offline.png b/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_offline.png deleted file mode 100644 index aa121437..00000000 Binary files a/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_offline.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_online.png b/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_online.png deleted file mode 100644 index 0b310c63..00000000 Binary files a/src/keepass2android/Resources/drawable-mdpi-v4/ic_launcher_online.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_offline.png b/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_offline.png deleted file mode 100644 index 5486a780..00000000 Binary files a/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_offline.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_online.png b/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_online.png deleted file mode 100644 index 825a1e4e..00000000 Binary files a/src/keepass2android/Resources/drawable-xhdpi/ic_launcher_online.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_offline.png b/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_offline.png deleted file mode 100644 index f442de30..00000000 Binary files a/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_offline.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_online.png b/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_online.png deleted file mode 100644 index 44af377b..00000000 Binary files a/src/keepass2android/Resources/drawable-xxxhdpi/ic_launcher_online.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable/ic_launcher.png b/src/keepass2android/Resources/drawable/ic_launcher.png deleted file mode 100644 index 0b310c63..00000000 Binary files a/src/keepass2android/Resources/drawable/ic_launcher.png and /dev/null differ diff --git a/src/keepass2android/Resources/drawable/ic_launcher_background_blue.xml b/src/keepass2android/Resources/drawable/ic_launcher_background_blue.xml new file mode 100644 index 00000000..254e1b49 --- /dev/null +++ b/src/keepass2android/Resources/drawable/ic_launcher_background_blue.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/keepass2android/Resources/drawable/ic_launcher_background_green.xml b/src/keepass2android/Resources/drawable/ic_launcher_background_green.xml new file mode 100644 index 00000000..7fa8b42a --- /dev/null +++ b/src/keepass2android/Resources/drawable/ic_launcher_background_green.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/keepass2android/Resources/drawable/ic_launcher_foreground.xml b/src/keepass2android/Resources/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..f6a8d2e7 --- /dev/null +++ b/src/keepass2android/Resources/drawable/ic_launcher_foreground.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/keepass2android/Resources/layout/entry_extrastring_value.xml b/src/keepass2android/Resources/layout/entry_extrastring_value.xml index b5d14fe1..0bc1272e 100644 --- a/src/keepass2android/Resources/layout/entry_extrastring_value.xml +++ b/src/keepass2android/Resources/layout/entry_extrastring_value.xml @@ -22,4 +22,11 @@ android:typeface="monospace" android:layout_toLeftOf="@id/extra_vdots" style="@style/EntryItem" /> + \ No newline at end of file diff --git a/src/keepass2android/Resources/layout/entry_view_contents.xml b/src/keepass2android/Resources/layout/entry_view_contents.xml index 8c05ad38..4d08fa4f 100644 --- a/src/keepass2android/Resources/layout/entry_view_contents.xml +++ b/src/keepass2android/Resources/layout/entry_view_contents.xml @@ -174,6 +174,12 @@ android:typeface="monospace" android:layout_toLeftOf="@id/password_vdots" style="@style/EntryItem" /> + diff --git a/src/keepass2android/Resources/layout/file_row.xml b/src/keepass2android/Resources/layout/file_row.xml index 03188fa5..8c25e70c 100644 --- a/src/keepass2android/Resources/layout/file_row.xml +++ b/src/keepass2android/Resources/layout/file_row.xml @@ -17,10 +17,28 @@ You should have received a copy of the GNU General Public License along with Keepass2Android. If not, see . --> - \ No newline at end of file + + + + + diff --git a/src/keepass2android/Resources/layout/ftpcredentials.xml b/src/keepass2android/Resources/layout/ftpcredentials.xml index 8e666bcb..f0fc468e 100644 --- a/src/keepass2android/Resources/layout/ftpcredentials.xml +++ b/src/keepass2android/Resources/layout/ftpcredentials.xml @@ -16,8 +16,8 @@ android:layout_height="wrap_content" android:singleLine="true" android:text="" - android:layout_weight="1" - android:inputType="textNoSuggestions" + android:layout_weight="1" + android:inputType="textWebEmailAddress" android:hint="@string/hint_sftp_host" /> @@ -62,6 +63,7 @@ android:layout_height="wrap_content" android:layout_marginLeft="4dip" android:layout_marginTop="4dip" + android:inputType="textWebEmailAddress" android:text="@string/initial_directory" /> + +
public class MoveElementsTask: AppTask { - public const String UuidsKey = "MoveElement_Uuids"; + public override bool CanActivateSearchViewOnStart + { + get { return false; } + set { } + } + + public const String UuidsKey = "MoveElement_Uuids"; public IEnumerable Uuids { @@ -632,10 +656,16 @@ namespace keepass2android ShowUserNotifications = true; } - /// - /// extra key if only a URL is passed. optional. - /// - public const String UrlKey = "CreateEntry_Url"; + public override bool CanActivateSearchViewOnStart + { + get { return false; } + set { } + } + + /// + /// extra key if only a URL is passed. optional. + /// + public const String UrlKey = "CreateEntry_Url"; /// /// extra key if a json serialized key/value mapping is passed. optional. @@ -739,9 +769,15 @@ namespace keepass2android /// public abstract class NavigateAndLaunchTask: AppTask { - // All group Uuid are stored in guuidKey + indice - // The last one is the destination group - public const String NumberOfGroupsKey = "NumberOfGroups"; + public override bool CanActivateSearchViewOnStart + { + get { return false; } + set { } + } + + // All group Uuid are stored in guuidKey + indice + // The last one is the destination group + public const String NumberOfGroupsKey = "NumberOfGroups"; public const String GUuidKey = "gUuidKey"; public const String FullGroupNameKey = "fullGroupNameKey"; public const String ToastEnableKey = "toastEnableKey"; diff --git a/src/keepass2android/app/ApplicationBroadcastReceiver.cs b/src/keepass2android/app/ApplicationBroadcastReceiver.cs index e1096c39..51ba4303 100644 --- a/src/keepass2android/app/ApplicationBroadcastReceiver.cs +++ b/src/keepass2android/app/ApplicationBroadcastReceiver.cs @@ -8,7 +8,6 @@ using Android.Preferences; namespace keepass2android { [BroadcastReceiver] - [IntentFilter(new[] { Intents.LockDatabase, Intents.CloseDatabase })] public class ApplicationBroadcastReceiver : BroadcastReceiver { public override void OnReceive(Context context, Intent intent) diff --git a/src/keepass2android/fileselect/FileDbHelper.cs b/src/keepass2android/fileselect/FileDbHelper.cs index 7e01e3d5..edd5910d 100644 --- a/src/keepass2android/fileselect/FileDbHelper.cs +++ b/src/keepass2android/fileselect/FileDbHelper.cs @@ -34,13 +34,14 @@ namespace keepass2android private const String DatabaseName = "keepass2android"; private const String FileTable = "files"; - private const int DatabaseVersion = 1; + private const int DatabaseVersion = 2; private const int MaxFiles = 15; public const String KeyFileId = "_id"; public const String KeyFileFilename = "fileName"; - public const String KeyFileUsername = "username"; + public const String KeyFileDisplayname = "displayname"; + public const String KeyFileUsername = "username"; public const String KeyFilePassword = "password"; public const String KeyFileCredsavemode = "credSaveMode"; public const String KeyFileKeyfile = "keyFile"; @@ -48,12 +49,14 @@ namespace keepass2android private const String DatabaseCreate = "create table " + FileTable + " ( " + KeyFileId + " integer primary key autoincrement, " - + KeyFileFilename + " text not null, " - + KeyFileKeyfile + " text, " + + KeyFileFilename + " text not null, " + + KeyFileKeyfile + " text, " + KeyFileUsername + " text, " + KeyFilePassword + " text, " + KeyFileCredsavemode + " integer not null," - + KeyFileUpdated + " integer not null);"; + + KeyFileUpdated + " integer not null," + + KeyFileDisplayname + " text " + +");"; private readonly Context mCtx; private DatabaseHelper mDbHelper; @@ -71,7 +74,11 @@ namespace keepass2android public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // Only one database version so far + if (oldVersion == 1) + { + db.ExecSQL("alter table " + FileTable + " add column " + KeyFileDisplayname + " text "); + } + } } @@ -94,7 +101,7 @@ namespace keepass2android mDb.Close(); } - public long CreateFile(IOConnectionInfo ioc, String keyFile) { + public long CreateFile(IOConnectionInfo ioc, string keyFile, string displayName = "") { // Check to see if this filename is already used ICursor cursor; @@ -126,6 +133,7 @@ namespace keepass2android vals.Put(KeyFileUsername, iocToStore.UserName); vals.Put(KeyFilePassword, iocToStore.Password); vals.Put(KeyFileCredsavemode, (int)iocToStore.CredSaveMode); + vals.Put(KeyFileDisplayname, displayName); result = mDb.Update(FileTable, vals, KeyFileId + " = " + id, null); @@ -138,8 +146,9 @@ namespace keepass2android vals.Put(KeyFilePassword, iocToStore.Password); vals.Put(KeyFileCredsavemode, (int)iocToStore.CredSaveMode); vals.Put(KeyFileUpdated, Java.Lang.JavaSystem.CurrentTimeMillis()); - - result = mDb.Insert(FileTable, null, vals); + vals.Put(KeyFileDisplayname, displayName); + + result = mDb.Insert(FileTable, null, vals); } // Delete all but the last X records @@ -193,7 +202,8 @@ namespace keepass2android KeyFileKeyfile, KeyFileUsername, KeyFilePassword, - KeyFileCredsavemode + KeyFileCredsavemode, + KeyFileDisplayname }; } diff --git a/src/keepass2android/fileselect/FileSelectActivity.cs b/src/keepass2android/fileselect/FileSelectActivity.cs index a6b0a953..1097c566 100644 --- a/src/keepass2android/fileselect/FileSelectActivity.cs +++ b/src/keepass2android/fileselect/FileSelectActivity.cs @@ -72,8 +72,9 @@ namespace keepass2android internal AppTask AppTask; private const int RequestCodeSelectIoc = 456; + private const int RequestCodeEditIoc = 457; - public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity"; + public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity"; protected override void OnCreate(Bundle savedInstanceState) { @@ -200,35 +201,121 @@ namespace keepass2android outState.PutBoolean(BundleKeyRecentMode, _recentMode); } - - - class MyViewBinder: Java.Lang.Object, SimpleCursorAdapter.IViewBinder - { - private readonly Kp2aApp _app; - public MyViewBinder(Kp2aApp app) - { - _app = app; - } + class MyCursorAdapter: CursorAdapter + { + private LayoutInflater cursorInflater; + private readonly FileSelectActivity _activity; + private IKp2aApp _app; - public bool SetViewValue(View view, ICursor cursor, int columnIndex) - { - if (columnIndex == 1) - { - String path = cursor.GetString(columnIndex); - TextView textView = (TextView)view; - IOConnectionInfo ioc = new IOConnectionInfo {Path = path}; - var fileStorage = _app.GetFileStorage(ioc); - textView.Text = fileStorage.GetDisplayName(ioc); - textView.Tag = ioc.Path; - return true; - } + public MyCursorAdapter(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) + { + } - return false; - } - } - - private void FillData() + public MyCursorAdapter(FileSelectActivity activity, ICursor c, IKp2aApp app) : base(activity, c) + { + _activity = activity; + _app = app; + } + + public MyCursorAdapter(Context context, ICursor c, bool autoRequery) : base(context, c, autoRequery) + { + } + + public MyCursorAdapter(Context context, ICursor c, CursorAdapterFlags flags) : base(context, c, flags) + { + + } + + public override void BindView(View view, Context context, ICursor cursor) + { + + String path = cursor.GetString(1); + + TextView textView = view.FindViewById(Resource.Id.file_filename); + IOConnectionInfo ioc = new IOConnectionInfo { Path = path }; + var fileStorage = _app.GetFileStorage(ioc); + + String displayName = cursor.GetString(6); + if (string.IsNullOrEmpty(displayName)) + { + displayName = fileStorage.GetDisplayName(ioc); + + } + + textView.Text = displayName; + textView.Tag = ioc.Path; + + } + + public override View NewView(Context context, ICursor cursor, ViewGroup parent) + { + if (cursorInflater == null) + cursorInflater = (LayoutInflater)context.GetSystemService( Context.LayoutInflaterService); + View view = cursorInflater.Inflate(Resource.Layout.file_row, parent, false); + + view.FindViewById(Resource.Id.group_name_vdots).Click += (sender, args) => + { + Handler handler = new Handler(Looper.MainLooper); + handler.Post(() => + { + PopupMenu popupMenu = new PopupMenu(context, view.FindViewById(Resource.Id.group_name_vdots)); + + AccessManager.PreparePopup(popupMenu); + int remove = 0; + int edit = 1; + popupMenu.Menu.Add(0, remove, 0, context.GetString(Resource.String.remove_from_filelist)).SetIcon(Resource.Drawable.ic_menu_delete_grey); + + TextView textView = view.FindViewById(Resource.Id.file_filename); + + String filename = (string)textView.Tag; + IOConnectionInfo ioc = new IOConnectionInfo { Path = filename }; + if (FileSelectHelper.CanEditIoc(ioc)) + { + popupMenu.Menu.Add(0, edit, 0, context.GetString(Resource.String.edit)).SetIcon(Resource.Drawable.ic_menu_edit_grey); + } + + + popupMenu.MenuItemClick += delegate(object sender2, PopupMenu.MenuItemClickEventArgs args2) + { + if (args2.Item.ItemId == remove) + { + App.Kp2a.FileDbHelper.DeleteFile(filename); + + cursor.Requery(); + } + if (args2.Item.ItemId == edit) + { + var fsh = new FileSelectHelper(_activity, false, RequestCodeEditIoc); + fsh.OnOpen += (o, newConnectionInfo) => + { + _activity.EditFileEntry(filename, newConnectionInfo); + }; + fsh.PerformManualFileSelect(filename); + + } + }; + popupMenu.Show(); + }); + }; + + return view; + } + + + } + + private void EditFileEntry(string filename, IOConnectionInfo newConnectionInfo) + { + _dbHelper.CreateFile(newConnectionInfo, _dbHelper.GetKeyFileForFile(filename)); + _dbHelper.DeleteFile(filename); + + LaunchPasswordActivityForIoc(newConnectionInfo); + + } + + + private void FillData() { // Get all of the rows from the database and create the item list ICursor filesCursor = _dbHelper.FetchAllFiles(); @@ -241,15 +328,15 @@ namespace keepass2android // and an array of the fields we want to bind those fields to (in this // case just text1) int[] to = new[] { Resource.Id.file_filename }; - + /* // Now create a simple cursor adapter and set it to display SimpleCursorAdapter recentFilesAdapter = new SimpleCursorAdapter(this, Resource.Layout.file_row, filesCursor, from, to); recentFilesAdapter.ViewBinder = new MyViewBinder(App.Kp2a); - - FragmentManager.FindFragmentById(Resource.Id.recent_files).SetAdapter(recentFilesAdapter); + */ + FragmentManager.FindFragmentById(Resource.Id.recent_files).SetAdapter(new MyCursorAdapter(this, filesCursor,App.Kp2a)); } @@ -284,9 +371,9 @@ namespace keepass2android Finish(); } - public void OnListItemClick(ListView l, View v, int position, long id) { - - ICursor cursor = _dbHelper.FetchFile(id); + public void OnListItemClick(ListView l, View v, int position, long id) + { + ICursor cursor = _dbHelper.FetchFile(id); StartManagingCursor(cursor); IOConnectionInfo ioc = _dbHelper.CursorToIoc(cursor); @@ -328,8 +415,15 @@ namespace keepass2android PasswordActivity.SetIoConnectionFromIntent(ioc, data); LaunchPasswordActivityForIoc(ioc); } - - } + + if ((resultCode == Result.Ok) && (requestCode == RequestCodeEditIoc)) + { + string filename = Util.IntentToFilename(data, this); + + LaunchPasswordActivityForIoc(IOConnectionInfo.FromPath(filename)); + } + + } protected override void OnResume() { @@ -462,30 +556,7 @@ namespace keepass2android cursor.Requery(); } - public override void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo) - { - base.OnCreateContextMenu(menu, v, menuInfo); - menu.Add(0, Menu.First, 0, Resource.String.remove_from_filelist); - } - - public override bool OnContextItemSelected(IMenuItem item) - { - if (item.ItemId == Menu.First) - { - AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo)item.MenuInfo; - - TextView tv = (TextView)acmi.TargetView; - String filename = (string)tv.Tag; - App.Kp2a.FileDbHelper.DeleteFile(filename); - - RefreshList(); - - - return true; - } - return base.OnContextItemSelected(item); - - } + } } diff --git a/src/keepass2android/icons/DrawableFactory.cs b/src/keepass2android/icons/DrawableFactory.cs index 78085c50..ea82ccf2 100644 --- a/src/keepass2android/icons/DrawableFactory.cs +++ b/src/keepass2android/icons/DrawableFactory.cs @@ -101,7 +101,21 @@ private static Drawable _blank; { string packageName = PreferenceManager.GetDefaultSharedPreferences(Application.Context).GetString("IconSetKey", context.PackageName); - Resources res = context.PackageManager.GetResourcesForApplication(packageName); + Resources res; + try + { + res = context.PackageManager.GetResourcesForApplication(packageName); + } + catch (Exception) + { + //can happen after uninstalling icons + packageName = context.PackageName; + res = context.PackageManager.GetResourcesForApplication(packageName); + PreferenceManager.GetDefaultSharedPreferences(Application.Context) + .Edit() + .PutString("IconSetKey", packageName) + .Commit(); + } try { diff --git a/src/keepass2android/keepass2android.csproj b/src/keepass2android/keepass2android.csproj index a565f9a1..82ad4a7a 100644 --- a/src/keepass2android/keepass2android.csproj +++ b/src/keepass2android/keepass2android.csproj @@ -106,6 +106,7 @@ + @@ -162,6 +163,7 @@ + @@ -247,6 +249,7 @@ + @@ -631,23 +634,16 @@ - - - - - - - Designer @@ -816,6 +812,7 @@ + @@ -1592,12 +1589,6 @@ - - - - - - @@ -1765,18 +1756,85 @@ - - False - 25.1.1.0 - - - False - 25.1.1.0 - - - False - 25.1.1.0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/keepass2android/search/SearchProvider.cs b/src/keepass2android/search/SearchProvider.cs index 56c2b367..e638f740 100644 --- a/src/keepass2android/search/SearchProvider.cs +++ b/src/keepass2android/search/SearchProvider.cs @@ -121,8 +121,9 @@ namespace keepass2android.search var customIconUuid = new PwUuid(MemUtil.HexStringToByteArray(uri.GetQueryParameter(CustomIconUuidParameter))); var iconDrawable = _db.DrawableFactory.GetIconDrawable(App.Context, _db.KpDatabase, iconId, customIconUuid, false) as BitmapDrawable; - if (iconDrawable != null) - { + if ((iconDrawable != null) && (iconDrawable.Bitmap != null)) + + { var pipe = ParcelFileDescriptor.CreatePipe(); var outStream = new OutputStreamInvoker(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])); diff --git a/src/keepass2android/services/AutofillBase/StructureParser.cs b/src/keepass2android/services/AutofillBase/StructureParser.cs index 8f50388e..908baad1 100644 --- a/src/keepass2android/services/AutofillBase/StructureParser.cs +++ b/src/keepass2android/services/AutofillBase/StructureParser.cs @@ -7,6 +7,7 @@ using Android.Text; using Android.Util; using Android.Views; using Android.Views.Autofill; +using Android.Views.InputMethods; using keepass2android.services.AutofillBase.model; using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection; @@ -152,16 +153,36 @@ namespace keepass2android.services.AutofillBase || (f.Hint?.ToLowerInvariant().Contains("password") ?? false); } - private static bool IsPassword(AssistStructure.ViewNode f) + private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass) { - return + if (!InputTypes.MaskClass.HasFlag(inputTypeClass)) + throw new Exception("invalid inputTypeClas"); + return (((int)inputType) & (int)InputTypes.MaskClass) == (int) (inputTypeClass); + } + private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation) + { + if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation)) + throw new Exception("invalid inputTypeVariation"); + return (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation); + } + + private static bool IsPassword(AssistStructure.ViewNode f) + { + InputTypes inputType = f.InputType; + + return (!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) && (!f.Hint?.ToLowerInvariant().Contains("search") ?? true) && ( - f.InputType.HasFlag(InputTypes.TextVariationPassword) || - f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) || - f.InputType.HasFlag(InputTypes.TextVariationWebPassword) || - (f.HtmlInfo?.Attributes.Any(p => p.First.ToString() == "type" && p.Second.ToString() == "password") ?? false) + (IsInputTypeClass(inputType, InputTypes.ClassText) + && + ( + IsInputTypeVariation(inputType, InputTypes.TextVariationPassword) + || IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword) + || IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword) + ) + ) + || (f.HtmlInfo?.Attributes.Any(p => p.First.ToString() == "type" && p.Second.ToString() == "password") ?? false) ); } diff --git a/src/keepass2android/services/CopyToClipboardService.cs b/src/keepass2android/services/CopyToClipboardService.cs index 9761c3c3..205cbd21 100644 --- a/src/keepass2android/services/CopyToClipboardService.cs +++ b/src/keepass2android/services/CopyToClipboardService.cs @@ -24,6 +24,8 @@ using Java.Util; using Android.App; using Android.Content; +using Android.Graphics; +using Android.Graphics.Drawables; using Android.OS; using Android.Runtime; using Android.Widget; @@ -75,23 +77,23 @@ namespace keepass2android _hasKeyboard = true; } - public int CreateNotifications(string entryName) + public int CreateNotifications(string entryName, Bitmap entryIcon) { if (((int)Build.VERSION.SdkInt < 16) || (PreferenceManager.GetDefaultSharedPreferences(_ctx) .GetBoolean(_ctx.GetString(Resource.String.ShowSeparateNotifications_key), _ctx.Resources.GetBoolean(Resource.Boolean.ShowSeparateNotifications_default)))) { - return CreateSeparateNotifications(entryName); + return CreateSeparateNotifications(entryName, entryIcon); } else { - return CreateCombinedNotification(entryName); + return CreateCombinedNotification(entryName, entryIcon); } } - private int CreateCombinedNotification(string entryName) + private int CreateCombinedNotification(string entryName, Bitmap entryIcon) { if ((!_hasUsername) && (!_hasPassword) && (!_hasKeyboard)) return 0; @@ -100,12 +102,12 @@ namespace keepass2android if (_hasKeyboard) { notificationBuilder = GetNotificationBuilder(Intents.CheckKeyboard, Resource.String.available_through_keyboard, - Resource.Drawable.ic_notify_keyboard, entryName); + Resource.Drawable.ic_notify_keyboard, entryName, entryIcon); } else { notificationBuilder = GetNotificationBuilder(null, Resource.String.entry_is_available, Resource.Drawable.ic_launcher_gray, - entryName); + entryName, entryIcon); } //add action buttons to base notification: @@ -127,14 +129,14 @@ namespace keepass2android return 1; } - private int CreateSeparateNotifications(string entryName) + private int CreateSeparateNotifications(string entryName, Bitmap entryIcon) { int numNotifications = 0; if (_hasPassword) { // only show notification if password is available Notification password = GetNotification(Intents.CopyPassword, Resource.String.copy_password, - Resource.Drawable.ic_action_password, entryName); + Resource.Drawable.ic_action_password, entryName, entryIcon); numNotifications++; password.DeleteIntent = CreateDeleteIntent(NotifyPassword); _notificationManager.Notify(NotifyPassword, password); @@ -143,7 +145,7 @@ namespace keepass2android { // only show notification if username is available Notification username = GetNotification(Intents.CopyUsername, Resource.String.copy_username, - Resource.Drawable.ic_action_username, entryName); + Resource.Drawable.ic_action_username, entryName, entryIcon); username.DeleteIntent = CreateDeleteIntent(NotifyUsername); _notificationManager.Notify(NotifyUsername, username); numNotifications++; @@ -152,7 +154,7 @@ namespace keepass2android { // only show notification if username is available Notification keyboard = GetNotification(Intents.CheckKeyboard, Resource.String.available_through_keyboard, - Resource.Drawable.ic_notify_keyboard, entryName); + Resource.Drawable.ic_notify_keyboard, entryName, entryIcon); keyboard.DeleteIntent = CreateDeleteIntent(NotifyKeyboard); _notificationManager.Notify(NotifyKeyboard, keyboard); numNotifications++; @@ -173,14 +175,14 @@ namespace keepass2android } - private Notification GetNotification(String intentText, int descResId, int drawableResId, String entryName) + private Notification GetNotification(string intentText, int descResId, int drawableResId, string entryName, Bitmap entryIcon) { - var builder = GetNotificationBuilder(intentText, descResId, drawableResId, entryName); + var builder = GetNotificationBuilder(intentText, descResId, drawableResId, entryName, entryIcon); return builder.Build(); } - private NotificationCompat.Builder GetNotificationBuilder(string intentText, int descResId, int drawableResId, string entryName) + private NotificationCompat.Builder GetNotificationBuilder(string intentText, int descResId, int drawableResId, string entryName, Bitmap entryIcon) { String desc = _ctx.GetString(descResId); @@ -198,22 +200,24 @@ namespace keepass2android pending = GetPendingIntent(intentText, descResId); } - var builder = new NotificationCompat.Builder(_ctx); + var builder = new NotificationCompat.Builder(_ctx, App.NotificationChannelIdEntry); builder.SetSmallIcon(drawableResId) .SetContentText(desc) .SetContentTitle(entryName) - .SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis()) + .SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis()) .SetTicker(entryName + ": " + desc) .SetVisibility((int)Android.App.NotificationVisibility.Secret) .SetContentIntent(pending); + if (entryIcon != null) + builder.SetLargeIcon(entryIcon); return builder; } private PendingIntent GetPendingIntent(string intentText, int descResId) { PendingIntent pending; - Intent intent = new Intent(intentText); - intent.SetPackage(_ctx.PackageName); + Intent intent = new Intent(_ctx, typeof(CopyToClipboardBroadcastReceiver)); + intent.SetAction(intentText); pending = PendingIntent.GetBroadcast(_ctx, descResId, intent, PendingIntentFlags.CancelCurrent); return pending; } @@ -388,13 +392,29 @@ namespace keepass2android private const string ActionNotificationCancelled = "notification_cancelled"; - public void DisplayAccessNotifications(PwEntryOutput entry, bool closeAfterCreate, string searchUrl) + + + + public void DisplayAccessNotifications(PwEntryOutput entry, bool closeAfterCreate, string searchUrl) { var hadKeyboardData = ClearNotifications(); String entryName = entry.OutputStrings.ReadSafe(PwDefs.TitleField); - ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); + var bmp = Util.DrawableToBitmap(App.Kp2a.GetDb().DrawableFactory.GetIconDrawable(this, + App.Kp2a.GetDb().KpDatabase, entry.Entry.IconId, entry.Entry.CustomIconUuid, false)); + + + if (!(((entry.Entry.CustomIconUuid != null) && (!entry.Entry.CustomIconUuid.Equals(PwUuid.Zero)))) + && PreferenceManager.GetDefaultSharedPreferences(this).GetString("IconSetKey", PackageName) == PackageName) + { + Color drawingColor = new Color(189, 189, 189); + bmp = Util.ChangeImageColor(bmp, drawingColor); + } + + Bitmap entryIcon = Util.MakeLargeIcon(bmp, this); + + ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); var notBuilder = new PasswordAccessNotificationBuilder(this, _notificationManager); if (prefs.GetBoolean(GetString(Resource.String.CopyToClipboardNotification_key), Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default))) { @@ -447,19 +467,14 @@ namespace keepass2android { ClearKeyboard(true); //this clears again and then (this is the point) broadcasts that we no longer have keyboard data } - _numElementsToWaitFor = notBuilder.CreateNotifications(entryName); + _numElementsToWaitFor = notBuilder.CreateNotifications(entryName, entryIcon); if (_numElementsToWaitFor == 0) { StopSelf(); return; } - - IntentFilter filter = new IntentFilter(); - filter.AddAction(Intents.CopyUsername); - filter.AddAction(Intents.CopyPassword); - filter.AddAction(Intents.CheckKeyboard); - + //register receiver to get notified when notifications are discarded in which case we can shutdown the service _notificationDeletedBroadcastReceiver = new NotificationDeletedBroadcastReceiver(this); IntentFilter deletefilter = new IntentFilter(); @@ -632,21 +647,38 @@ namespace keepass2android public override void Run() { String currentClip = Util.GetClipboard(_service); - _handler.Post(() => _service.OnWaitElementDeleted(ClearClipboard)); + DoPostClear(); if (currentClip.Equals(_clearText)) { - Util.CopyToClipboard(_service, ""); - _handler.Post(() => - { - string message = _service.GetString(Resource.String.ClearClipboard) + " " - + _service.GetString(Resource.String.ClearClipboardWarning); - Android.Util.Log.Debug("KP2A", message); - Toast.MakeText(_service, - message, - ToastLength.Long).Show(); - }); + Util.CopyToClipboard(_service, ""); + DoPostWarn(); } } + + private void DoPostWarn() + { + _handler.Post(ShowClipboardWarning); + } + + private void DoPostClear() + { + _handler.Post(DoClearClipboard); + } + + private void DoClearClipboard() + { + _service.OnWaitElementDeleted(CopyToClipboardService.ClearClipboard); + } + + private void ShowClipboardWarning() + { + string message = _service.GetString(Resource.String.ClearClipboard) + " " + + _service.GetString(Resource.String.ClearClipboardWarning); + Android.Util.Log.Debug("KP2A", message); + Toast.MakeText(_service, + message, + ToastLength.Long).Show(); + } } @@ -757,7 +789,6 @@ namespace keepass2android } [BroadcastReceiver(Permission = "keepass2android." + AppNames.PackagePart + ".permission.CopyToClipboard")] - [IntentFilter(new[] { Intents.CopyUsername, Intents.CopyPassword, Intents.CheckKeyboard })] class CopyToClipboardBroadcastReceiver : BroadcastReceiver { public CopyToClipboardBroadcastReceiver(IntPtr javaReference, JniHandleOwnership transfer) diff --git a/src/keepass2android/services/OngoingNotificationsService.cs b/src/keepass2android/services/OngoingNotificationsService.cs index 17925a67..dfda57e0 100644 --- a/src/keepass2android/services/OngoingNotificationsService.cs +++ b/src/keepass2android/services/OngoingNotificationsService.cs @@ -48,7 +48,7 @@ namespace keepass2android public override void OnCreate() { base.OnCreate(); - + _screenOffReceiver = new ScreenOffReceiver(); IntentFilter filter = new IntentFilter(); filter.AddAction(Intent.ActionScreenOff); @@ -65,11 +65,11 @@ namespace keepass2android // Set the icon to reflect the current state if (App.Kp2a.DatabaseIsUnlocked) { - // Clear current foreground status and QuickUnlock icon - StopForeground(true); + // Clear QuickUnlock icon + notificationManager.Cancel(QuickUnlockId); - //use foreground again to let the app not be killed too easily. - StartForeground(UnlockedWarningId, GetUnlockedNotification()); + //use foreground again to let the app not be killed too easily. + StartForeground(UnlockedWarningId, GetUnlockedNotification()); } else { @@ -104,24 +104,30 @@ namespace keepass2android { base.OnDestroy(); - var notificationManager = (NotificationManager)GetSystemService(NotificationService); - notificationManager.Cancel(UnlockedWarningId); - // Quick Unlock notification should be removed automatically by the service (if present), as it was the foreground notification. + CancelNotifications(this); - Kp2aLog.Log("OngoingNotificationsService.OnDestroy"); + Kp2aLog.Log("OngoingNotificationsService.OnDestroy"); // If the service is killed, then lock the database immediately if (App.Kp2a.DatabaseIsUnlocked) { - App.Kp2a.LockDatabase(); + App.Kp2a.LockDatabase(false); } - //also remove any notifications of the app - notificationManager.CancelAll(); UnregisterReceiver(_screenOffReceiver); } - - public override IBinder OnBind(Intent intent) + + public static void CancelNotifications(Context ctx) + { + var notificationManager = (NotificationManager) ctx.GetSystemService(NotificationService); + notificationManager.Cancel(UnlockedWarningId); + // Quick Unlock notification should be removed automatically by the service (if present), as it was the foreground notification. + + //also remove any notifications of the app + notificationManager.CancelAll(); + } + + public override IBinder OnBind(Intent intent) { return null; } @@ -139,7 +145,7 @@ namespace keepass2android grayIconResouceId = Resource.Drawable.transparent; } NotificationCompat.Builder builder = - new NotificationCompat.Builder(this) + new NotificationCompat.Builder(this, App.NotificationChannelIdQuicklocked) .SetSmallIcon(grayIconResouceId) .SetLargeIcon(MakeLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.NotificationLockedIcon))) .SetVisibility((int)Android.App.NotificationVisibility.Secret) @@ -163,7 +169,7 @@ namespace keepass2android builder.SetContentIntent(GetSwitchToAppPendingIntent()); // Additional action to allow locking the database builder.AddAction(Android.Resource.Drawable.IcLockLock, GetString(Resource.String.QuickUnlock_lockButton), - PendingIntent.GetBroadcast(this, 0, new Intent(Intents.CloseDatabase), PendingIntentFlags.UpdateCurrent)); + PendingIntent.GetBroadcast(this, 0, new Intent(this, typeof(ApplicationBroadcastReceiver)).SetAction(Intents.CloseDatabase), PendingIntentFlags.UpdateCurrent)); return builder.Build(); @@ -171,9 +177,7 @@ namespace keepass2android private Bitmap MakeLargeIcon(Bitmap unscaled) { - int height = (int)(0.9*Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconHeight)); - int width = (int)(0.9*Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconWidth)); - return Bitmap.CreateScaledBitmap(unscaled, width, height, true); + return Util.MakeLargeIcon(unscaled, this); } #endregion @@ -183,7 +187,7 @@ namespace keepass2android private Notification GetUnlockedNotification() { NotificationCompat.Builder builder = - new NotificationCompat.Builder(this) + new NotificationCompat.Builder(this, App.NotificationChannelIdUnlocked) .SetOngoing(true) .SetSmallIcon(Resource.Drawable.ic_notify) .SetLargeIcon(MakeLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.NotificationUnlockedIcon))) @@ -208,7 +212,7 @@ namespace keepass2android // Default action is to show Kp2A builder.SetContentIntent(GetSwitchToAppPendingIntent()); // Additional action to allow locking the database - builder.AddAction(Resource.Drawable.ic_action_lock, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent)); + builder.AddAction(Resource.Drawable.ic_action_lock, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(this, typeof(ApplicationBroadcastReceiver)).SetAction(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent)); return builder.Build(); } diff --git a/src/keepass2android/settings/DatabaseSettingsActivity.cs b/src/keepass2android/settings/DatabaseSettingsActivity.cs index ce9d6fd2..5b7f4094 100644 --- a/src/keepass2android/settings/DatabaseSettingsActivity.cs +++ b/src/keepass2android/settings/DatabaseSettingsActivity.cs @@ -364,9 +364,16 @@ namespace keepass2android // Re-use the change handlers for the application settings FindPreference(GetString(Resource.String.keyfile_key)).PreferenceChange += OnRememberKeyFileHistoryChanged; - FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += OnShowUnlockedNotificationChanged; - FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += OnShowUnlockedNotificationChanged; - FindPreference(GetString(Resource.String.DebugLog_key)).PreferenceChange += OnDebugLogChanged; + var unlockedNotificationPref = FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)); + unlockedNotificationPref.PreferenceChange += OnShowUnlockedNotificationChanged; + if ((int)Build.VERSION.SdkInt >= 26) + { + //use system notification channels to control notification visibility + unlockedNotificationPref.Parent.RemovePreference(unlockedNotificationPref); + } + + + FindPreference(GetString(Resource.String.DebugLog_key)).PreferenceChange += OnDebugLogChanged; FindPreference(GetString(Resource.String.DebugLog_send_key)).PreferenceClick += OnSendDebug; UpdateAutofillPref(); @@ -448,10 +455,16 @@ namespace keepass2android Preference hideQuickUnlockTranspIconPref = FindPreference(GetString(Resource.String.QuickUnlockIconHidden_key)); Preference hideQuickUnlockIconPref = FindPreference(GetString(Resource.String.QuickUnlockIconHidden16_key)); var quickUnlockScreen = ((PreferenceScreen)FindPreference(GetString(Resource.String.QuickUnlock_prefs_key))); - if ((int)Android.OS.Build.VERSION.SdkInt >= 16) + if ((int)Android.OS.Build.VERSION.SdkInt >= 26) + { + //use notification channels + quickUnlockScreen.RemovePreference(hideQuickUnlockTranspIconPref); + quickUnlockScreen.RemovePreference(hideQuickUnlockIconPref); + } + else if ((int)Android.OS.Build.VERSION.SdkInt >= 16) { quickUnlockScreen.RemovePreference(hideQuickUnlockTranspIconPref); - FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += (sender, args) => App.Kp2a.UpdateOngoingNotification(); + unlockedNotificationPref.PreferenceChange += (sender, args) => App.Kp2a.UpdateOngoingNotification(); hideQuickUnlockIconPref.PreferenceChange += delegate { App.Kp2a.UpdateOngoingNotification(); }; } else @@ -462,7 +475,7 @@ namespace keepass2android delegate { App.Kp2a.UpdateOngoingNotification(); }; ((PreferenceScreen)FindPreference(GetString(Resource.String.display_prefs_key))).RemovePreference( - FindPreference(GetString(Resource.String.ShowUnlockedNotification_key))); + unlockedNotificationPref); } } catch (Exception ex) @@ -852,7 +865,7 @@ namespace keepass2android { CompositeKey masterKey = App.Kp2a.GetDb().KpDatabase.MasterKey; var sourceIoc = ((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).Ioc; - var newIoc = ImportFileToInternalDirectory(sourceIoc); + var newIoc = IoUtil.ImportFileToInternalDirectory(sourceIoc, Activity, App.Kp2a); ((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).ResetIoc(newIoc); var keyfileString = IOConnectionInfo.SerializeToString(newIoc); App.Kp2a.StoreOpenedFileAsRecent(App.Kp2a.GetDb().Ioc, keyfileString); @@ -892,7 +905,9 @@ namespace keepass2android { //Import db/key file preferences: Preference importDb = FindPreference("import_db_prefs"); - if (!App.Kp2a.GetDb().Ioc.IsLocalFile()) + bool isLocalOrContent = + App.Kp2a.GetDb().Ioc.IsLocalFile() || App.Kp2a.GetDb().Ioc.Path.StartsWith("content://"); + if (!isLocalOrContent) { importDb.Summary = GetString(Resource.String.OnlyAvailableForLocalFiles); importDb.Enabled = false; @@ -919,7 +934,7 @@ namespace keepass2android try { var sourceIoc = App.Kp2a.GetDb().Ioc; - var newIoc = ImportFileToInternalDirectory(sourceIoc); + var newIoc = IoUtil.ImportFileToInternalDirectory(sourceIoc, Activity, App.Kp2a); return () => { var builder = new AlertDialog.Builder(Activity); @@ -953,32 +968,6 @@ namespace keepass2android } - private IOConnectionInfo ImportFileToInternalDirectory(IOConnectionInfo sourceIoc) - { - Java.IO.File internalDirectory = IoUtil.GetInternalDirectory(Activity); - string targetPath = UrlUtil.GetFileName(sourceIoc.Path); - targetPath = targetPath.Trim("|\\?*<\":>+[]/'".ToCharArray()); - if (targetPath == "") - targetPath = "imported"; - if (new File(internalDirectory, targetPath).Exists()) - { - int c = 1; - var ext = UrlUtil.GetExtension(targetPath); - var filenameWithoutExt = UrlUtil.StripExtension(targetPath); - do - { - c++; - targetPath = filenameWithoutExt + c; - if (!String.IsNullOrEmpty(ext)) - targetPath += "." + ext; - } while (new File(internalDirectory, targetPath).Exists()); - } - var targetIoc = IOConnectionInfo.FromPath(new File(internalDirectory, targetPath).CanonicalPath); - - IoUtil.Copy(targetIoc, sourceIoc, App.Kp2a); - return targetIoc; - } - private void SetAlgorithm(Database db, Preference algorithm) diff --git a/src/keepass2android/timeout/TimeoutHelper.cs b/src/keepass2android/timeout/TimeoutHelper.cs index 04ca4425..c49e6383 100644 --- a/src/keepass2android/timeout/TimeoutHelper.cs +++ b/src/keepass2android/timeout/TimeoutHelper.cs @@ -35,7 +35,7 @@ namespace keepass2android private static PendingIntent BuildIntent(Context ctx) { - return PendingIntent.GetBroadcast(ctx, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent); + return PendingIntent.GetBroadcast(ctx, 0, new Intent(ctx, typeof(ApplicationBroadcastReceiver)).SetAction(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent); } public static void Start(Context ctx) diff --git a/src/keepass2android/views/GroupListItemView.cs b/src/keepass2android/views/GroupListItemView.cs index 8eaefbcf..57ab984f 100644 --- a/src/keepass2android/views/GroupListItemView.cs +++ b/src/keepass2android/views/GroupListItemView.cs @@ -20,6 +20,7 @@ namespace keepass2android.view : base(context) { _groupBaseActivity = context; + } public GroupListItemView(Context context, IAttributeSet attrs) diff --git a/src/keepass2android/views/PwEntryView.cs b/src/keepass2android/views/PwEntryView.cs index 81e40c75..930b692e 100644 --- a/src/keepass2android/views/PwEntryView.cs +++ b/src/keepass2android/views/PwEntryView.cs @@ -103,8 +103,11 @@ namespace keepass2android.view { _entry = pw; _pos = pos; - - ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon); + ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible; + ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible; + + + ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon); bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now; if (isExpired) { diff --git a/src/keepass2android/views/PwGroupView.cs b/src/keepass2android/views/PwGroupView.cs index 5feba08b..f94157b0 100644 --- a/src/keepass2android/views/PwGroupView.cs +++ b/src/keepass2android/views/PwGroupView.cs @@ -22,6 +22,7 @@ using Android.Runtime; using Android.Views; using Android.Widget; using KeePassLib; +using Object = Java.Lang.Object; namespace keepass2android.view { @@ -64,7 +65,10 @@ namespace keepass2android.view gv.FindViewById(Resource.Id.group_icon_bkg).Visibility = App.Kp2a.GetDb().DrawableFactory.IsWhiteIconSet ? ViewStates.Visible : ViewStates.Gone; - PopulateView(gv, pw); + gv.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible; + gv.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible; + + PopulateView(gv, pw); LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent); @@ -76,8 +80,11 @@ namespace keepass2android.view ImageView iv = (ImageView) gv.FindViewById(Resource.Id.icon); App.Kp2a.GetDb().DrawableFactory.AssignDrawableTo(iv, _groupBaseActivity, App.Kp2a.GetDb().KpDatabase, pw.IconId, pw.CustomIconUuid, true); - - _textview.Text = pw.Name; + gv.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible; + gv.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible; + + + _textview.Text = pw.Name; if (_defaultTextColor == null) _defaultTextColor = _textview.TextColors.DefaultColor; @@ -91,11 +98,15 @@ namespace keepass2android.view _textview.SetTextColor(new Color((int)_defaultTextColor)); _label.Text = _groupBaseActivity.GetString (Resource.String.group)+" - "; - uint numEntries = CountEntries (pw); - if (numEntries == 1) - _label.Text += Context.GetString (Resource.String.Entry_singular); - else - _label.Text += Context.GetString (Resource.String.Entry_plural, new Java.Lang.Object[] { numEntries }); + uint numEntries = CountEntries (pw); + if (numEntries == 1) + _label.Text += Context.GetString(Resource.String.Entry_singular); + else + { + Java.Lang.Object obj = (int)numEntries; + _label.Text += Context.GetString(Resource.String.Entry_plural, obj); + } + } uint CountEntries(PwGroup g) @@ -159,5 +170,26 @@ namespace keepass2android.view LaunchGroup(); } } + + + internal class JavaObjectAdapter : Java.Lang.Object + { + private readonly uint _value; + public JavaObjectAdapter(uint value) + { + _value = value; + } + + public JavaObjectAdapter(System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) + : base(handle, transfer) + { + + } + + public override string ToString() + { + return _value.ToString(); + } + } } diff --git a/src/netftpandroid b/src/netftpandroid index 040e8bbe..23df4cf1 160000 --- a/src/netftpandroid +++ b/src/netftpandroid @@ -1 +1 @@ -Subproject commit 040e8bbe564bd140203255e11c86c01c2f7c1892 +Subproject commit 23df4cf135857eb33f3656567d08ec228d96d4b5