Explorar o código

[add] read gpg crypted data

isundil %!s(int64=7) %!d(string=hai) anos
pai
achega
80857370f2
Modificáronse 33 ficheiros con 483 adicións e 269 borrados
  1. 1 0
      app/build.gradle
  2. 4 0
      app/src/main/AndroidManifest.xml
  3. 1 136
      app/src/main/cpp/native-lib.cpp
  4. 1 1
      app/src/main/java/info/knacki/pass/bridge/ZLib.java
  5. 6 6
      app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java
  6. 0 2
      app/src/main/java/info/knacki/pass/git/GitInterfaceFactory.java
  7. 1 1
      app/src/main/java/info/knacki/pass/git/GitLocal.java
  8. 10 7
      app/src/main/java/info/knacki/pass/git/entities/GitCommit.java
  9. 2 3
      app/src/main/java/info/knacki/pass/git/entities/GitObject.java
  10. 2 2
      app/src/main/java/info/knacki/pass/git/entities/GitRef.java
  11. 18 13
      app/src/main/java/info/knacki/pass/input/InputService.java
  12. 8 2
      app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java
  13. 4 3
      app/src/main/java/info/knacki/pass/io/FingerprintFileInterface.java
  14. 203 6
      app/src/main/java/info/knacki/pass/io/GPGFileInterface.java
  15. 3 2
      app/src/main/java/info/knacki/pass/io/IFileInterface.java
  16. 4 3
      app/src/main/java/info/knacki/pass/io/PasswordFileInterface.java
  17. 16 10
      app/src/main/java/info/knacki/pass/io/RawFileInterface.java
  18. 28 21
      app/src/main/java/info/knacki/pass/settings/SettingsManager.java
  19. 46 12
      app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java
  20. 8 2
      app/src/main/java/info/knacki/pass/ui/AlertText.java
  21. 31 13
      app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java
  22. 2 5
      app/src/main/java/info/knacki/pass/ui/GitPullActivity.java
  23. 7 5
      app/src/main/java/info/knacki/pass/ui/MainActivity.java
  24. 0 1
      app/src/main/java/info/knacki/pass/ui/PasswordListView.java
  25. 44 0
      app/src/main/java/info/knacki/pass/ui/PasswordPicker.java
  26. 5 9
      app/src/main/java/info/knacki/pass/ui/PasswordView.java
  27. 2 2
      app/src/main/res/layout/activity_git_pull.xml
  28. 1 1
      app/src/main/res/layout/activity_pass_edit.xml
  29. 7 1
      app/src/main/res/values-fr/lang.xml
  30. 6 0
      app/src/main/res/values/lang.xml
  31. 1 0
      app/src/main/res/values/strings.xml
  32. 4 0
      app/src/main/res/xml/pref_encryption.xml
  33. 7 0
      app/src/main/res/xml/pref_gpg.xml

+ 1 - 0
app/build.gradle

@@ -40,4 +40,5 @@ dependencies {
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
     implementation 'commons-http:commons-http:1.1'
+    implementation 'org.bouncycastle:bcpg-jdk16:1.46'
 }

+ 4 - 0
app/src/main/AndroidManifest.xml

@@ -2,12 +2,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="info.knacki.pass">
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
     <application
         android:allowBackup="true"
+        android:fullBackupContent="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
+        android:extractNativeLibs="false"
         android:theme="@style/AppTheme">
         <service
             android:name=".input.InputService"

+ 1 - 136
app/src/main/cpp/native-lib.cpp

@@ -1,139 +1,4 @@
 #include <jni.h>
-#include <stdlib.h>
-#include <string.h>
-#include <deque>
-#include <zlib.h>
 
-#define CHUNK 1024
-
-/*
-int deflate(char* str_src, char* str_dest, int level)
-{
-    FILE *source;
-    FILE *dest;
-    int ret, flush;
-    unsigned have;
-    z_stream strm;
-    char in[CHUNK];
-    char out[CHUNK];
-
-    source = fopen(str_src, "rb");
-    dest   = fopen(str_dest, "wb");
-
-    strm.zalloc = Z_NULL;
-    strm.zfree  = Z_NULL;
-    strm.opaque = Z_NULL;
-
-    ret = deflateInit(&strm, level);
-
-    if (ret != Z_OK)
-        return ret;
-
-    / * compress until end of file * /
-
-    do
-    {
-        strm.avail_in = fread(in, 1, CHUNK, source);
-
-        if (ferror(source))
-        {
-            deflateEnd(&strm);
-            fclose(source);
-            fclose(dest);
-            return Z_ERRNO;
-        }
-
-        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
-
-        strm.next_in = (Bytef*)in;
-
-        do
-        {
-            strm.avail_out = CHUNK;
-            strm.next_out  = (Bytef*) out;
-
-            ret = deflate(&strm, flush);    / * no bad return value * /
-
-            have = CHUNK - strm.avail_out;
-
-            if (fwrite(out, 1, have, dest) != have || ferror(dest))
-            {
-                deflateEnd(&strm);
-                fclose(source);
-                fclose(dest);
-                return Z_ERRNO;
-            }
-        }
-        while (strm.avail_out == 0);
-
-        / * done when last data in file processed * /
-    }
-    while (flush != Z_FINISH);
-
-    / * clean up and return * /
-
-    deflateEnd(&strm);
-
-    fclose(source);
-    fclose(dest);
-
-    return Z_OK;
-}
-*/
-
-jcharArray createCharArray(JNIEnv *env, std::deque<char> deque) {
-    jcharArray result = env->NewCharArray(deque.size());
-    jchar *arr = (jchar *)malloc(sizeof(*arr) * deque.size());
-    for (int i =0; i < deque.size(); ++i)
-        arr[i] = (jchar) deque.at(i);
-    env->SetCharArrayRegion(result, 0, deque.size(), arr);
-    free(arr);
-    return result;
-}
-
-jcharArray inflate(JNIEnv *env, char* in_buf, int bufLen)
-{
-    z_stream strm;
-    char out[CHUNK];
-    std::deque<char> ss;
-
-    strm.zalloc   = Z_NULL;
-    strm.zfree    = Z_NULL;
-    strm.opaque   = Z_NULL;
-    strm.next_in  = Z_NULL;
-    strm.avail_in = 0;
-
-    if (inflateInit2(&strm, -MAX_WBITS) != Z_OK)
-        return nullptr;
-
-    strm.avail_in = (unsigned) bufLen;
-    strm.next_in = (Bytef*) in_buf;
-    do
-    {
-        strm.avail_out = CHUNK;
-        strm.next_out  = (Bytef*) out;
-
-        int ret = inflate(&strm, Z_NO_FLUSH);
-        const unsigned have = CHUNK - strm.avail_out;
-        for (int i =0; i < have; ++i)
-            ss.emplace_back(out[i]);
-        if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
-            break;
-    }
-    while (strm.avail_out == 0);
-
-    inflateEnd(&strm);
-    return createCharArray(env, ss);
-}
-
-extern "C" JNIEXPORT jcharArray JNICALL Java_info_knacki_pass_bridge_ZLib_Inflate(JNIEnv *env, jobject, jcharArray input, jint inputLength) {
-    jchar *inputJchar = (jchar*) malloc(sizeof(*inputJchar) *((int)inputLength));
-    char *inputNative = (char*) malloc(sizeof(*inputNative) *((int)inputLength));
-    env->GetCharArrayRegion(input, 0, inputLength, inputJchar);
-    for (int i =0; i < inputLength; ++i)
-        inputNative[i] = (char) inputJchar[i];
-    free(inputJchar);
-    jcharArray  resultJchararray = inflate(env, inputNative, inputLength);
-    free(inputNative);
-    return resultJchararray;
+extern "C" JNIEXPORT void JNICALL Java_info_knacki_pass_bridge_ZLib_Inflate(JNIEnv *env, jobject) {
 }

+ 1 - 1
app/src/main/java/info/knacki/pass/bridge/ZLib.java

@@ -1,5 +1,5 @@
 package info.knacki.pass.bridge;
 
 public class ZLib {
-    public static native char[] Inflate(char []str, int length);
+    public static native void Inflate();
 }

+ 6 - 6
app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java

@@ -15,12 +15,12 @@ import info.knacki.pass.R;
 import info.knacki.pass.generator.PasswordGenerator;
 
 public class PasswordGeneratorWizard extends LinearLayout implements PasswordGenerator.PasswordGeneratorParams {
-    public AppCompatSpinner fDifficulty;
-    public AppCompatCheckBox fAlpha;
-    public AppCompatCheckBox fCapitalize;
-    public AppCompatCheckBox fNum;
-    public AppCompatCheckBox fSpecial;
-    public NumberPicker fLength;
+    public final AppCompatSpinner fDifficulty;
+    public final AppCompatCheckBox fAlpha;
+    public final AppCompatCheckBox fCapitalize;
+    public final AppCompatCheckBox fNum;
+    public final AppCompatCheckBox fSpecial;
+    public final NumberPicker fLength;
 
     public static final int PRESET_NUM =0;
     public static final int PRESET_WEAK =1;

+ 0 - 2
app/src/main/java/info/knacki/pass/git/GitInterfaceFactory.java

@@ -2,8 +2,6 @@ package info.knacki.pass.git;
 
 import android.content.Context;
 
-import java.nio.channels.UnsupportedAddressTypeException;
-
 import info.knacki.pass.settings.SettingsManager;
 
 public class GitInterfaceFactory {

+ 1 - 1
app/src/main/java/info/knacki/pass/git/GitLocal.java

@@ -21,7 +21,7 @@ public class GitLocal {
             fMd5 = md5;
         }
     }
-    Map<String, ControlSum> cache;
+    final Map<String, ControlSum> cache;
 
     public GitLocal(File file) {
         cache = new HashMap<>();

+ 10 - 7
app/src/main/java/info/knacki/pass/git/entities/GitCommit.java

@@ -1,22 +1,17 @@
 package info.knacki.pass.git.entities;
 
 public class GitCommit {
-    private String fCommit;
     private String fTree;
     private String fParent;
     private String fAuthor;
     private String fCommitter;
-    private String fMessage;
+    private final String fMessage;
 
     public GitCommit(String sha1Content) {
         int pos = sha1Content.indexOf('\0');
 
-        if (pos >= 0) {
-            fCommit = sha1Content.substring(0, pos);
+        if (pos >= 0)
             sha1Content = sha1Content.substring(pos +1);
-        } else {
-            fCommit = null;
-        }
         StringBuilder message = null;
         for (String line: sha1Content.split("\n")) {
             if (message != null)
@@ -43,6 +38,14 @@ public class GitCommit {
         return fTree;
     }
 
+    public String GetAuthor() {
+        return fAuthor;
+    }
+
+    public String GetCommitter() {
+        return fCommitter;
+    }
+
     public String GetParent() {
         return fParent;
     }

+ 2 - 3
app/src/main/java/info/knacki/pass/git/entities/GitObject.java

@@ -86,9 +86,8 @@ public class GitObject implements Comparable<GitObject> {
         }
 
         public Iterable<GitObject> objects() {
-            SortedSet<GitObject> objects = new TreeSet<>();
-            objects.addAll(fItems.values());
-            return objects;
+            SortedSet<GitObject> result = new TreeSet<>(fItems.values());
+            return result;
         }
 
         public Iterable<GitBlob> GetBlobs() {

+ 2 - 2
app/src/main/java/info/knacki/pass/git/entities/GitRef.java

@@ -1,8 +1,8 @@
 package info.knacki.pass.git.entities;
 
 public class GitRef {
-    protected String fHash;
-    protected String fBranch;
+    protected final String fHash;
+    protected final String fBranch;
 
     public GitRef(String hash, String branch) {
         fHash = hash;

+ 18 - 13
app/src/main/java/info/knacki/pass/input/InputService.java

@@ -1,5 +1,6 @@
 package info.knacki.pass.input;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.inputmethodservice.InputMethodService;
@@ -11,15 +12,16 @@ import android.widget.ScrollView;
 import android.widget.Toast;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import info.knacki.pass.R;
+import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.io.FileInterfaceFactory;
 import info.knacki.pass.ui.PasswordClickListener;
 import info.knacki.pass.ui.PasswordListView;
 import info.knacki.pass.ui.MainActivity;
+import info.knacki.pass.ui.PasswordPicker;
 
 public class InputService extends InputMethodService implements PasswordClickListener {
     protected PasswordListView fPasswordListView;
@@ -35,8 +37,8 @@ public class InputService extends InputMethodService implements PasswordClickLis
 
     @Override
     public View onCreateInputView() {
-        View view = LayoutInflater.from(this).inflate(R.layout.input, null, false);
-        fPasswordListView = new PasswordListView(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
+        @SuppressLint("InflateParams") View view = LayoutInflater.from(this).inflate(R.layout.input, null, false);
+        fPasswordListView = new PasswordListView<>(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
         ((ScrollView)view.findViewById(R.id.passwordListContainer)).addView(fPasswordListView);
         view.findViewById(R.id.prevButton).setOnClickListener(new View.OnClickListener() {
             @Override
@@ -60,17 +62,20 @@ public class InputService extends InputMethodService implements PasswordClickLis
 
     @Override
     public void OnPasswordClicked(File f) {
-        try {
-            String passwordContent = FileInterfaceFactory.GetFileInterface(this, f).ReadFile();
-            for (char i: passwordContent.toCharArray()) {
-                sendKeyChar(i);
+        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), f).ReadFile(new GitInterface.OnResponseListener<String>() {
+            @Override
+            public void onResponse(String passwordContent) {
+                for (char i: passwordContent.toCharArray()) {
+                    sendKeyChar(i);
+                }
             }
-        }
-        catch (IOException e) {
-            Toast.makeText(this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
-            log.log(Level.SEVERE, e.getMessage(), e);
-            return;
-        }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                Toast.makeText(InputService.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                log.log(Level.SEVERE, e.getMessage(), e);
+            }
+        });
         onFinishInput();
     }
 }

+ 8 - 2
app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java

@@ -4,20 +4,26 @@ import android.content.Context;
 
 import java.io.File;
 
+import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.settings.SettingsManager;
 
 public class FileInterfaceFactory {
+    public interface PasswordGetter {
+        void GetPassword(GitInterface.OnResponseListener<String> onPassword);
+        void WrongPassword();
+    }
+
     public final static String FINGERPRINT_SUFFIX = ".fck";
     public final static String PASSWORD_SUFFIX = ".enc";
     public final static String GPG_SUFFIX = ".gpg";
 
-    public static IFileInterface GetFileInterface(Context ctx, File f) {
+    public static IFileInterface GetFileInterface(Context ctx, PasswordGetter passwordGetter, File f) {
         if (f.getName().endsWith(FINGERPRINT_SUFFIX))
             return new FingerprintFileInterface(ctx, f);
         else if (f.getName().endsWith(PASSWORD_SUFFIX))
             return new PasswordFileInterface(f);
         else if (f.getName().endsWith(GPG_SUFFIX))
-            return new GPGFileInterface(ctx, f);
+            return new GPGFileInterface(ctx, passwordGetter, f);
         return new RawFileInterface(f);
     }
 

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

@@ -3,7 +3,8 @@ package info.knacki.pass.io;
 import android.content.Context;
 
 import java.io.File;
-import java.io.IOException;
+
+import info.knacki.pass.git.GitInterface;
 
 class FingerprintFileInterface implements IFileInterface {
     private final File fFile;
@@ -13,13 +14,13 @@ class FingerprintFileInterface implements IFileInterface {
     }
 
     @Override
-    public String ReadFile() throws IOException {
+    public void ReadFile(GitInterface.OnResponseListener<String> onResp) {
         //FIXME
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void WriteFile(String content) throws IOException {
+    public void WriteFile(String content) {
         //FIXME
         throw new UnsupportedOperationException();
     }

+ 203 - 6
app/src/main/java/info/knacki/pass/io/GPGFileInterface.java

@@ -2,25 +2,222 @@ package info.knacki.pass.io;
 
 import android.content.Context;
 
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.util.io.Streams;
+
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+import info.knacki.pass.git.GitInterface;
+import info.knacki.pass.settings.SettingsManager;
 
 class GPGFileInterface implements IFileInterface {
+    private final static Logger log = Logger.getLogger(GPGFileInterface.class.getName());
     private final File fFile;
+    private final FileInterfaceFactory.PasswordGetter fPasswordGetter;
+    private final File fKeyFile;
 
-    GPGFileInterface(Context ctx, File f) {
+    GPGFileInterface(Context ctx, FileInterfaceFactory.PasswordGetter passwordGetter, File f) {
         fFile = f;
+        String gpgFile = SettingsManager.GetGPGKeyFile(ctx);
+        fKeyFile = gpgFile == null ? null : new File(gpgFile);
+        fPasswordGetter = passwordGetter;
+    }
+
+    private boolean TryPassword(PGPSecretKey pgpSecKey, String pass, GitInterface.OnResponseListener<PGPPrivateKey> onResponse) {
+        try {
+            PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(pass.toCharArray(), "BC");
+            onResponse.onResponse(pgpPrivKey);
+            return true;
+        }
+        catch (NoSuchProviderException e) {
+            onResponse.onError(e.getMessage(), e);
+            return true;
+        }
+        catch (PGPException e) {
+            // Wrong password
+            return false;
+        }
+    }
+
+    protected boolean FindSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, final GitInterface.OnResponseListener<PGPPrivateKey> onResponse) throws PGPException {
+        final PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
+
+        if (pgpSecKey != null) {
+            if (!TryPassword(pgpSecKey, "", onResponse)) {
+                GitInterface.OnResponseListener<String> onResp = new GitInterface.OnResponseListener<String>() {
+                    @Override
+                    public void onResponse(String result) {
+                        if (result == null) {
+                            onResponse.onError("Invalid password", null);
+                        } else if (!TryPassword(pgpSecKey, result, onResponse)) {
+                            fPasswordGetter.WrongPassword();
+                            fPasswordGetter.GetPassword(this);
+                        }
+                    }
+
+                    @Override
+                    public void onError(String msg, Throwable e) {
+                        onResponse.onError(msg, e);
+                    }
+                };
+                fPasswordGetter.GetPassword(onResp);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public void DecryptFile(final GitInterface.OnResponseListener<byte[]> resp) throws IOException, PGPException {
+        PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(fFile)));
+        Object o = pgpF.nextObject();
+        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(fKeyFile)));
+        Iterator<?> it = ((o instanceof PGPEncryptedDataList) ? (PGPEncryptedDataList) o : (PGPEncryptedDataList) pgpF.nextObject()).getEncryptedDataObjects();
+
+        while (it.hasNext()) {
+            final PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) it.next();
+
+            if (FindSecretKey(pgpSec, pbe.getKeyID(), new GitInterface.OnResponseListener<PGPPrivateKey>() {
+                @Override
+                public void onResponse(PGPPrivateKey sKey) {
+                    byte[] output;
+                    try {
+                        InputStream clear = pbe.getDataStream(sKey, "BC");
+                        PGPObjectFactory plainFact = new PGPObjectFactory(clear);
+                        Object message = null;
+                        PGPOnePassSignatureList onePassSignatureList = null;
+                        PGPSignatureList signatureList = null;
+                        PGPCompressedData compressedData = null;
+                        message = plainFact.nextObject();
+                        ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
+
+                        while (message != null) {
+                            log.info(message.toString());
+                            if (message instanceof PGPCompressedData) {
+                                compressedData = (PGPCompressedData) message;
+                                plainFact = new PGPObjectFactory(compressedData.getDataStream());
+                                message = plainFact.nextObject();
+                            }
+
+                            if (message instanceof PGPLiteralData) {
+                                Streams.pipeAll(((PGPLiteralData) message).getInputStream(), actualOutput);
+                            } else if (message instanceof PGPOnePassSignatureList) {
+                                onePassSignatureList = (PGPOnePassSignatureList) message;
+                            } else if (message instanceof PGPSignatureList) {
+                                signatureList = (PGPSignatureList) message;
+                            } else {
+                                throw new PGPException("message unknown message type.");
+                            }
+                            message = plainFact.nextObject();
+                        }
+                        actualOutput.close();
+                        PGPPublicKey publicKey = null;
+                        output = actualOutput.toByteArray();
+                        if (onePassSignatureList == null || signatureList == null) {
+                            log.warning("Poor PGP. Signatures not found.");
+                            resp.onResponse(output);
+                            return;
+                        } else {
+
+                            for (int i = 0; i < onePassSignatureList.size(); i++) {
+                                PGPOnePassSignature ops = onePassSignatureList.get(0);
+                                log.info("verifier : " + ops.getKeyID());
+                                PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(fKeyFile)));
+                                publicKey = pgpRing.getPublicKey(ops.getKeyID());
+                                if (publicKey != null) {
+                                    // FIXME ops.init(publicKey, "BC");
+                                    ops.update(output);
+                                    PGPSignature signature = signatureList.get(i);
+                                    if (ops.verify(signature)) {
+                                        Iterator<?> userIds = publicKey.getUserIDs();
+                                        while (userIds.hasNext()) {
+                                            String userId = (String) userIds.next();
+                                            log.info("Signed by " + userId);
+                                        }
+                                        log.info("Signature verified");
+                                    } else {
+                                        throw new SignatureException("Signature verification failed");
+                                    }
+                                }
+                            }
+
+                        }
+
+                        if (pbe.isIntegrityProtected() && !pbe.verify()) {
+                            throw new PGPException("Data is integrity protected but integrity is lost.");
+                        } else if (publicKey == null) {
+                            throw new SignatureException("Signature not found");
+                        }
+                    }
+                    catch (Throwable e) {
+                        resp.onError(e.getMessage(), e);
+                        return;
+                    }
+                    resp.onResponse(output);
+                }
+
+                @Override
+                public void onError(String msg, Throwable e) {
+
+                }
+            }))
+                break;
+        }
+    }
+
+    private void CryptFile(FileOutputStream file, byte[] data) {
     }
 
     @Override
-    public String ReadFile() throws IOException {
-        //FIXME
-        throw new UnsupportedOperationException();
+    public void ReadFile(final GitInterface.OnResponseListener<String> resp) {
+        try {
+            if (fKeyFile == null)
+                throw new FileNotFoundException("GPG key not set");
+            DecryptFile(new GitInterface.OnResponseListener<byte[]>() {
+                @Override
+                public void onResponse(byte[] result) {
+                    resp.onResponse(new String(result, Charset.defaultCharset()));
+                }
+
+                @Override
+                public void onError(String msg, Throwable e) {
+                    resp.onError(msg, e);
+                }
+            });
+        } catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+        }
     }
 
     @Override
     public void WriteFile(String content) throws IOException {
-        //FIXME
-        throw new UnsupportedOperationException();
+        if (fKeyFile == null)
+            throw new FileNotFoundException("GPG key not set");
+        CryptFile(new FileOutputStream(fFile), content.getBytes());
     }
 }

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

@@ -1,9 +1,10 @@
 package info.knacki.pass.io;
 
-import java.io.File;
 import java.io.IOException;
 
+import info.knacki.pass.git.GitInterface;
+
 public interface IFileInterface {
-    String ReadFile() throws IOException;
+    void ReadFile(GitInterface.OnResponseListener<String> resp);
     void WriteFile(String content) throws IOException;
 }

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

@@ -1,7 +1,8 @@
 package info.knacki.pass.io;
 
 import java.io.File;
-import java.io.IOException;
+
+import info.knacki.pass.git.GitInterface;
 
 class PasswordFileInterface implements IFileInterface {
     private final File fFile;
@@ -11,13 +12,13 @@ class PasswordFileInterface implements IFileInterface {
     }
 
     @Override
-    public String ReadFile() throws IOException {
+    public void ReadFile(GitInterface.OnResponseListener<String> onResp) {
         throw new UnsupportedOperationException();
         //FIXME
     }
 
     @Override
-    public void WriteFile(String content) throws IOException {
+    public void WriteFile(String content) {
         throw new UnsupportedOperationException();
         //FIXME
     }

+ 16 - 10
app/src/main/java/info/knacki/pass/io/RawFileInterface.java

@@ -6,6 +6,7 @@ import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 
+import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.io.IFileInterface;
 
 class RawFileInterface implements IFileInterface {
@@ -16,18 +17,23 @@ class RawFileInterface implements IFileInterface {
     }
 
     @Override
-    public String ReadFile() throws IOException {
-        BufferedReader buf = new BufferedReader(new FileReader(fFile));
-        StringBuilder result = new StringBuilder();
-        String tmp;
+    public void ReadFile(GitInterface.OnResponseListener<String> resp) {
+        try {
+            BufferedReader buf = new BufferedReader(new FileReader(fFile));
+            StringBuilder result = new StringBuilder();
+            String tmp;
 
-        while ((tmp = buf.readLine()) != null) {
-            if (result.length() > 0)
-                result.append("\n");
-            result.append(tmp);
+            while ((tmp = buf.readLine()) != null) {
+                if (result.length() > 0)
+                    result.append("\n");
+                result.append(tmp);
+            }
+            buf.close();
+            resp.onResponse(result.toString());
+        }
+        catch (IOException e) {
+            resp.onError(e.getMessage(), e);
         }
-        buf.close();
-        return result.toString();
     }
 
     @Override

+ 28 - 21
app/src/main/java/info/knacki/pass/settings/SettingsManager.java

@@ -5,8 +5,6 @@ import android.content.SharedPreferences;
 import android.util.JsonReader;
 import android.util.MalformedJsonException;
 
-import org.json.JSONStringer;
-
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.logging.Level;
@@ -81,27 +79,30 @@ public class SettingsManager {
                     throw new MalformedJsonException(data);
                 reader.beginObject();
                 while (reader.hasNext()) {
-                    String key = reader.nextName();
-                    if ("url".equals(key))
-                        fUrl = reader.nextString();
-                    else if ("user".equals(key))
-                        fUser = reader.nextString();
-                    else if ("pass".equals(key))
-                        fPassword = reader.nextString();
-                    else if ("branch".equals(key))
-                        fBranch = reader.nextString();
+                    switch (reader.nextName()) {
+                        case "url":
+                            fUrl = reader.nextString();
+                            break;
+                        case "user":
+                            fUser = reader.nextString();
+                            break;
+                        case "pass":
+                            fPassword = reader.nextString();
+                            break;
+                        case "branch":
+                            fBranch = reader.nextString();
+                            break;
+                    }
                 }
                 reader.close();
             }
             catch (MalformedJsonException e) {
                 log.log(Level.SEVERE, e.getMessage(), e);
                 reset();
-                return;
             }
             catch (IOException e) {
                 log.log(Level.SEVERE, e.getMessage(), e);
                 reset();
-                return;
             }
         }
 
@@ -164,14 +165,12 @@ public class SettingsManager {
         }
 
         public String GetData() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("[{")
-                    .append("\"url\":\"").append(jsonEscape(fUrl)).append("\",")
-                    .append("\"user\":\"").append(jsonEscape(fUser)).append("\",")
-                    .append("\"pass\":\"").append(jsonEscape(fPassword)).append("\",")
-                    .append("\"branch\":\"").append(jsonEscape(fBranch)).append("\"")
-                    .append("}]");
-            return sb.toString();
+            return "[{" +
+                    "\"url\":\"" +jsonEscape(fUrl) +"\"," +
+                    "\"user\":\"" +jsonEscape(fUser) +"\"," +
+                    "\"pass\":\"" +jsonEscape(fPassword) +"\"," +
+                    "\"branch\":\"" +jsonEscape(fBranch) +"\"" +
+                    "}]";
         }
     }
 
@@ -201,4 +200,12 @@ public class SettingsManager {
         else
             prefs.remove(VCS.class.getSimpleName()).commit();
     }
+
+    public static void SetGPGKeyFile(Context ctx, String filename) {
+        GetPrefManager(ctx).edit().putString("GPGKeyFile", filename).commit();
+    }
+
+    public static String GetGPGKeyFile(Context ctx) {
+        return GetPrefManager(ctx).getString("GPGKeyFile", null);
+    }
 }

+ 46 - 12
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -105,7 +105,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
         return PreferenceFragment.class.getName().equals(fragmentName)
                 || GeneralPreferenceFragment.class.getName().equals(fragmentName)
                 || EncryptionPreferenceFragment.class.getName().equals(fragmentName)
-                || VCSPreferenceFragment.class.getName().equals(fragmentName);
+                || VCSPreferenceFragment.class.getName().equals(fragmentName)
+                || GPGPreferenceFragment.class.getName().equals(fragmentName);
     }
 
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@@ -192,7 +193,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             findPreference(getResources().getString(R.string.id_vcs_enable)).setOnPreferenceChangeListener(new PrefListener() {
                 @Override
                 void savePref(Preference preference, Object o) {
-                    if ((Boolean)o)
+                    if ((Boolean) o)
                         SettingsManager.SetVCS(getActivity(), new SettingsManager.Git());
                     else
                         SettingsManager.SetVCS(getActivity(), null);
@@ -260,7 +261,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             reload();
         }
 
-        HashMap<String, Preference> hiddenPreferences = new HashMap<>();
+        final HashMap<String, Preference> hiddenPreferences = new HashMap<>();
 
         protected void reload() {
             final SettingsManager.VCS versioning = SettingsManager.GetVCS(getActivity());
@@ -277,24 +278,24 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                         gitUrl = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_url));
                         getPreferenceScreen().addPreference(gitUrl);
                     }
-                    gitUrl.setSummary(((SettingsManager.Git)versioning).GetUrl());
-                    gitUrl.setText(((SettingsManager.Git)versioning).GetUrl());
+                    gitUrl.setSummary(((SettingsManager.Git) versioning).GetUrl());
+                    gitUrl.setText(((SettingsManager.Git) versioning).GetUrl());
 
                     EditTextPreference gitUser = (EditTextPreference) findPreference(getResources().getString(R.string.id_vcs_git_user));
                     if (gitUser == null) {
                         gitUser = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_user));
                         getPreferenceScreen().addPreference(gitUser);
                     }
-                    gitUser.setSummary(((SettingsManager.Git)versioning).GetUser());
-                    gitUser.setText(((SettingsManager.Git)versioning).GetUser());
+                    gitUser.setSummary(((SettingsManager.Git) versioning).GetUser());
+                    gitUser.setText(((SettingsManager.Git) versioning).GetUser());
 
                     EditTextPreference gitPass = (EditTextPreference) findPreference(getResources().getString(R.string.id_vcs_git_pass));
                     if (gitPass == null) {
                         gitPass = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_pass));
                         getPreferenceScreen().addPreference(gitPass);
                     }
-                    gitPass.setSummary(((SettingsManager.Git)versioning).GetPassword().length() == 0 ? "" : "******");
-                    gitPass.setText(((SettingsManager.Git)versioning).GetPassword());
+                    gitPass.setSummary(((SettingsManager.Git) versioning).GetPassword().length() == 0 ? "" : "******");
+                    gitPass.setText(((SettingsManager.Git) versioning).GetPassword());
 
                     ListPreference gitBranches = (ListPreference) findPreference(getResources().getString(R.string.id_vcs_git_branches));
                     if (gitBranches == null) {
@@ -377,8 +378,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                                 CharSequence[] refs = new CharSequence[result.length];
                                 int selected = -1;
 
-                                for (int i =0; i < result.length; ++i) {
-                                    refNames[i] = result[i].GetBranchName   ();
+                                for (int i = 0; i < result.length; ++i) {
+                                    refNames[i] = result[i].GetBranchName();
                                     refs[i] = result[i].GetBranch();
                                     if (result[i].GetBranch().equals(versioning.GetBranch()))
                                         selected = i;
@@ -406,7 +407,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 });
             } else {
                 gitBranches.setEntryValues(new CharSequence[]{});
-                gitBranches.setEntries(new CharSequence[] {});
+                gitBranches.setEntries(new CharSequence[]{});
                 gitBranches.setEnabled(false);
             }
         }
@@ -421,4 +422,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             return super.onOptionsItemSelected(item);
         }
     }
+
+    public final static int ACTIVITY_REQUEST_CODE_BROWSEGPG = 1;
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class GPGPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_gpg);
+            setHasOptionsMenu(true);
+
+            findPreference(getResources().getString(R.string.id_gpg_keyfile)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+                    i.setType("file/*");
+                    GPGPreferenceFragment.this.startActivityForResult(i, ACTIVITY_REQUEST_CODE_BROWSEGPG);
+                    return true;
+                }
+            });
+        }
+
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent data) {
+            if (requestCode == ACTIVITY_REQUEST_CODE_BROWSEGPG) {
+                if (resultCode == RESULT_OK) {
+                    String path = data.getData().getPath();
+                    Logger.getAnonymousLogger().severe("Coucou path {" +path +"}");
+                    SettingsManager.SetGPGKeyFile(getActivity(), path);
+                }
+            }
+        }
+    }
 }

+ 8 - 2
app/src/main/java/info/knacki/pass/ui/AlertText.java

@@ -5,6 +5,7 @@ import android.content.DialogInterface;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.AppCompatCheckBox;
 import android.support.v7.widget.AppCompatEditText;
+import android.text.InputType;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -31,11 +32,16 @@ public class AlertText {
         public String getStr() {
             return super.getText().toString().trim();
         }
+
+        public SimpleTextEdit SetPassword() {
+            setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+            return this;
+        }
     }
 
     public static class TextEditAndCheckbox extends LinearLayout {
-        private AppCompatEditText fTextEdit;
-        private AppCompatCheckBox fCheckbox;
+        private final AppCompatEditText fTextEdit;
+        private final AppCompatCheckBox fCheckbox;
 
         TextEditAndCheckbox(Context c) {
             super(c);

+ 31 - 13
app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java

@@ -15,6 +15,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import info.knacki.pass.R;
+import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.io.FileInterfaceFactory;
 
 public class EditPasswordActivity extends AppCompatActivity {
@@ -47,7 +48,7 @@ public class EditPasswordActivity extends AppCompatActivity {
             @Override
             public void onClick(View view) {
                 try {
-                    FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, fOutputFile).WriteFile(fTextEdit.getText().toString());
+                    FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, new PasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(fTextEdit.getText().toString());
                 }
                 catch (IOException e) {
                     Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
@@ -64,18 +65,35 @@ public class EditPasswordActivity extends AppCompatActivity {
         }
     }
 
-    protected void populateContent(boolean requestFocus) {
-        try {
-            String pass = FileInterfaceFactory.GetFileInterface(this, fOutputFile).ReadFile();
-            fTextEdit.setText(pass);
-            if (requestFocus) {
-                fTextEdit.setSelection(pass.length());
-                fTextEdit.requestFocus();
+    protected void populateContent(final boolean requestFocus) {
+        fTextEdit.setEnabled(false);
+        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), fOutputFile).ReadFile(new GitInterface.OnResponseListener<String>() {
+            @Override
+            public void onResponse(final String pass) {
+                EditPasswordActivity.this.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        fTextEdit.setEnabled(true);
+                        fTextEdit.setText(pass);
+                        if (requestFocus) {
+                            fTextEdit.setSelection(pass.length());
+                            fTextEdit.requestFocus();
+                        }
+                    }
+                });
             }
-        }
-        catch (IOException e) {
-            Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
-            log.log(Level.WARNING, e.getMessage(), e);
-        }
+
+            @Override
+            public void onError(final String msg, final Throwable e) {
+                EditPasswordActivity.this.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        Toast.makeText(EditPasswordActivity.this, "Error: " +msg, Toast.LENGTH_LONG).show();
+                        log.log(Level.WARNING, msg, e);
+                        finish();
+                    }
+                });
+            }
+        });
     }
 }

+ 2 - 5
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -8,13 +8,10 @@ import android.widget.TextView;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.nio.charset.Charset;
 import java.security.MessageDigest;
-import java.util.ArrayDeque;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Stack;
@@ -113,8 +110,8 @@ public class GitPullActivity extends AppCompatActivity {
 
             byte [] md5Bytes = md5Hash.digest();
             StringBuilder sb = new StringBuilder();
-            for (int i=0; i < md5Bytes.length; i++)
-                sb.append(Integer.toString((md5Bytes[i] & 0xff) + 0x100, 16).substring(1));
+            for (byte i: md5Bytes)
+                sb.append(Integer.toString((i & 0xff) + 0x100, 16).substring(1));
             return sb.toString();
         }
         catch(Throwable t) {

+ 7 - 5
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -1,7 +1,9 @@
 package info.knacki.pass.ui;
 
+import android.Manifest;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.os.Build;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.Menu;
@@ -112,6 +114,9 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
     }
 
     void requestPermissions() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
+        }
     }
 
     @Override
@@ -126,13 +131,10 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
         setContentView(R.layout.activity_pass_list);
 
         setTitle(R.string.title);
-        vPasswordListView = new PasswordListView(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
+        vPasswordListView = new PasswordListView<>(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
         ((ScrollView)findViewById(R.id.passwordListContainer)).addView(vPasswordListView);
 
         requestPermissions();
-
-        startActivity(new Intent(this, GitPullActivity.class));
-
     }
 
     @Override
@@ -171,7 +173,7 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
             .setPositiveButton(R.string.add, new AlertText.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialogInterface, View view) {
-                    IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, f);
+                    IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, new PasswordPicker(MainActivity.this), f);
                     try {
                         writer.WriteFile(PasswordGenerator.generate(wiz));
                     }

+ 0 - 1
app/src/main/java/info/knacki/pass/ui/PasswordListView.java

@@ -6,7 +6,6 @@ import android.view.View;
 import android.widget.LinearLayout;
 
 import java.io.File;
-import java.util.ArrayDeque;
 import java.util.SortedSet;
 import java.util.TreeSet;
 

+ 44 - 0
app/src/main/java/info/knacki/pass/ui/PasswordPicker.java

@@ -0,0 +1,44 @@
+package info.knacki.pass.ui;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.View;
+import android.widget.Toast;
+
+import info.knacki.pass.R;
+import info.knacki.pass.git.GitInterface;
+import info.knacki.pass.io.FileInterfaceFactory;
+
+public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
+    private final Context fContext;
+
+    public PasswordPicker(Context context) {
+        fContext = context;
+    }
+
+    @Override
+    public void GetPassword(final GitInterface.OnResponseListener<String> onPassword) {
+        new AlertText(fContext)
+            .setCancelable(true)
+            .setView(new AlertText.SimpleTextEdit(fContext).SetPassword())
+            .setPositiveButton(R.string.ok, new AlertText.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, View view) {
+                    onPassword.onResponse(((AlertText.SimpleTextEdit) view).getStr());
+                }
+            })
+            .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, View s) {
+                    onPassword.onResponse(null);
+                }
+            })
+            .setTitle(R.string.enter_password)
+            .show();
+    }
+
+    @Override
+    public void WrongPassword() {
+        Toast.makeText(fContext, fContext.getResources().getString(R.string.wrongPassword), Toast.LENGTH_LONG).show();
+    }
+}

+ 5 - 9
app/src/main/java/info/knacki/pass/ui/PasswordView.java

@@ -2,24 +2,20 @@ package info.knacki.pass.ui;
 
 import android.content.Context;
 import android.view.Gravity;
-import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 import info.knacki.pass.R;
 
 public class PasswordView extends LinearLayout {
     public final static int ICON_SIZE = 128;
-    protected ImageView vIcon;
-    protected TextView vName;
+    protected final ImageView vIcon;
+    protected final TextView vName;
 
-    public static int TYPE_DIR = 1;
-    public static int TYPE_PARENT = 2;
-    public static int TYPE_PASSWORD = 4;
+    public static final int TYPE_DIR = 1;
+    public static final int TYPE_PARENT = 2;
+    public static final int TYPE_PASSWORD = 4;
 
     protected static String trimExtension(String in) {
         int lastIndex = in.lastIndexOf('.');

+ 2 - 2
app/src/main/res/layout/activity_git_pull.xml

@@ -13,11 +13,11 @@
         android:layout_height="wrap_content" />
     <ScrollView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="match_parent">
         <android.support.v7.widget.AppCompatTextView
             android:id="@+id/logView"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:inputType="textMultiLine" />
     </ScrollView>
 </LinearLayout>

+ 1 - 1
app/src/main/res/layout/activity_pass_edit.xml

@@ -19,7 +19,7 @@
             tools:layout_editor_absoluteY="0dp"/>
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="0dp"
             android:layout_weight="1"
             android:gravity="bottom">
             <android.support.v7.widget.AppCompatCheckBox

+ 7 - 1
app/src/main/res/values-fr/lang.xml

@@ -24,10 +24,11 @@
     <string name="pref_header_VCS">VCS</string>
     <string name="pref_title_enctype">Méthode de chiffrement</string>
     <string name="pref_vcs_url">Addresse du dépot git</string>
-    <string name="pref_vcs_git_user_title">Nom d'utilisateur</string>
+    <string name="pref_vcs_git_user_title">Nom d\'utilisateur</string>
     <string name="pref_vcs_git_pass_title">Mot de passe</string>
     <string name="pref_vcs_branch_title">Branche GIT</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_header_GPG">Configuration de GPG</string>
     <array name="pref_enctype_title">
         <item>Pas de chiffrement</item>
         <item>Empreinte digitale</item>
@@ -47,4 +48,9 @@
         <item>Fort</item>
         <item>Personalisé</item>
     </array>
+    <string name="pref_header_gpg_keyfile">Clé GPG</string>
+    <string name="pref_summary_gpg_keyfile">Rechercher une clé GPG</string>
+    <string name="enter_password">Mot de passe</string>
+    <string name="wrongPassword">Mot de passe erroné</string>
+    <string name="ok">OK</string>
 </resources>

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

@@ -37,6 +37,7 @@
     <string name="pref_vcs_git_pass_title">Password</string>
     <string name="pref_vcs_branch_title">Git branch</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_header_GPG">GPG settings</string>
     <array name="pref_vcs_list_keys">
         <item>GIT</item>
     </array>
@@ -47,4 +48,9 @@
         <item>Strong</item>
         <item>Custom</item>
     </array>
+    <string name="pref_header_gpg_keyfile">GPG key</string>
+    <string name="pref_summary_gpg_keyfile">Browse to find GPG exported key</string>
+    <string name="enter_password">Enter password</string>
+    <string name="wrongPassword">Wrong password</string>
+    <string name="ok">OK</string>
 </resources>

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

@@ -21,4 +21,5 @@
     <string name="id_vcs_git_pass">id_vcs_git_pass</string>
     <string name="id_vcs_git_branches">id_vcs_git_branches</string>
     <string name="id_vcs_git_pull">id_vcs_git_pull</string>
+    <string name="id_gpg_keyfile">id_gpg_keyfile</string>
 </resources>

+ 4 - 0
app/src/main/res/xml/pref_encryption.xml

@@ -11,4 +11,8 @@
         android:negativeButtonText="@null"
         android:positiveButtonText="@null"
         android:title="@string/pref_title_enctype" />
+    <PreferenceScreen
+        android:fragment="info.knacki.pass.settings.ui.SettingsActivity$GPGPreferenceFragment"
+        android:icon="@drawable/ic_info_black_24dp"
+        android:title="@string/pref_header_GPG" />
 </PreferenceScreen>

+ 7 - 0
app/src/main/res/xml/pref_gpg.xml

@@ -0,0 +1,7 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:key="@string/id_gpg_keyfile"
+        android:title="@string/pref_header_gpg_keyfile"
+        android:summary="@string/pref_summary_gpg_keyfile"
+        />
+</PreferenceScreen>