Browse Source

Export all passwords capacity

isundil 4 years ago
parent
commit
58991fcde8

+ 3 - 0
.idea/gradle.xml

@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="testRunner" value="PLATFORM" />
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="modules">
@@ -13,6 +15,7 @@
           </set>
         </option>
         <option name="resolveModulePerSourceSet" value="false" />
+        <option name="useQualifiedModuleNames" value="true" />
       </GradleProjectSettings>
     </option>
   </component>

+ 1 - 1
.idea/misc.xml

@@ -29,7 +29,7 @@
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">

+ 90 - 7
app/src/main/java/info/knacki/pass/io/FileMigratoryUtils.java

@@ -2,8 +2,12 @@ package info.knacki.pass.io;
 
 import android.content.Context;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayDeque;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import info.knacki.gitdroid.callback.OnResponseListener;
 
@@ -50,8 +54,8 @@ public class FileMigratoryUtils {
         });
     }
 
-    public static void MigratePassword(Context ctx, File from, File to, FileInterfaceFactory.PasswordGetter passInput, OnResponseListener<Void> onDone, boolean removePrevious) {
-        MigratePassword(FileInterfaceFactory.GetFileInterface(ctx, passInput, from), FileInterfaceFactory.GetFileInterface(ctx, passInput, to), from.getAbsolutePath().equals(to.getAbsolutePath()) || !removePrevious ? onDone : new OnResponseListener<Void>() {
+    public static void MigratePassword(Context ctx, File from, IFileInterface to, FileInterfaceFactory.PasswordGetter passInput, OnResponseListener<Void> onDone, boolean removePrevious) {
+        MigratePassword(FileInterfaceFactory.GetFileInterface(ctx, passInput, from), to, !removePrevious ? onDone : new OnResponseListener<Void>() {
             @Override
             public void OnResponse(Void result) {
                 FileUtils.DeleteFile(from);
@@ -65,17 +69,27 @@ public class FileMigratoryUtils {
         });
     }
 
-    public static void MigratePasswordToPassword(final Context ctx, FileInterfaceFactory.PasswordGetter passwordGetter) {
+    public static void MigratePassword(Context ctx, File from, File to, FileInterfaceFactory.PasswordGetter passInput, OnResponseListener<Void> onDone, boolean removePrevious) {
+        MigratePassword(ctx, from, FileInterfaceFactory.GetFileInterface(ctx, passInput, to), passInput, onDone, removePrevious);
+    }
+
+    private interface OutputGetter {
+        IFileInterface GetOutput(File input);
+    }
+
+    private static void MigratePasswords(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final OutputGetter outputGetter, ArrayDeque<File> inputFiles, OnResponseListener<Void> onDone) {
         final ArrayDeque<String> oldPasswords = new ArrayDeque<>();
-        final ArrayDeque<File> oldPasswordFiles = ListPasswords(ctx, (file) -> file.getName().endsWith(PasswordFileInterface.PASSWORD_SUFFIX));
 
         final Runnable nextFile = new Runnable() {
             private void NextFile() {
-                if (oldPasswordFiles.isEmpty())
+                if (inputFiles.isEmpty()) {
+                    if (onDone != null)
+                        onDone.OnResponse(null);
                     return;
-                final File currentFile = oldPasswordFiles.poll();
+                }
+                final File currentFile = inputFiles.poll();
                 final ArrayDeque<String> currentPasswordToTry = new ArrayDeque<>(oldPasswords);
-                MigratePassword(ctx, currentFile, currentFile, new FileInterfaceFactory.PasswordGetter() {
+                MigratePassword(ctx, currentFile, outputGetter.GetOutput(currentFile), new FileInterfaceFactory.PasswordGetter() {
                     @Override
                     public FileInterfaceFactory.PasswordGetter SetFile(File file) { return this; }
 
@@ -117,4 +131,73 @@ public class FileMigratoryUtils {
         };
         nextFile.run();
     }
+
+    private static String GetFileName(Context ctx, File f) {
+        String rootDir = PathUtils.GetPassDir(ctx);
+        String absPath = f.getAbsolutePath();
+        return absPath.substring(rootDir.length());
+    }
+
+    public static void ExportAllPasswords(final Context ctx, FileInterfaceFactory.PasswordGetter passwordGetter, OnResponseListener<byte[]> onDone) {
+        // Get output password
+        passwordGetter.GetPassword(new FileInterfaceFactory.OnPasswordEnteredListener() {
+            @Override
+            public boolean OnResponse(String outputPassword) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                ZipOutputStream compressedStream = new ZipOutputStream(out);
+
+                MigratePasswords(ctx, passwordGetter, input -> new IFileInterface() {
+                    @Override
+                    public void ReadFile(OnResponseListener<String> resp) {
+                        resp.OnError("Unsupported Operation", new UnsupportedOperationException("ReadFile"));
+                    }
+
+                    @Override
+                    public void WriteFile(String content, OnResponseListener<Void> resp) throws InvalidPasswordException {
+                        try {
+                            ZipEntry compressedFile = new ZipEntry(GetFileName(ctx, input));
+                            compressedStream.putNextEntry(compressedFile);
+                            PasswordFileInterface.CryptData(compressedStream, content.getBytes(), outputPassword.toCharArray());
+                            compressedStream.closeEntry();
+                            resp.OnResponse(null);
+                        }
+                        catch (Throwable e) {
+                            resp.OnError("Failed to write to output", e);
+                        }
+                    }
+
+                    @Override
+                    public String GetMethodName() {
+                        return "";
+                    }
+                }, ListPasswords(ctx, f -> true), new OnResponseListener<Void>() {
+                    @Override
+                    public void OnResponse(Void result) {
+                        try {
+                            compressedStream.close();
+                        }
+                        catch (IOException e) {
+                            onDone.OnError("Cannot write to output", e);
+                            return;
+                        }
+                        onDone.OnResponse(out.toByteArray());
+                    }
+
+                    @Override
+                    public void OnError(String msg, Throwable e) {
+                    }
+                });
+                return true;
+            }
+
+            @Override
+            public void OnError(String msg, Throwable e) {
+
+            }
+        });
+    }
+
+    public static void MigratePasswordToPassword(Context ctx, FileInterfaceFactory.PasswordGetter passwordGetter) {
+        MigratePasswords(ctx, passwordGetter, input -> FileInterfaceFactory.GetFileInterface(ctx, passwordGetter, input), ListPasswords(ctx, (file) -> file.getName().endsWith(PasswordFileInterface.PASSWORD_SUFFIX)), null);
+    }
 }

+ 3 - 3
app/src/main/java/info/knacki/pass/io/PasswordFileInterface.java

@@ -120,7 +120,7 @@ class PasswordFileInterface implements IFileInterface {
         });
     }
 
-    private void CryptFile(OutputStream fileOutStream, byte[] data) throws IOException, PGPException {
+    public static void CryptData(OutputStream fileOutStream, byte[] data, char[] password) throws IOException, PGPException {
         ByteArrayOutputStream compressedDataStream = new ByteArrayOutputStream();
         PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.UNCOMPRESSED);
         OutputStream cos = comData.open(compressedDataStream);
@@ -141,7 +141,7 @@ class PasswordFileInterface implements IFileInterface {
         OutputStream out = new ArmoredOutputStream(fileOutStream);
 
         PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedDataGenerator.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()).setProvider("BC"));
-        encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(fPassword).setProvider("BC"));
+        encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(password).setProvider("BC"));
         OutputStream encOut = encGen.open(fileOutStream, compressedData.length);
 
         encOut.write(compressedData);
@@ -186,7 +186,7 @@ class PasswordFileInterface implements IFileInterface {
         Security.addProvider(new BouncyCastleProvider());
 
         try {
-            CryptFile(new FileOutputStream(fFile), CharsetHelper.StringToByteArray(content));
+            CryptData(new FileOutputStream(fFile), CharsetHelper.StringToByteArray(content), fPassword);
         }
         catch (Throwable e) {
             resp.OnError(e.getMessage(), e);

+ 42 - 2
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -59,6 +59,8 @@ import info.knacki.pass.ui.alertPrompt.views.TextView;
 import info.knacki.pass.ui.alertPrompt.views.UsernameAndEmail;
 import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
 /**
  * A {@link PreferenceActivity} that presents a set of application settings. On
  * handset devices, settings are presented as a single list. On tablets,
@@ -386,6 +388,44 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 GitPullActivity.StartSyncActivity(getActivity());
                 return true;
             });
+
+            findPreference(getResources().getString(R.string.id_vcs_export)).setOnPreferenceClickListener(preference -> {
+                FileMigratoryUtils.ExportAllPasswords(getActivity().getApplicationContext(), PasswordPickerFactory.GetPasswordPicker(getActivity()), new OnResponseListener<byte[]>() {
+                    @Override
+                    public void OnResponse(final byte[] result) {
+                        final File outFile = new File(getActivity().getCacheDir().getAbsolutePath() + "/passwords.zip");
+                        try {
+                            FileUtils.Touch(outFile);
+                            FileOutputStream fileOut = new FileOutputStream(outFile);
+                            fileOut.write(result);
+                            fileOut.close();
+                            outFile.deleteOnExit();
+                        } catch (IOException e) {
+                            Toast.makeText(getActivity(), "Cannot prepare key for sharing: " + e.getMessage(), Toast.LENGTH_LONG).show();
+                            log.log(Level.SEVERE, "Cannot write key to cache", e);
+                            return;
+                        }
+                        final Uri fileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", outFile);
+                        final Intent shareIntent = new Intent(Intent.ACTION_SEND)
+                                .putExtra(Intent.EXTRA_STREAM, fileUri)
+                                .setType("application/zip")
+                                .addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+
+                        for (ResolveInfo resolveInfo : getActivity().getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY))
+                            getActivity().grantUriPermission(resolveInfo.activityInfo.packageName, fileUri, FLAG_GRANT_READ_URI_PERMISSION);
+                        startActivity(Intent.createChooser(shareIntent, "Export passwords"));
+                    }
+
+                    @Override
+                    public void OnError(String msg, Throwable e) {
+
+                    }
+                });
+                return true;
+            });
+            findPreference(getResources().getString(R.string.id_vcs_import)).setOnPreferenceClickListener(preference -> {
+                return true;
+            });
         }
 
         @Override
@@ -692,10 +732,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     .setType("application/octet-stream")
                     .getIntent()
                     .setData(fileUri)
-                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    .addFlags(FLAG_GRANT_READ_URI_PERMISSION);
 
             for (ResolveInfo resolveInfo : getActivity().getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY))
-                getActivity().grantUriPermission(resolveInfo.activityInfo.packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                getActivity().grantUriPermission(resolveInfo.activityInfo.packageName, fileUri, FLAG_GRANT_READ_URI_PERMISSION);
             startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.id_gpg_export)));
         }
 

+ 2 - 0
app/src/main/res/values-fr/lang.xml

@@ -34,6 +34,8 @@
     <string name="pref_vcs_git_private_key_change">Changer sa clée privée</string>
     <string name="pref_vcs_branch_title">Branche GIT</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_vcs_export">Export</string>
+    <string name="pref_vcs_import">Import</string>
     <string name="pref_vcs_git_auth_category">Authentication</string>
     <string name="pref_vcs_git_commit_info_category">Informations de commit</string>
     <string name="pref_vcs_git_username_title">Nom d\'utilisateur</string>

+ 2 - 0
app/src/main/res/values/lang.xml

@@ -40,6 +40,8 @@
     <string name="pref_vcs_git_private_key_change">Change RSA private key</string>
     <string name="pref_vcs_branch_title">Git branch</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_vcs_export">Export</string>
+    <string name="pref_vcs_import">Import</string>
     <string name="pref_vcs_git_auth_category">Authentication</string>
     <string name="pref_vcs_git_commit_info_category">Commit information</string>
     <string name="pref_vcs_git_username_title">Username</string>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -13,6 +13,8 @@
     <string name="id_vcs_git_private_key" translatable="false">id_vcs_git_private_key</string>
     <string name="id_vcs_git_branches" translatable="false">id_vcs_git_branches</string>
     <string name="id_vcs_git_pull" translatable="false">id_vcs_git_pull</string>
+    <string name="id_vcs_export" translatable="false">id_vcs_export</string>
+    <string name="id_vcs_import" translatable="false">id_vcs_import</string>
     <string name="id_vcs_git_ci_username" translatable="false">id_vcs_git_ci_username</string>
     <string name="id_vcs_git_ci_user_email" translatable="false">id_vcs_git_ci_user_email</string>
     <string name="id_vcs_git_commit_info_category" translatable="false">id_vcs_git_commit_info_category</string>

+ 2 - 0
app/src/main/res/xml/pref_vcs.xml

@@ -47,4 +47,6 @@
         android:title="@string/pref_vcs_git_user_email_title"/>
 
     <Preference android:title="@string/pref_vcs_git_pull" android:key="@string/id_vcs_git_pull" />
+    <Preference android:title="@string/pref_vcs_export" android:key="@string/id_vcs_export" />
+    <Preference android:title="@string/pref_vcs_import" android:key="@string/id_vcs_import" />
 </PreferenceScreen>

+ 1 - 1
build.gradle

@@ -7,7 +7,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.1'
+        classpath 'com.android.tools.build:gradle:4.1.3'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }

+ 2 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,6 @@
+#Thu Mar 25 21:20:09 CET 2021
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip