Browse Source

[add] remove all data
[add] password encrypt/decrypt
[add] gpg key decrypt
[wip] gpg key encrypt

isundil 7 years ago
parent
commit
6a93877c98

+ 2 - 1
app/build.gradle

@@ -40,5 +40,6 @@ 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'
+    implementation 'org.bouncycastle:bcpg-jdk15on:1.60'
+    implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
 }

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

@@ -3,6 +3,7 @@
     package="info.knacki.pass">
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application
         android:allowBackup="true"

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

@@ -81,11 +81,8 @@ public class GitLocal {
         try {
             f.createNewFile();
             FileWriter writer = new FileWriter(f);
-            Logger.getAnonymousLogger().severe("Start writing cache hashes");
-            for (HashMap.Entry<String, ControlSum> i: cache.entrySet()) {
+            for (HashMap.Entry<String, ControlSum> i: cache.entrySet())
                 writer.write(i.getValue().fSha1 +" " +i.getValue().fMd5 +" " +i.getKey() +"\n");
-                Logger.getAnonymousLogger().severe(i.getValue().fSha1 +" " +i.getValue().fMd5 +" " +i.getKey() +"\n");
-            }
             writer.close();
         }
         catch (IOException e)

+ 0 - 1
app/src/main/java/info/knacki/pass/git/entities/GitObject.java

@@ -54,7 +54,6 @@ public class GitObject implements Comparable<GitObject> {
         }
 
         public GitObject GetObjectFullPath(String path) {
-            Logger.getAnonymousLogger().severe("get git obj {" +path +"}");
             if (path.charAt(0) == '/')
                 path = path.substring(1);
             int nextSlash = path.indexOf('/');

+ 2 - 1
app/src/main/java/info/knacki/pass/input/InputService.java

@@ -18,6 +18,7 @@ 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.io.PathUtils;
 import info.knacki.pass.ui.PasswordClickListener;
 import info.knacki.pass.ui.PasswordListView;
 import info.knacki.pass.ui.MainActivity;
@@ -38,7 +39,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
     @Override
     public View onCreateInputView() {
         @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));
+        fPasswordListView = new PasswordListView<>(this, PathUtils.GetPassDir(this));
         ((ScrollView)view.findViewById(R.id.passwordListContainer)).addView(fPasswordListView);
         view.findViewById(R.id.prevButton).setOnClickListener(new View.OnClickListener() {
             @Override

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

@@ -21,7 +21,7 @@ public class FileInterfaceFactory {
         if (f.getName().endsWith(FINGERPRINT_SUFFIX))
             return new FingerprintFileInterface(ctx, f);
         else if (f.getName().endsWith(PASSWORD_SUFFIX))
-            return new PasswordFileInterface(f);
+            return new PasswordFileInterface(passwordGetter, f);
         else if (f.getName().endsWith(GPG_SUFFIX))
             return new GPGFileInterface(ctx, passwordGetter, f);
         return new RawFileInterface(f);

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

@@ -20,7 +20,7 @@ class FingerprintFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content) {
+    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
         //FIXME
         throw new UnsupportedOperationException();
     }

+ 266 - 89
app/src/main/java/info/knacki/pass/io/GPGFileInterface.java

@@ -2,22 +2,37 @@ package info.knacki.pass.io;
 
 import android.content.Context;
 
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataList;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPObjectFactory;
-import org.bouncycastle.openpgp.PGPOnePassSignature;
-import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
 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.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
-import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
 import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 import org.bouncycastle.util.io.Streams;
 
 import java.io.ByteArrayOutputStream;
@@ -27,10 +42,12 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.Charset;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
+import java.security.Security;
+import java.util.Date;
 import java.util.Iterator;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import info.knacki.pass.git.GitInterface;
@@ -49,23 +66,23 @@ class GPGFileInterface implements IFileInterface {
         fPasswordGetter = passwordGetter;
     }
 
-    private boolean TryPassword(PGPSecretKey pgpSecKey, String pass, GitInterface.OnResponseListener<PGPPrivateKey> onResponse) {
+    private boolean TryPassword(PGPSecretKey pgpSecKey, String pass, GitInterface.OnResponseListener<PGPPrivateKeyAndPass> onResponse) {
         try {
-            PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(pass.toCharArray(), "BC");
-            onResponse.onResponse(pgpPrivKey);
-            return true;
-        }
-        catch (NoSuchProviderException e) {
-            onResponse.onError(e.getMessage(), e);
+            PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(pass.toCharArray()));
+            PGPPrivateKeyAndPass res = new PGPPrivateKeyAndPass();
+            res.key = pgpPrivKey;
+            res.pass = pass;
+            onResponse.onResponse(res);
             return true;
         }
         catch (PGPException e) {
             // Wrong password
+            log.log(Level.INFO, e.getMessage() +" (wrong password ?)", e);
             return false;
         }
     }
 
-    protected boolean FindSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, final GitInterface.OnResponseListener<PGPPrivateKey> onResponse) throws PGPException {
+    protected boolean FindSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, final GitInterface.OnResponseListener<PGPPrivateKeyAndPass> onResponse) throws PGPException {
         final PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
 
         if (pgpSecKey != null) {
@@ -93,88 +110,82 @@ class GPGFileInterface implements IFileInterface {
         return false;
     }
 
+    private InputStream getFirstLiteralDataInputStream(InputStream in, PGPPrivateKeyAndPass pKey) throws IOException, PGPException {
+        PGPObjectFactory pof = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());
+        Object po;
+
+        while (true) {
+            po = pof.nextObject();
+
+            if (po == null) {
+                break;
+            } else if (po instanceof PGPCompressedData) {
+                InputStream ret = getFirstLiteralDataInputStream(((PGPCompressedData) po).getDataStream(), pKey);
+
+                if (ret != null) return ret;
+            } else if (po instanceof PGPLiteralData) {
+                return ((PGPLiteralData) po).getDataStream();
+            } else if (po instanceof PGPEncryptedDataList) {
+                for (Iterator<PGPEncryptedData> it = ((PGPEncryptedDataList) po).getEncryptedDataObjects(); it.hasNext();) {
+                    PGPEncryptedData ped = it.next();
+
+                    if (ped instanceof PGPPublicKeyEncryptedData) {
+                        PGPPublicKeyEncryptedData pked = (PGPPublicKeyEncryptedData) ped;
+
+                        InputStream ret = getFirstLiteralDataInputStream(pked.getDataStream(new BcPublicKeyDataDecryptorFactory(pKey.key)), pKey);
+
+                        if (ret != null) return ret;
+                        // TODO: To verify integrity,
+                        //       we need to keep the
+                        //       pked reference.
+                    } else if (ped instanceof PGPPBEEncryptedData) {
+                        PGPPBEEncryptedData pped = (PGPPBEEncryptedData) ped;
+
+                        InputStream ret = getFirstLiteralDataInputStream(pped.getDataStream(new BcPBEDataDecryptorFactory(pKey.pass.toCharArray(), new BcPGPDigestCalculatorProvider())), pKey);
+
+                        if (ret != null) return ret;
+                    } else {
+                        throw new IOException("Unknown encryption packet");
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private class PGPPrivateKeyAndPass {
+        public PGPPrivateKey key;
+        public String pass;
+    }
+
     public void DecryptFile(final GitInterface.OnResponseListener<byte[]> resp) throws IOException, PGPException {
-        PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(fFile)));
+        PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(fFile)), new JcaKeyFingerprintCalculator());
         Object o = pgpF.nextObject();
-        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(fKeyFile)));
-        Iterator<?> it = ((o instanceof PGPEncryptedDataList) ? (PGPEncryptedDataList) o : (PGPEncryptedDataList) pgpF.nextObject()).getEncryptedDataObjects();
+        final 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>() {
+            if (FindSecretKey(
+                    new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(fKeyFile)), new JcaKeyFingerprintCalculator()),
+                    pbe.getKeyID(),
+                    new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
                 @Override
-                public void onResponse(PGPPrivateKey sKey) {
+                public void onResponse(PGPPrivateKeyAndPass 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();
+                        BcPublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory = new BcPublicKeyDataDecryptorFactory(sKey.key);
+                        InputStream ss = getFirstLiteralDataInputStream(pbe.getDataStream(publicKeyDataDecryptorFactory), sKey);
+                        if (null == ss) {
+                            resp.onError("No literal GPG data found", null);
+                            return;
                         }
+                        ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
+                        Streams.pipeAll(ss, actualOutput);
                         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) {
+                    catch (PGPException | IOException e) {
                         resp.onError(e.getMessage(), e);
                         return;
                     }
@@ -183,18 +194,179 @@ class GPGFileInterface implements IFileInterface {
 
                 @Override
                 public void onError(String msg, Throwable e) {
-
+                    resp.onError(msg, e);
                 }
             }))
                 break;
         }
     }
 
-    private void CryptFile(FileOutputStream file, byte[] data) {
+    private void FindPassword(final PGPSecretKey pgpSecKey, final GitInterface.OnResponseListener<PGPPrivateKeyAndPass> resp) {
+        if (!TryPassword(pgpSecKey, "", resp)) {
+            GitInterface.OnResponseListener<String> onResp = new GitInterface.OnResponseListener<String>() {
+                @Override
+                public void onResponse(String result) {
+                    if (result == null) {
+                        resp.onError("Invalid password", null);
+                    } else if (!TryPassword(pgpSecKey, result, resp)) {
+                        fPasswordGetter.WrongPassword();
+                        fPasswordGetter.GetPassword(this);
+                    }
+                }
+
+                @Override
+                public void onError(String msg, Throwable e) {
+                    resp.onError(msg, e);
+                }
+            };
+            fPasswordGetter.GetPassword(onResp);
+        }
+    }
+
+    private static PGPSecretKey findSecretKey(InputStream in) throws IOException, PGPException {
+        in = PGPUtil.getDecoderStream(in);
+        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new JcaKeyFingerprintCalculator());
+        PGPSecretKey key = null;
+        Iterator rIt = pgpSec.getKeyRings();
+
+        while (key == null && rIt.hasNext()) {
+            PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next();
+            Iterator kIt = kRing.getSecretKeys();
+
+            while (key == null && kIt.hasNext()) {
+                PGPSecretKey k = (PGPSecretKey) kIt.next();
+
+                if (k.isSigningKey()) {
+                    key = k;
+                }
+            }
+        }
+
+        if (key == null) {
+            throw new IllegalArgumentException("Can't find signing key in key ring.");
+        }
+        return key;
+    }
+
+    private static PGPPublicKey findPublicKey(InputStream in) throws IOException, PGPException {
+        return findSecretKey(in).getPublicKey();
+    }
+
+    private void CryptFile(final OutputStream fileOutStream, final byte[] data, final GitInterface.OnResponseListener<Void> resp) {
+        Security.addProvider(new BouncyCastleProvider());
+        final PGPEncryptedDataGenerator pedg = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_256));
+        final ArmoredOutputStream armoredOutput = new ArmoredOutputStream(fileOutStream);
+        final PGPSecretKey secretKey;
+
+        try {
+            secretKey = findSecretKey(new FileInputStream(fKeyFile));
+        }
+        catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+            return;
+        }
+        FindPassword(secretKey, new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
+            @Override
+            public void onResponse(PGPPrivateKeyAndPass privateKey) {
+                try {
+                    PGPSignatureGenerator sg = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1));
+                    pedg.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(secretKey.getPublicKey()));
+                    OutputStream encryptdOutStream = pedg.open(armoredOutput, new byte[1 << 16]);
+                    PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator( PGPCompressedData.UNCOMPRESSED);
+                    OutputStream compressedOutStream = comData.open(encryptdOutStream);
+
+                    sg.init(PGPSignature.BINARY_DOCUMENT, privateKey.key);
+                    Iterator it = secretKey.getPublicKey().getUserIDs();
+                    if (it.hasNext()) {
+                        PGPSignatureSubpacketGenerator ssg = new PGPSignatureSubpacketGenerator();
+                        ssg.setSignerUserID(false, (String) it.next());
+                        sg.setHashedSubpackets(ssg.generate());
+                    }
+                    sg.generateOnePassVersion(false).encode(compressedOutStream);
+
+                    PGPLiteralDataGenerator lg = new PGPLiteralDataGenerator();
+                    OutputStream literalDataOutStream = lg.open(compressedOutStream, PGPLiteralData.BINARY, PGPLiteralDataGenerator.CONSOLE, new Date(), data);
+
+                    literalDataOutStream.write(data);
+                    //sg.update(data);
+                    sg.generate().encode(compressedOutStream);
+                    literalDataOutStream.close();
+                    lg.close();
+                    compressedOutStream.close();
+                    comData.close();
+                    pedg.close();
+                    armoredOutput.close();
+                    resp.onResponse(null);
+                }
+                catch (Throwable e) {
+                    resp.onError(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                resp.onError(msg, e);
+            }
+        });
+    }
+
+    /*
+    This one do write, but ???9 instead of 12359*
+    private void CryptFile(OutputStream fileOutStream, final byte[] data, final GitInterface.OnResponseListener<Void> resp) {
+        if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        final PGPEncryptedDataGenerator pedg = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TWOFISH));
+        ArmoredOutputStream aos = new ArmoredOutputStream(fileOutStream);
+
+        try {
+            pedg.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(findPublicKey(new FileInputStream(fKeyFile))));
+            PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.UNCOMPRESSED);
+            OutputStream compressedOutStream = comData.open(pedg.open(aos, new byte[1 << 16]));
+
+            PGPLiteralDataGenerator lg = new PGPLiteralDataGenerator();
+            OutputStream literalDataOutStream = lg.open(compressedOutStream, PGPLiteralData.TEXT, "-", new Date(), data);
+            literalDataOutStream.write(data);
+            literalDataOutStream.close();
+            lg.close();
+
+            compressedOutStream.close();
+            comData.close();
+            literalDataOutStream.close();
+            pedg.close();
+            aos.close();
+            fileOutStream.close();
+            resp.onResponse(null);
+        }
+        catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+        }
+    }
+
+
+    /*
+    private void CryptFile(final OutputStream fileOutStream, final byte[] data, final GitInterface.OnResponseListener<Void> resp) {
+        try {
+            PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
+            OutputStream pOut = lData.open(fileOutStream, PGPLiteralDataGenerator.TEXT, "-", new Date(), data);
+            pOut.write(data, 0, data.length);
+            lData.close();
+            fileOutStream.close();
+            resp.onResponse(null);
+        }
+        catch (IOException e) {
+            resp.onError(e.getMessage(), e);
+        }
     }
+    */
 
     @Override
     public void ReadFile(final GitInterface.OnResponseListener<String> resp) {
+        if (fFile.length() == 0) {
+            resp.onResponse("");
+            return;
+        }
         try {
             if (fKeyFile == null)
                 throw new FileNotFoundException("GPG key not set");
@@ -215,9 +387,14 @@ class GPGFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content) throws IOException {
-        if (fKeyFile == null)
-            throw new FileNotFoundException("GPG key not set");
-        CryptFile(new FileOutputStream(fFile), content.getBytes());
+    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+        try {
+            if (fKeyFile == null)
+                throw new FileNotFoundException("GPG key not set");
+            CryptFile(new FileOutputStream(fFile), content.getBytes(), resp);
+        }
+        catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+        }
     }
 }

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

@@ -6,5 +6,5 @@ import info.knacki.pass.git.GitInterface;
 
 public interface IFileInterface {
     void ReadFile(GitInterface.OnResponseListener<String> resp);
-    void WriteFile(String content) throws IOException;
+    void WriteFile(String content, GitInterface.OnResponseListener<Void> resp);
 }

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

@@ -1,25 +1,185 @@
 package info.knacki.pass.io;
 
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPDataValidationException;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.util.io.Streams;
+
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.logging.Logger;
 
 import info.knacki.pass.git.GitInterface;
 
 class PasswordFileInterface implements IFileInterface {
+    private final static Logger log = Logger.getLogger(GPGFileInterface.class.getName());
     private final File fFile;
+    private final FileInterfaceFactory.PasswordGetter fPasswordGetter;
+
+    private final String passwordWrite = "mpass";
 
-    PasswordFileInterface(File f) {
+    PasswordFileInterface(FileInterfaceFactory.PasswordGetter passwordGetter, File f) {
         fFile = f;
+        fPasswordGetter = passwordGetter;
+    }
+
+    private boolean TryPassword(String pass, GitInterface.OnResponseListener<InputStream> onResponse) {
+        try {
+            InputStream in = PGPUtil.getDecoderStream(new FileInputStream(fFile));
+            JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+            Object o = pgpF.nextObject();
+            PGPEncryptedDataList enc = (o instanceof PGPEncryptedDataList) ? (PGPEncryptedDataList) o : (PGPEncryptedDataList) pgpF.nextObject();
+            InputStream clear = ((PGPPBEEncryptedData) enc.get(0)).getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(pass.toCharArray()));
+
+            onResponse.onResponse(clear);
+            return true;
+        }
+        catch (PGPDataValidationException e) {
+            // Wrong password
+            return false;
+        }
+        catch (IOException | PGPException e) {
+            onResponse.onError(e.getMessage(), e);
+            return true;
+        }
+    }
+
+    private void FindPassword(final GitInterface.OnResponseListener<InputStream> 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(result, onResponse)) {
+                    fPasswordGetter.WrongPassword();
+                    fPasswordGetter.GetPassword(this);
+                }
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                onResponse.onError(msg, e);
+            }
+        };
+        fPasswordGetter.GetPassword(onResp);
+    }
+
+    private void DecryptFile(final GitInterface.OnResponseListener<byte[]> resp) {
+
+        FindPassword(new GitInterface.OnResponseListener<InputStream>() {
+            @Override
+            public void onResponse(InputStream clear) {
+                try {
+                    JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
+                    PGPCompressedData cData = (PGPCompressedData) pgpFact.nextObject();
+                    pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
+                    PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
+                    resp.onResponse(Streams.readAll(ld.getInputStream()));
+                }
+                catch (IOException |PGPException e) {
+                    resp.onError(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                resp.onError(msg, e);
+            }
+        });
+    }
+
+    private void CryptFile(OutputStream fileOutStream, byte[] data) throws IOException, PGPException {
+        ByteArrayOutputStream compressedDataStream = new ByteArrayOutputStream();
+        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.UNCOMPRESSED);
+        OutputStream cos = comData.open(compressedDataStream);
+        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
+
+        OutputStream  pOut = lData.open(cos, // the compressed output stream
+                PGPLiteralData.BINARY,
+                PGPLiteralData.CONSOLE,  // "filename" to store
+                data.length, // length of clear data
+                new Date()  // current time
+        );
+
+        pOut.write(data);
+        pOut.close();
+        comData.close();
+
+        byte []compressedData = compressedDataStream.toByteArray();
+        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(passwordWrite.toCharArray()).setProvider("BC"));
+        OutputStream encOut = encGen.open(fileOutStream, compressedData.length);
+
+        encOut.write(compressedData);
+        encOut.close();
+        out.close();
+        fileOutStream.close();
     }
 
     @Override
-    public void ReadFile(GitInterface.OnResponseListener<String> onResp) {
-        throw new UnsupportedOperationException();
-        //FIXME
+    public void ReadFile(final GitInterface.OnResponseListener<String> resp) {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        Security.addProvider(new BouncyCastleProvider());
+
+        if (fFile.length() == 0) {
+            resp.onResponse("");
+            return;
+        }
+        try {
+            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) {
-        throw new UnsupportedOperationException();
-        //FIXME
+    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        Security.addProvider(new BouncyCastleProvider());
+
+        try {
+            CryptFile(new FileOutputStream(fFile), content.getBytes());
+        }
+        catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+            return;
+        }
+        resp.onResponse(null);
     }
 }

+ 26 - 0
app/src/main/java/info/knacki/pass/io/PathUtils.java

@@ -0,0 +1,26 @@
+package info.knacki.pass.io;
+
+import android.content.Context;
+import android.os.Environment;
+
+import java.io.File;
+
+public class PathUtils {
+    private static final String DATA_DIR_NAME = "pass";
+    private static final String DATA_GIT_LOCAL = "gitfiles";
+
+    private static String GetAppRootDir(Context ctx) {
+        File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/pass");
+        f.mkdir();
+        return f.getAbsolutePath();
+        //return ctx.getFilesDir().getAbsolutePath();
+    }
+
+    public static String GetPassDir(Context ctx) {
+        return GetAppRootDir(ctx) +"/" +DATA_DIR_NAME;
+    }
+
+    public static String GetGitFile(Context ctx) {
+        return GetAppRootDir(ctx) +"/" +DATA_GIT_LOCAL;
+    }
+}

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

@@ -37,9 +37,15 @@ class RawFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content) throws IOException {
-        FileWriter writer = new FileWriter(fFile);
-        writer.write(content);
-        writer.close();
+    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+        try {
+            FileWriter writer = new FileWriter(fFile);
+            writer.write(content);
+            writer.close();
+            resp.onResponse(null);
+        }
+        catch (IOException e) {
+            resp.onError(e.getMessage(), e);
+        }
     }
 }

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

@@ -2,6 +2,7 @@ package info.knacki.pass.settings.ui;
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Build;
@@ -15,15 +16,19 @@ import android.support.v7.app.ActionBar;
 import android.preference.PreferenceFragment;
 import android.view.MenuItem;
 import android.support.v4.app.NavUtils;
+import android.view.View;
 import android.widget.Toast;
 
 import info.knacki.pass.R;
 import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.git.GitInterfaceFactory;
 import info.knacki.pass.git.entities.GitRef;
+import info.knacki.pass.io.PathUtils;
+import info.knacki.pass.ui.AlertText;
 import info.knacki.pass.ui.GitPullActivity;
 import info.knacki.pass.settings.SettingsManager;
 
+import java.io.File;
 import java.util.HashMap;
 import java.util.List;
 import java.util.logging.Logger;
@@ -109,6 +114,17 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 || GPGPreferenceFragment.class.getName().equals(fragmentName);
     }
 
+    static void rmdir(File f) {
+        if (!f.isDirectory()) {
+            f.delete();
+        }
+        else {
+            for (File i : f.listFiles())
+                rmdir(i);
+            f.delete();
+        }
+    }
+
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     public static class GeneralPreferenceFragment extends PreferenceFragment {
         @Override
@@ -124,6 +140,32 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     return true;
                 }
             });
+
+            findPreference(getResources().getString(R.string.id_removeall)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    new AlertText(getActivity())
+                            .setTitle(R.string.are_you_sure)
+                            .setView(new AlertText.TextView(getActivity())
+                                .SetText(R.string.about_to_rm_root_star))
+                            .setPositiveButton(R.string.ok, new AlertText.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialogInterface, View view) {
+                                    new File(PathUtils.GetGitFile(getActivity())).delete();
+                                    File f = new File(PathUtils.GetPassDir(getActivity()));
+                                    rmdir(f);
+                                    f.mkdir();
+                                }
+                            })
+                            .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialogInterface, View view) {
+                                }
+                            })
+                            .show();
+                    return true;
+                }
+            });
         }
 
         @Override

+ 15 - 3
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.support.v7.widget.AppCompatTextView;
 import android.text.InputType;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -13,7 +14,7 @@ public class AlertText {
     protected final AlertDialog.Builder fAlertBuilder;
     protected View fView = null;
 
-    interface OnClickListener {
+    public interface OnClickListener {
         void onClick(DialogInterface dialogInterface, View view);
     }
 
@@ -34,7 +35,7 @@ public class AlertText {
         }
 
         public SimpleTextEdit SetPassword() {
-            setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+            setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
             return this;
         }
     }
@@ -80,7 +81,18 @@ public class AlertText {
         }
     }
 
-    AlertText(Context c) {
+    public static class TextView extends AppCompatTextView {
+        public TextView(Context c) {
+            super(c);
+        }
+
+        public TextView SetText(int str) {
+            setText(str);
+            return this;
+        }
+    }
+
+    public AlertText(Context c) {
         fAlertBuilder = new AlertDialog.Builder(c);
     }
 

+ 17 - 9
app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java

@@ -47,15 +47,23 @@ public class EditPasswordActivity extends AppCompatActivity {
         findViewById(R.id.saveButton).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                try {
-                    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();
-                    log.log(Level.WARNING, e.getMessage(), e);
-                    return;
-                }
-                EditPasswordActivity.this.finish();
+                FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, new PasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(fTextEdit.getText().toString(), new GitInterface.OnResponseListener<Void>() {
+                    @Override
+                    public void onResponse(Void result) {
+                        EditPasswordActivity.this.finish();
+                    }
+
+                    @Override
+                    public void onError(final String msg, final Throwable e) {
+                        EditPasswordActivity.this.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                                log.log(Level.WARNING, msg, e);
+                            }
+                        });
+                    }
+                });
             }
         });
         boolean editMode = getIntent().getBooleanExtra(INTENT_EDIT, true);

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

@@ -23,6 +23,7 @@ import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.git.GitInterfaceFactory;
 import info.knacki.pass.git.GitLocal;
 import info.knacki.pass.git.entities.GitObject;
+import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 
 public class GitPullActivity extends AppCompatActivity {
@@ -43,7 +44,7 @@ public class GitPullActivity extends AppCompatActivity {
             finish();
             return;
         }
-        LOCALGIT_HASH_VERSION_FILE = getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_git_local);
+        LOCALGIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
         fGitInterfage = GitInterfaceFactory.factory(this, (SettingsManager.Git) versioning);
         if (fGitInterfage != null) {
             fGitInterfage.FetchTree(new GitInterface.OnStreamResponseListener<GitObject.GitTree>() {
@@ -89,7 +90,7 @@ public class GitPullActivity extends AppCompatActivity {
 
     public String getMd5OfFile(String filePath)
     {
-        return getMd5OfFile(new File(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filePath));
+        return getMd5OfFile(new File(PathUtils.GetPassDir(this) +filePath));
     }
 
     public String getMd5OfFile(File f)
@@ -274,7 +275,7 @@ public class GitPullActivity extends AppCompatActivity {
             if (files.peek().getValue() == null) {
                 // remove file
                 String filename = files.pop().getKey();
-                new File(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filename).delete();
+                new File(PathUtils.GetPassDir(this) +filename).delete();
                 localCache.remove(filename);
                 DownloadNext(files, localCache, downloader, resp);
             } else {
@@ -286,7 +287,7 @@ public class GitPullActivity extends AppCompatActivity {
     }
 
     void WriteFile(String filename, final GitLocal localVersion, GitObject.GitBlob blob, byte[] result) {
-        File f = new File(getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name) +filename);
+        File f = new File(PathUtils.GetPassDir(this) +filename);
         f.getParentFile().mkdirs();
         final int chunkSize = 1024;
         final int nbChunk = (int) Math.ceil((double) result.length / chunkSize);

+ 24 - 9
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -13,7 +13,11 @@ import android.widget.ScrollView;
 import android.widget.Toast;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -21,8 +25,10 @@ import info.knacki.pass.R;
 import info.knacki.pass.bridge.BridgeUtils;
 import info.knacki.pass.generator.PasswordGenerator;
 import info.knacki.pass.generator.ui.PasswordGeneratorWizard;
+import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.io.FileInterfaceFactory;
 import info.knacki.pass.io.IFileInterface;
+import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.settings.ui.SettingsActivity;
 
@@ -115,7 +121,7 @@ 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);
+            requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
         }
     }
 
@@ -131,7 +137,7 @@ 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, PathUtils.GetPassDir(this));
         ((ScrollView)findViewById(R.id.passwordListContainer)).addView(vPasswordListView);
 
         requestPermissions();
@@ -174,13 +180,22 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
                 @Override
                 public void onClick(DialogInterface dialogInterface, View view) {
                     IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, new PasswordPicker(MainActivity.this), f);
-                    try {
-                        writer.WriteFile(PasswordGenerator.generate(wiz));
-                    }
-                    catch (IOException e) {
-                        Toast.makeText(MainActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
-                        log.log(Level.WARNING, e.getMessage(), e);
-                    }
+                    writer.WriteFile(PasswordGenerator.generate(wiz), new GitInterface.OnResponseListener<Void>() {
+                        @Override
+                        public void onResponse(Void result) {
+                        }
+
+                        @Override
+                        public void onError(final String msg, final Throwable e) {
+                            MainActivity.this.runOnUiThread(new Runnable() {
+                                @Override
+                                public void run() {
+                                    Toast.makeText(MainActivity.this, "Error: " +msg, Toast.LENGTH_LONG).show();
+                                    log.log(Level.WARNING, msg, e);
+                                }
+                            });
+                        }
+                    });
                 }
             })
             .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {

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

@@ -100,11 +100,11 @@ public class PasswordListView<T extends Context & PasswordClickListener> extends
         ClearView();
         SortedSet<FileIdent> files = new TreeSet<>();
 
-        for (File i: f.listFiles()) {
-            if (IsAccessGranted(i)) {
-                files.add(new FileIdent(i.isDirectory(), i.getName()));
-            }
-        }
+        File[] fileArr = f.listFiles();
+        if (fileArr != null)
+            for (File i: fileArr)
+                if (IsAccessGranted(i))
+                    files.add(new FileIdent(i.isDirectory(), i.getName()));
         if (!f.getAbsolutePath().equals(fRootPath)) {
             addView(CreateView(new FileIdent(true, "Parent").setParent()));
         }

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

@@ -53,4 +53,5 @@
     <string name="enter_password">Mot de passe</string>
     <string name="wrongPassword">Mot de passe erroné</string>
     <string name="ok">OK</string>
+    <string name="pref_title_remove_all">Supprimer toutes les données</string>
 </resources>

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

@@ -53,4 +53,7 @@
     <string name="enter_password">Enter password</string>
     <string name="wrongPassword">Wrong password</string>
     <string name="ok">OK</string>
+    <string name="pref_title_remove_all">Remove all data</string>
+    <string name="are_you_sure">Are you sure ?</string>
+    <string name="about_to_rm_root_star">You are about to remove all password from the password store</string>
 </resources>

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

@@ -1,8 +1,5 @@
 <resources>
     <string name="app_name">pass</string>
-    <string name="data_dir_name">pass</string>
-    <string name="data_git_local">gitfiles</string>
-
     <string-array name="pref_enctype_values">
         <item>0</item>
         <item>1</item>
@@ -22,4 +19,5 @@
     <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>
+    <string name="id_removeall">id_removeall</string>
 </resources>

+ 1 - 0
app/src/main/res/xml/pref_general.xml

@@ -1,3 +1,4 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
     <Preference android:title="@string/pref_title_system_keyboard_settings" android:key="@string/id_softSettings"/>
+    <Preference android:title="@string/pref_title_remove_all" android:key="@string/id_removeall"/>
 </PreferenceScreen>