Sfoglia il codice sorgente

Merge branch 'secretpasswd' of isundil/pass into master

isundil 7 anni fa
parent
commit
dbe9b2cad9
23 ha cambiato i file con 241 aggiunte e 129 eliminazioni
  1. 1 1
      app/src/main/AndroidManifest.xml
  2. 31 0
      app/src/main/java/info/knacki/pass/Sha1Builder.java
  3. 3 10
      app/src/main/java/info/knacki/pass/git/GitInterface.java
  4. 5 3
      app/src/main/java/info/knacki/pass/git/HttpGitProtocol.java
  5. 2 2
      app/src/main/java/info/knacki/pass/input/InputService.java
  6. 1 2
      app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java
  7. 61 5
      app/src/main/java/info/knacki/pass/io/FingerprintFileInterface.java
  8. 3 4
      app/src/main/java/info/knacki/pass/io/GPGFileInterface.java
  9. 58 59
      app/src/main/java/info/knacki/pass/io/GPGUtil.java
  10. 3 4
      app/src/main/java/info/knacki/pass/io/IFileInterface.java
  11. 7 0
      app/src/main/java/info/knacki/pass/io/OnResponseListener.java
  12. 5 0
      app/src/main/java/info/knacki/pass/io/OnStreamResponseListener.java
  13. 8 10
      app/src/main/java/info/knacki/pass/io/PasswordFileInterface.java
  14. 2 5
      app/src/main/java/info/knacki/pass/io/RawFileInterface.java
  15. 3 2
      app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java
  16. 3 3
      app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java
  17. 11 9
      app/src/main/java/info/knacki/pass/ui/GitPullActivity.java
  18. 4 4
      app/src/main/java/info/knacki/pass/ui/MainActivity.java
  19. 6 6
      app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPicker.java
  20. 24 0
      app/src/main/res/drawable/ic_sync.xml
  21. BIN
      app/src/main/res/drawable/sym_keyboard_delete.png
  22. BIN
      app/src/main/res/drawable/sym_keyboard_shift.png
  23. BIN
      app/src/main/res/drawable/sym_keyboard_space.png

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

@@ -5,7 +5,7 @@
     <uses-permission android:name="android.permission.INTERNET"/>
 
     <!-- For fingerprint unlock -->
-    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
     <!-- For GPG keys -->
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

+ 31 - 0
app/src/main/java/info/knacki/pass/Sha1Builder.java

@@ -0,0 +1,31 @@
+package info.knacki.pass;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class Sha1Builder {
+    private final MessageDigest sha1Builder;
+
+    Sha1Builder() {
+        MessageDigest sha1Builder;
+        try {
+            sha1Builder = MessageDigest.getInstance("SHA1");
+        } catch (NoSuchAlgorithmException e) {
+            sha1Builder = null;
+        }
+        this.sha1Builder = sha1Builder;
+    }
+
+    public Sha1Builder Write(String in) {
+        sha1Builder.update(in.getBytes());
+        return this;
+    }
+
+    public byte[] GetHash() {
+        return sha1Builder.digest();
+    }
+
+    public static byte[] Hash(String in) {
+        return new Sha1Builder().Write(in).GetHash();
+    }
+}

+ 3 - 10
app/src/main/java/info/knacki/pass/git/GitInterface.java

@@ -3,17 +3,10 @@ package info.knacki.pass.git;
 import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitObject;
 import info.knacki.pass.git.entities.GitRef;
+import info.knacki.pass.io.OnResponseListener;
+import info.knacki.pass.io.OnStreamResponseListener;
 
 public interface GitInterface {
-    interface OnResponseListener<T> {
-        void onResponse(T result);
-
-        void onError(String msg, Throwable e);
-    }
-
-    interface OnStreamResponseListener<T> extends OnResponseListener<T> {
-        void onMsg(String message);
-    }
 
     void GetRefs(OnResponseListener<GitRef[]> callback);
 
@@ -25,5 +18,5 @@ public interface GitInterface {
 
     void FetchBlob(GitObject.GitBlob blob, OnResponseListener<byte[]> response);
 
-    void PushBlobs(GitCommit.Builder commitBuilder, GitInterface.OnStreamResponseListener<Void> resp);
+    void PushBlobs(GitCommit.Builder commitBuilder, OnStreamResponseListener<Void> resp);
 }

+ 5 - 3
app/src/main/java/info/knacki/pass/git/HttpGitProtocol.java

@@ -29,6 +29,8 @@ import info.knacki.pass.git.entities.GitObject;
 import info.knacki.pass.git.entities.GitPackable;
 import info.knacki.pass.git.entities.GitPackableUtil;
 import info.knacki.pass.git.entities.GitRef;
+import info.knacki.pass.io.OnResponseListener;
+import info.knacki.pass.io.OnStreamResponseListener;
 import info.knacki.pass.io.OutputStreamWithCheckSum;
 import info.knacki.pass.settings.SettingsManager;
 
@@ -383,7 +385,7 @@ class HttpGitProtocol implements GitInterface {
     }
 
     public void FetchHead(final OnStreamResponseListener<GitCommit> response) {
-        GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
+        GetRefs(new OnResponseListener<GitRef[]>() {
             @Override
             public void onResponse(final GitRef[] result) {
                 GitRef myref = null;
@@ -468,8 +470,8 @@ class HttpGitProtocol implements GitInterface {
         return true;
     }
 
-    public void PushBlobs(final GitCommit.Builder commit, final GitInterface.OnStreamResponseListener<Void> response) {
-        GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
+    public void PushBlobs(final GitCommit.Builder commit, final OnStreamResponseListener<Void> response) {
+        GetRefs(new OnResponseListener<GitRef[]>() {
             @Override
             public void onResponse(final GitRef[] result) {
                 GitRef myref = null;

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

@@ -16,8 +16,8 @@ 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.io.OnResponseListener;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.ui.MainActivity;
 import info.knacki.pass.ui.passwordList.PasswordClickListener;
@@ -66,7 +66,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
 
     @Override
     public void OnPasswordClicked(File f) {
-        FileInterfaceFactory.GetFileInterface(this, new ServicePasswordPicker(this, fInputView), f).ReadFile(new GitInterface.OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, new ServicePasswordPicker(this, fInputView), f).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(String passwordContent) {
                 for (char i: passwordContent.toCharArray()) {

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

@@ -4,12 +4,11 @@ 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 GetPassword(OnResponseListener<String> onPassword);
         void WrongPassword();
     }
 

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

@@ -1,27 +1,83 @@
 package info.knacki.pass.io;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
+import android.support.v4.os.CancellationSignal;
 
 import java.io.File;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.logging.Logger;
 
-import info.knacki.pass.git.GitInterface;
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import info.knacki.pass.Sha1Builder;
 
 class FingerprintFileInterface implements IFileInterface {
     private final File fFile;
+    private final Context fContext;
+
+    public class CryptoObject extends FingerprintManagerCompat.CryptoObject {
+        public CryptoObject(@NonNull Cipher cipher) {
+            super(cipher);
+        }
+    }
 
     FingerprintFileInterface(Context c, File f) {
         fFile = f;
+        fContext = c;
     }
 
     @Override
-    public void ReadFile(GitInterface.OnResponseListener<String> onResp) {
+    public void ReadFile(OnResponseListener<String> onResp) {
         //FIXME
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
-        //FIXME
-        throw new UnsupportedOperationException();
+    public void WriteFile(String content, OnResponseListener<Void> resp) {
+        CancellationSignal cancelSignal = new CancellationSignal();
+        final Cipher cipher;
+        Security.removeProvider("BC");
+        /*
+            AndroidNSSP
+2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidOpenSSL
+2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> CertPathProvider
+2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidKeyStoreBCWorkaround
+2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> BC
+2018-10-07 16:21:18.583 24744-24744/info.knacki.pass E/null: >> Provider >> HarmonyJSSE
+2018-10-07 16:21:18.583 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidKeyStore
+         */
+        try {
+            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Arrays.copyOf(Sha1Builder.Hash("pass"), 16), "AES"));
+        } catch (Throwable e) {
+            resp.onError(e.getMessage(), e);
+            return;
+        }
+        Logger.getAnonymousLogger().severe("Coucou");
+        FingerprintManagerCompat.from(fContext).authenticate(this.new CryptoObject(cipher), 0, cancelSignal, new FingerprintManagerCompat.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errMsgId, CharSequence errString) {
+                super.onAuthenticationError(errMsgId, errString);
+            }
+
+            @Override
+            public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+                super.onAuthenticationHelp(helpMsgId, helpString);
+            }
+
+            @Override
+            public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
+                super.onAuthenticationSucceeded(result);
+            }
+
+            @Override
+            public void onAuthenticationFailed() {
+                super.onAuthenticationFailed();
+            }
+        }, null);
     }
 }

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

@@ -7,7 +7,6 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.nio.charset.Charset;
 
-import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.settings.SettingsManager;
 
 class GPGFileInterface implements IFileInterface {
@@ -22,7 +21,7 @@ class GPGFileInterface implements IFileInterface {
     }
 
     @Override
-    public void ReadFile(final GitInterface.OnResponseListener<String> resp) {
+    public void ReadFile(final OnResponseListener<String> resp) {
         if (fFile.length() == 0) {
             resp.onResponse("");
             return;
@@ -30,7 +29,7 @@ class GPGFileInterface implements IFileInterface {
         try {
             if (!SettingsManager.HasGPGKey(fContext))
                 throw new FileNotFoundException("GPG key not set");
-            GPGUtil.DecryptFile(fContext, fPasswordGetter, fFile, new GitInterface.OnResponseListener<byte[]>() {
+            GPGUtil.DecryptFile(fContext, fPasswordGetter, fFile, new OnResponseListener<byte[]>() {
                 @Override
                 public void onResponse(byte[] result) {
                     resp.onResponse(new String(result, Charset.defaultCharset()));
@@ -47,7 +46,7 @@ class GPGFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+    public void WriteFile(String content, OnResponseListener<Void> resp) {
         try {
             if (!SettingsManager.HasGPGKey(fContext))
                 throw new FileNotFoundException("GPG key not set");

+ 58 - 59
app/src/main/java/info/knacki/pass/io/GPGUtil.java

@@ -1,18 +1,16 @@
 package info.knacki.pass.io;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.CompressionAlgorithmTags;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
 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.PGPKeyPair;
-import org.bouncycastle.openpgp.PGPKeyRingGenerator;
 import org.bouncycastle.openpgp.PGPLiteralData;
 import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPObjectFactory;
@@ -22,13 +20,13 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
 import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
 import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
 import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
 import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
-import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
 import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
 import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
@@ -40,6 +38,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -47,19 +46,19 @@ import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.settings.SettingsManager;
 
 public class GPGUtil {
     private final static Logger log = Logger.getLogger(GPGUtil.class.getName());
 
     public static class MalformedKeyException extends IOException {
-        public final Throwable fCause;
+        final Throwable fCause;
 
-        public MalformedKeyException(Throwable e) {
+        MalformedKeyException(Throwable e) {
             fCause = e;
         }
 
@@ -75,20 +74,22 @@ public class GPGUtil {
     }
 
     private static class PGPPrivateKeyAndPass {
-        public final PGPPrivateKey fPrivateKey;
-        public final String fPass;
+        final PGPPrivateKey fPrivateKey;
+        final String fPass;
 
-        public PGPPrivateKeyAndPass(PGPPrivateKey priv, String key) {
+        PGPPrivateKeyAndPass(PGPPrivateKey priv, String key) {
             fPrivateKey = priv;
             fPass = key;
         }
     }
 
+    private static PBESecretKeyDecryptor CreateDecryptor(char[] pass) throws PGPException {
+        return new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(pass);
+    }
+
     private static PGPPrivateKeyAndPass TryPassword(PGPSecretKey pgpSecKey, String pass) {
         try {
-            PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(pass.toCharArray()));
-            PGPPrivateKeyAndPass res = new PGPPrivateKeyAndPass(pgpPrivKey, pass);
-            return res;
+            return TryPasswordUnsafe(pgpSecKey, pass);
         } catch (PGPException e) {
             // Wrong password
             log.log(Level.INFO, e.getMessage() + " (wrong password ?)", e);
@@ -96,13 +97,18 @@ public class GPGUtil {
         }
     }
 
-    private static void FindPassword(final FileInterfaceFactory.PasswordGetter passwordGetter, final PGPSecretKey pgpSecKey, final GitInterface.OnResponseListener<PGPPrivateKeyAndPass> resp) {
+    private static @NonNull
+    PGPPrivateKeyAndPass TryPasswordUnsafe(PGPSecretKey pgpSecKey, String pass) throws PGPException {
+        return new PGPPrivateKeyAndPass(pgpSecKey.extractPrivateKey(CreateDecryptor(pass.toCharArray())), pass);
+    }
+
+    private static void FindPassword(final FileInterfaceFactory.PasswordGetter passwordGetter, final PGPSecretKey pgpSecKey, final OnResponseListener<PGPPrivateKeyAndPass> resp) {
         PGPPrivateKeyAndPass priv = TryPassword(pgpSecKey, "");
 
         if (priv != null) {
             resp.onResponse(priv);
         } else {
-            GitInterface.OnResponseListener<String> onResp = new GitInterface.OnResponseListener<String>() {
+            OnResponseListener<String> onResp = new OnResponseListener<String>() {
                 @Override
                 public void onResponse(String result) {
                     if (result == null) {
@@ -164,36 +170,35 @@ public class GPGUtil {
         return null;
     }
 
-    private static InputStream getKeyInputStream(Context ctx) {
-        return SettingsManager.GetGPGKeyContent(ctx);
+    private static @NonNull
+    InputStream getKeyInputStream(Context ctx) throws FileNotFoundException {
+        InputStream ss = SettingsManager.GetGPGKeyContent(ctx);
+        if (ss == null)
+            throw new FileNotFoundException("GPG key");
+        return ss;
     }
 
-    private static PGPSecretKey findSecretKey(InputStream in) throws IOException {
-        in = PGPUtil.getDecoderStream(in);
-        PGPSecretKeyRingCollection pgpSec;
+    private static @NonNull
+    PGPSecretKeyRing findSecretKeyring(@NonNull InputStream in) throws IOException {
+        final PGPSecretKeyRingCollection pgpSec;
+
         try {
-            pgpSec = new PGPSecretKeyRingCollection(in, new JcaKeyFingerprintCalculator());
+            pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());
         } catch (PGPException e) {
             throw new MalformedKeyException(e);
         }
-        Iterator rIt = pgpSec.getKeyRings();
-
-        while (rIt.hasNext()) {
-            PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next();
-            Iterator kIt = kRing.getSecretKeys();
-
-            while (kIt.hasNext()) {
-                PGPSecretKey k = (PGPSecretKey) kIt.next();
+        Iterator<PGPSecretKeyRing> rIt = pgpSec.getKeyRings();
+        if (rIt.hasNext())
+            return rIt.next();
+        throw new NoSuchElementException("GPG key not found");
+    }
 
-                if (k.isSigningKey()) {
-                    return k;
-                }
-            }
-        }
-        throw new IllegalArgumentException("Can't find signing key in key ring.");
+    private static @NonNull
+    PGPSecretKey findSecretKey(@NonNull InputStream in) throws IOException {
+        return findSecretKeyring(in).getSecretKey();
     }
 
-    private static void DoDecryptFile(PGPPrivateKeyAndPass sKey, InputStream dataStream, GitInterface.OnResponseListener<byte[]> resp) {
+    private static void DoDecryptFile(PGPPrivateKeyAndPass sKey, InputStream dataStream, OnResponseListener<byte[]> resp) {
         byte[] output;
         try {
             InputStream ss = getFirstLiteralDataInputStream(dataStream, sKey);
@@ -212,7 +217,7 @@ public class GPGUtil {
         resp.onResponse(output);
     }
 
-    public static void DecryptFile(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final File file, final GitInterface.OnResponseListener<byte[]> resp) throws IOException {
+    public static void DecryptFile(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final File file, final OnResponseListener<byte[]> resp) throws IOException {
         PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(file)), new JcaKeyFingerprintCalculator());
         Object o = pgpF.nextObject();
         final Iterator<?> it = ((o instanceof PGPEncryptedDataList) ? (PGPEncryptedDataList) o : (PGPEncryptedDataList) pgpF.nextObject()).getEncryptedDataObjects();
@@ -223,7 +228,7 @@ public class GPGUtil {
             FindPassword(
                     passwordGetter,
                     findSecretKey(getKeyInputStream(ctx)),
-                    new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
+                    new OnResponseListener<PGPPrivateKeyAndPass>() {
                         @Override
                         public void onResponse(PGPPrivateKeyAndPass sKey) {
                             try {
@@ -241,7 +246,7 @@ public class GPGUtil {
         }
     }
 
-    public static void CryptFile(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final OutputStream fileOutStream, final byte[] data, final GitInterface.OnResponseListener<Void> resp) {
+    public static void CryptFile(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final OutputStream fileOutStream, final byte[] data, final OnResponseListener<Void> resp) {
         try {
             ByteArrayOutputStream compressedDataStream = new ByteArrayOutputStream();
             PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.UNCOMPRESSED);
@@ -281,34 +286,29 @@ public class GPGUtil {
         }
     }
 
-    private static PGPSecretKeyRing DoChangePassword(PGPSecretKey sec, PGPPrivateKeyAndPass sKey, String newPassword) throws PGPException {
-        return new PGPKeyRingGenerator(
-                PGPSignature.POSITIVE_CERTIFICATION,
-                new PGPKeyPair(sec.getPublicKey(), sKey.fPrivateKey),
-                "" + sec.getKeyID(),
-                new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1),
-                null,
-                null,
-                new JcaPGPContentSignerBuilder(sec.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
-                new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).setProvider("BC").build(newPassword.toCharArray()))
-                .generateSecretKeyRing();
+    private static PGPSecretKeyRing DoChangePassword(PGPSecretKeyRing secKeyring, String oldPassword, String newPassword) throws PGPException {
+        final PBESecretKeyDecryptor keyDecryptor = CreateDecryptor(oldPassword.toCharArray());
+        final PBESecretKeyEncryptor newEncryptor = new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).setProvider("BC").build(newPassword.toCharArray());
+        return PGPSecretKeyRing.copyWithNewPassword(secKeyring, keyDecryptor, newEncryptor);
     }
 
-    public static void ChangePassword(final Context ctx, final FileInterfaceFactory.ChangePasswordGetter passwordGetter, final GitInterface.OnResponseListener<Void> onDone) throws IOException {
-        final PGPSecretKey secretKey = findSecretKey(getKeyInputStream(ctx));
+    public static void ChangePassword(final Context ctx, final FileInterfaceFactory.ChangePasswordGetter passwordGetter, final OnResponseListener<Void> onDone) throws IOException {
+        final PGPSecretKeyRing secretKeyring = findSecretKeyring(getKeyInputStream(ctx));
+        final PGPSecretKey signingKey = secretKeyring.getSecretKey();
+
         FindPassword(
                 passwordGetter.SetStep(FileInterfaceFactory.ChangePasswordGetter.eStep.eOldPassword),
-                secretKey,
-                new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
+                signingKey,
+                new OnResponseListener<PGPPrivateKeyAndPass>() {
                     @Override
                     public void onResponse(final PGPPrivateKeyAndPass sKey) {
-                        passwordGetter.SetStep(FileInterfaceFactory.ChangePasswordGetter.eStep.eNewPassword).GetPassword(new GitInterface.OnResponseListener<String>() {
+                        passwordGetter.SetStep(FileInterfaceFactory.ChangePasswordGetter.eStep.eNewPassword).GetPassword(new OnResponseListener<String>() {
                             @Override
                             public void onResponse(String result) {
                                 if (result != null) {
                                     try {
                                         ArrayList<PGPSecretKeyRing> keyringCollection = new ArrayList<>();
-                                        keyringCollection.add(DoChangePassword(secretKey, sKey, result));
+                                        keyringCollection.add(DoChangePassword(secretKeyring, sKey.fPass, result));
                                         ByteArrayOutputStream stream = new ByteArrayOutputStream();
                                         new PGPSecretKeyRingCollection(keyringCollection).encode(stream);
                                         SettingsManager.SetGPGKeyContent(ctx, SettingsManager.GPG_GENERATED, new ByteArrayInputStream(stream.toByteArray()));
@@ -333,7 +333,6 @@ public class GPGUtil {
     }
 
     public static boolean CheckIsPasswordProtected(Context ctx) throws IOException {
-        final PGPSecretKey secretKey = findSecretKey(getKeyInputStream(ctx));
-        return TryPassword(secretKey, "") == null;
+        return TryPassword(findSecretKey(getKeyInputStream(ctx)), "") == null;
     }
 }

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

@@ -1,8 +1,7 @@
 package info.knacki.pass.io;
 
-import info.knacki.pass.git.GitInterface;
-
 public interface IFileInterface {
-    void ReadFile(GitInterface.OnResponseListener<String> resp);
-    void WriteFile(String content, GitInterface.OnResponseListener<Void> resp);
+    void ReadFile(OnResponseListener<String> resp);
+
+    void WriteFile(String content, OnResponseListener<Void> resp);
 }

+ 7 - 0
app/src/main/java/info/knacki/pass/io/OnResponseListener.java

@@ -0,0 +1,7 @@
+package info.knacki.pass.io;
+
+public interface OnResponseListener<T> {
+    void onResponse(T result);
+
+    void onError(String msg, Throwable e);
+}

+ 5 - 0
app/src/main/java/info/knacki/pass/io/OnStreamResponseListener.java

@@ -0,0 +1,5 @@
+package info.knacki.pass.io;
+
+public interface OnStreamResponseListener<T> extends OnResponseListener<T> {
+    void onMsg(String message);
+}

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

@@ -32,8 +32,6 @@ import java.security.SecureRandom;
 import java.security.Security;
 import java.util.Date;
 
-import info.knacki.pass.git.GitInterface;
-
 class PasswordFileInterface implements IFileInterface {
     private final File fFile;
     private final FileInterfaceFactory.PasswordGetter fPasswordGetter;
@@ -45,7 +43,7 @@ class PasswordFileInterface implements IFileInterface {
         fPasswordGetter = passwordGetter;
     }
 
-    private boolean TryPassword(String pass, GitInterface.OnResponseListener<InputStream> onResponse) {
+    private boolean TryPassword(String pass, OnResponseListener<InputStream> onResponse) {
         try {
             InputStream in = PGPUtil.getDecoderStream(new FileInputStream(fFile));
             JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
@@ -66,8 +64,8 @@ class PasswordFileInterface implements IFileInterface {
         }
     }
 
-    private void FindPassword(final GitInterface.OnResponseListener<InputStream> onResponse) {
-        GitInterface.OnResponseListener<String> onResp = new GitInterface.OnResponseListener<String>() {
+    private void FindPassword(final OnResponseListener<InputStream> onResponse) {
+        OnResponseListener<String> onResp = new OnResponseListener<String>() {
             @Override
             public void onResponse(String result) {
                 if (result == null) {
@@ -86,9 +84,9 @@ class PasswordFileInterface implements IFileInterface {
         fPasswordGetter.GetPassword(onResp);
     }
 
-    private void DecryptFile(final GitInterface.OnResponseListener<byte[]> resp) {
+    private void DecryptFile(final OnResponseListener<byte[]> resp) {
 
-        FindPassword(new GitInterface.OnResponseListener<InputStream>() {
+        FindPassword(new OnResponseListener<InputStream>() {
             @Override
             public void onResponse(InputStream clear) {
                 try {
@@ -141,7 +139,7 @@ class PasswordFileInterface implements IFileInterface {
     }
 
     @Override
-    public void ReadFile(final GitInterface.OnResponseListener<String> resp) {
+    public void ReadFile(final OnResponseListener<String> resp) {
         Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
         Security.addProvider(new BouncyCastleProvider());
 
@@ -150,7 +148,7 @@ class PasswordFileInterface implements IFileInterface {
             return;
         }
         try {
-            DecryptFile(new GitInterface.OnResponseListener<byte[]>() {
+            DecryptFile(new OnResponseListener<byte[]>() {
                 @Override
                 public void onResponse(byte[] result) {
                     resp.onResponse(new String(result, Charset.defaultCharset()));
@@ -167,7 +165,7 @@ class PasswordFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+    public void WriteFile(String content, OnResponseListener<Void> resp) {
         Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
         Security.addProvider(new BouncyCastleProvider());
 

+ 2 - 5
app/src/main/java/info/knacki/pass/io/RawFileInterface.java

@@ -6,9 +6,6 @@ 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 {
     private static File fFile;
 
@@ -17,7 +14,7 @@ class RawFileInterface implements IFileInterface {
     }
 
     @Override
-    public void ReadFile(GitInterface.OnResponseListener<String> resp) {
+    public void ReadFile(OnResponseListener<String> resp) {
         try {
             BufferedReader buf = new BufferedReader(new FileReader(fFile));
             StringBuilder result = new StringBuilder();
@@ -37,7 +34,7 @@ class RawFileInterface implements IFileInterface {
     }
 
     @Override
-    public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
+    public void WriteFile(String content, OnResponseListener<Void> resp) {
         try {
             FileWriter writer = new FileWriter(fFile);
             writer.write(content);

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

@@ -41,6 +41,7 @@ import info.knacki.pass.git.GitInterfaceFactory;
 import info.knacki.pass.git.entities.GitRef;
 import info.knacki.pass.io.FileUtils;
 import info.knacki.pass.io.GPGUtil;
+import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.GitPullActivity;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
@@ -388,7 +389,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             gitBranches.setEnabled(false);
             GitInterface gitInterface = GitInterfaceFactory.factory(getActivity(), versioning);
             if (gitInterface != null) {
-                gitInterface.GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
+                gitInterface.GetRefs(new OnResponseListener<GitRef[]>() {
                     @Override
                     public void onResponse(final GitRef[] result) {
                         getActivity().runOnUiThread(new Runnable() {
@@ -467,7 +468,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 @Override
                 public boolean onPreferenceClick(Preference preference) {
                     try {
-                        GPGUtil.ChangePassword(getActivity(), new PasswordPicker.ChangePasswordPicker(getActivity()), new GitInterface.OnResponseListener<Void>() {
+                        GPGUtil.ChangePassword(getActivity(), new PasswordPicker.ChangePasswordPicker(getActivity()), new OnResponseListener<Void>() {
                             @Override
                             public void onResponse(Void result) {
                                 updateGpgFileLabel();

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

@@ -15,8 +15,8 @@ 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.io.OnResponseListener;
 import info.knacki.pass.ui.passwordPicker.PasswordPicker;
 
 public class EditPasswordActivity extends AppCompatActivity {
@@ -51,7 +51,7 @@ public class EditPasswordActivity extends AppCompatActivity {
                 final Editable text = fTextEdit.getText();
                 final String textStr = text == null ? "" : text.toString();
 
-                FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, new PasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(textStr, new GitInterface.OnResponseListener<Void>() {
+                FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, new PasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(textStr, new OnResponseListener<Void>() {
                     @Override
                     public void onResponse(Void result) {
                         EditPasswordActivity.this.finish();
@@ -79,7 +79,7 @@ public class EditPasswordActivity extends AppCompatActivity {
 
     protected void populateContent(final boolean requestFocus) {
         fTextEdit.setEnabled(false);
-        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), fOutputFile).ReadFile(new GitInterface.OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), fOutputFile).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(final String pass) {
                 EditPasswordActivity.this.runOnUiThread(new Runnable() {

+ 11 - 9
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -26,6 +26,8 @@ import info.knacki.pass.git.GitLocal;
 import info.knacki.pass.git.GitSha1;
 import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitObject;
+import info.knacki.pass.io.OnResponseListener;
+import info.knacki.pass.io.OnStreamResponseListener;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
@@ -65,7 +67,7 @@ public class GitPullActivity extends AppCompatActivity {
         LOCALGIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
         fGitInterfage = GitInterfaceFactory.factory(this, (SettingsManager.Git) versioning);
         if (fGitInterfage != null) {
-            fGitInterfage.FetchHead(new GitInterface.OnStreamResponseListener<GitCommit>() {
+            fGitInterfage.FetchHead(new OnStreamResponseListener<GitCommit>() {
                 @Override
                 public void onMsg(final String msg) {
                     GitPullActivity.this.onMsg(msg);
@@ -218,7 +220,7 @@ public class GitPullActivity extends AppCompatActivity {
     }
 
     void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
-        final GitInterface.OnStreamResponseListener<Void> allDone = new GitInterface.OnStreamResponseListener<Void>() {
+        final OnStreamResponseListener<Void> allDone = new OnStreamResponseListener<Void>() {
             @Override
             public void onResponse(Void result) {
                 GitPullActivity.this.runOnUiThread(new Runnable() {
@@ -273,7 +275,7 @@ public class GitPullActivity extends AppCompatActivity {
             afterFetching.run();
         } else {
             onMsg("Updating local repository");
-            DownloadBlobs(filesToPull, localVersion, new GitInterface.OnResponseListener<Void>() {
+            DownloadBlobs(filesToPull, localVersion, new OnResponseListener<Void>() {
                 @Override
                 public void onResponse(Void result) {
                     afterFetching.run();
@@ -287,7 +289,7 @@ public class GitPullActivity extends AppCompatActivity {
         }
     }
 
-    void DownloadBlobs(Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final GitInterface.OnResponseListener<Void> resp) {
+    void DownloadBlobs(Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final OnResponseListener<Void> resp) {
         if (blobs.size() == 0) {
             resp.onResponse(null);
             return;
@@ -298,12 +300,12 @@ public class GitPullActivity extends AppCompatActivity {
             logView.append(" > " +i +"\n");
         final Stack<Map.Entry<String, GitObject.GitBlob>> files = new Stack<>();
         files.addAll(blobs.entrySet());
-        final GitInterface.OnResponseListener<byte[]> downloader = new GitInterface.OnResponseListener<byte[]>() {
+        final OnResponseListener<byte[]> downloader = new OnResponseListener<byte[]>() {
             @Override
             public void onResponse(final byte[] result) {
                 final String filename = files.peek().getKey();
                 final GitObject.GitBlob blob = files.peek().getValue();
-                final GitInterface.OnResponseListener<byte[]> _this = this;
+                final OnResponseListener<byte[]> _this = this;
 
                 GitPullActivity.this.runOnUiThread(new Runnable() {
                     @Override
@@ -339,7 +341,7 @@ public class GitPullActivity extends AppCompatActivity {
         DownloadNext(files, localVersion, downloader, resp);
     }
 
-    void DownloadNext(Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, GitInterface.OnResponseListener<byte[]> downloader, GitInterface.OnResponseListener<Void> resp) {
+    void DownloadNext(Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, OnResponseListener<byte[]> downloader, OnResponseListener<Void> resp) {
         if (!files.empty()) {
             if (files.peek().getValue() == null) {
                 // remove file
@@ -375,7 +377,7 @@ public class GitPullActivity extends AppCompatActivity {
         localVersion.SetHash(filename, GitSha1.BytesToString(blob.GetHash()));
     }
 
-    void PushBlobs(final Set<String> files, final GitLocal localVersion, final GitInterface.OnStreamResponseListener<Void> resp) {
+    void PushBlobs(final Set<String> files, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
         if (files.isEmpty()) {
             resp.onMsg("Nothing to commit");
             resp.onResponse(null);
@@ -384,7 +386,7 @@ public class GitPullActivity extends AppCompatActivity {
             final GitCommit.Builder commit = new GitCommit.Builder(fHeadCommit, config.GetUsername(), config.GetUserEmail(), COMMIT_MSG);
             for (String i : files)
                 commit.AddFile(i, new File(PathUtils.GetPassDir(this) + i));
-            fGitInterfage.PushBlobs(commit, new GitInterface.OnStreamResponseListener<Void>() {
+            fGitInterfage.PushBlobs(commit, new OnStreamResponseListener<Void>() {
                 @Override
                 public void onMsg(String message) {
                     resp.onMsg(message);

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

@@ -22,10 +22,10 @@ import java.util.logging.Logger;
 import info.knacki.pass.R;
 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.FileUtils;
 import info.knacki.pass.io.IFileInterface;
+import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.settings.ui.SettingsActivity;
@@ -87,7 +87,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
                     @Override
                     public void onClick(DialogInterface dialogInterface, View view) {
                         lastFileName = (TextEditAndCheckbox) view;
-                        String filename = lastFileName.getStr();
+                        final String filename = lastFileName.getStr();
                         if (filename.length() == 0) {
                             Toast.makeText(MainActivity.this, "Error: Empty file name", Toast.LENGTH_LONG).show();
                             return;
@@ -188,7 +188,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
                 @Override
                 public void onClick(DialogInterface dialogInterface, View view) {
                     IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, new PasswordPicker(MainActivity.this), f);
-                    writer.WriteFile(PasswordGenerator.generate(wiz), new GitInterface.OnResponseListener<Void>() {
+                    writer.WriteFile(PasswordGenerator.generate(wiz), new OnResponseListener<Void>() {
                         @Override
                         public void onResponse(Void result) {
                         }
@@ -270,7 +270,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
 
     @Override
     public void OnCopyToClipboard(File f) {
-        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), f).ReadFile(new GitInterface.OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), f).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(String result) {
                 ClipboardManager clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);

+ 6 - 6
app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPicker.java

@@ -6,8 +6,8 @@ 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;
+import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
 import info.knacki.pass.ui.alertPrompt.views.PasswordTextEdit;
 
@@ -27,7 +27,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
     }
 
     @Override
-    public void GetPassword(final GitInterface.OnResponseListener<String> onPassword) {
+    public void GetPassword(final OnResponseListener<String> onPassword) {
         AlertFactory()
             .setCancelable(true)
                 .setView(new PasswordTextEdit(fContext))
@@ -55,7 +55,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
     public static class ChangePasswordPicker extends PasswordPicker implements FileInterfaceFactory.ChangePasswordGetter {
         protected FileInterfaceFactory.ChangePasswordGetter.eStep fCurrentStep = FileInterfaceFactory.ChangePasswordGetter.eStep.eOldPassword;
         private String fFirstPassword = "";
-        private GitInterface.OnResponseListener<String> fListener;
+        private OnResponseListener<String> fListener;
 
         public ChangePasswordPicker(Context context) {
             super(context);
@@ -74,7 +74,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
             return R.string.enter_password;
         }
 
-        private GitInterface.OnResponseListener<String> fOnFirstPassEntered = new GitInterface.OnResponseListener<String>() {
+        private OnResponseListener<String> fOnFirstPassEntered = new OnResponseListener<String>() {
             @Override
             public void onResponse(String result) {
                 if (result != null) {
@@ -91,7 +91,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
             }
         };
 
-        private GitInterface.OnResponseListener<String> fOnRetypePassEntered = new GitInterface.OnResponseListener<String>() {
+        private OnResponseListener<String> fOnRetypePassEntered = new OnResponseListener<String>() {
             @Override
             public void onResponse(String result) {
                 if (result == null) {
@@ -111,7 +111,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
         };
 
         @Override
-        public void GetPassword(final GitInterface.OnResponseListener<String> onPassword) {
+        public void GetPassword(final OnResponseListener<String> onPassword) {
             fListener = onPassword;
             switch (fCurrentStep) {
                 case eOldPassword:

+ 24 - 0
app/src/main/res/drawable/ic_sync.xml

@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17.65,6.35c-1.63,-1.63 -3.94,-2.57 -6.48,-2.31c-3.67,0.37 -6.69,3.35 -7.1,7.02C3.52,15.91 7.27,20 12,20c3.19,0 5.93,-1.87 7.21,-4.57c0.31,-0.66 -0.16,-1.43 -0.89,-1.43h-0.01c-0.37,0 -0.72,0.2 -0.88,0.53c-1.13,2.43 -3.84,3.97 -6.81,3.32c-2.22,-0.49 -4.01,-2.3 -4.49,-4.52C5.31,9.44 8.26,6 12,6c1.66,0 3.14,0.69 4.22,1.78l-2.37,2.37C13.54,10.46 13.76,11 14.21,11H19c0.55,0 1,-0.45 1,-1V5.21c0,-0.45 -0.54,-0.67 -0.85,-0.35L17.65,6.35z" />
+</vector>

BIN
app/src/main/res/drawable/sym_keyboard_delete.png


BIN
app/src/main/res/drawable/sym_keyboard_shift.png


BIN
app/src/main/res/drawable/sym_keyboard_space.png