Kaynağa Gözat

Refs #21 Register Uber method on GPGStorageEngine factory

isundil 7 yıl önce
ebeveyn
işleme
2d6cd0cd04

+ 8 - 0
app/src/main/java/info/knacki/pass/io/FileUtils.java

@@ -1,6 +1,8 @@
 package info.knacki.pass.io;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
@@ -22,6 +24,12 @@ public class FileUtils {
         }
     }
 
+    public static byte[] ReadAllStream(InputStream s) throws IOException {
+        ByteArrayOutputStream str = new ByteArrayOutputStream();
+        pipe(s, str);
+        return str.toByteArray();
+    }
+
     public static String ReadAllFile(File file) throws IOException {
         BufferedReader buf = new BufferedReader(new FileReader(file));
         StringBuilder result = new StringBuilder();

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

@@ -7,6 +7,7 @@ public class PathUtils {
     @SuppressWarnings("SpellCheckingInspection")
     private static final String DATA_GIT_LOCAL = "gitfiles";
     private static final String DATA_FINGER_LOCAL = "fingerprint";
+    private static final String DATA_GPG_FILE = "key";
 
     private static String GetAppRootDir(Context ctx) {
         return ctx.getFilesDir().getAbsolutePath();
@@ -24,6 +25,10 @@ public class PathUtils {
         return GetAppRootDir(ctx) +"/" +DATA_FINGER_LOCAL;
     }
 
+    public static String GetGPGKeyFile(Context ctx) {
+        return GetAppRootDir(ctx) +"/" +DATA_GPG_FILE;
+    }
+
     public static final String TRASH_SUFFIX = ".trash";
 
     public static boolean IsHidden(String path) {

+ 210 - 0
app/src/main/java/info/knacki/pass/io/pgp/UberGPGStorage.java

@@ -0,0 +1,210 @@
+package info.knacki.pass.io.pgp;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import info.knacki.pass.io.FileUtils;
+import info.knacki.pass.io.PathUtils;
+
+public class UberGPGStorage implements GPGStorageEngine.IGPGStorage {
+    private static final Logger log = Logger.getLogger(UberGPGStorage.class.getName());
+    private final Context fContext;
+
+    private final static String KEY_PASS = UberGPGStorage.class.getName()+"___PASSWORD";
+
+    UberGPGStorage(Context ctx) {
+        fContext = ctx;
+    }
+
+    private Map<String, KeyStore.SecretKeyEntry> FindSecretKey(@NonNull byte[] in) throws IOException {
+        final PGPSecretKeyRingCollection pgpSec;
+        final HashMap<String, KeyStore.SecretKeyEntry> keyring = new HashMap<>();
+
+        try {
+            pgpSec = new PGPSecretKeyRingCollection(in, new JcaKeyFingerprintCalculator());
+        } catch (PGPException e) {
+            throw new GPGUtil.MalformedKeyException(e);
+        }
+        Iterator<PGPSecretKeyRing> rIt = pgpSec.getKeyRings();
+        while (rIt.hasNext()) {
+            for (Iterator<PGPSecretKey> it = rIt.next().getSecretKeys(); it.hasNext(); ) {
+                PGPSecretKey key = it.next();
+                keyring.put(String.format("%X", key.getKeyID()), new KeyStore.SecretKeyEntry(new SecretKeySpec(key.getEncoded(), "AES")));
+            }
+        }
+        if (keyring.isEmpty())
+            throw new NoSuchElementException("GPG key not found");
+        return keyring;
+    }
+
+    private KeyStore GetKeystore(boolean read) {
+        KeyStore ks;
+
+        try {
+            ks = KeyStore.getInstance("UBER", "BC");
+            if (read) {
+                File gpgKeyFile = new File(PathUtils.GetGPGKeyFile(fContext));
+                if (!gpgKeyFile.exists())
+                    throw new FileNotFoundException(gpgKeyFile.getName());
+                ks.load(new FileInputStream(gpgKeyFile), KEY_PASS.toCharArray());
+            } else {
+                ks.load(null, null);
+            }
+        } catch (KeyStoreException | NoSuchProviderException | IOException | CertificateException | NoSuchAlgorithmException e) {
+            log.log(Level.SEVERE, "Cannot retreive KeyStore", e);
+            ks = null;
+        }
+        return ks;
+    }
+
+    private void SaveKeystore(KeyStore ks) {
+        try {
+            File f = new File(PathUtils.GetGPGKeyFile(fContext));
+            if (!f.exists() && !f.createNewFile())
+                throw new FileNotFoundException(f.getName());
+            ks.store(new FileOutputStream(f), KEY_PASS.toCharArray());
+        }
+        catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
+            log.log(Level.SEVERE, "Cannot save keystore", e);
+        }
+    }
+
+    @Override
+    public boolean HasGPGKey() {
+        try {
+            KeyStore ks = GetKeystore(true);
+            return ks != null && ks.aliases().hasMoreElements();
+        } catch (KeyStoreException e) {
+            log.log(Level.SEVERE, "KeyStore Exception: " +e.getMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean SetGPGKeyContent(InputStream content) {
+        KeyStore ks = GetKeystore(false);
+        if (ks == null)
+            return false;
+        try {
+            Map<String, KeyStore.SecretKeyEntry> keyring = FindSecretKey(FileUtils.ReadAllStream(content));
+            for (Map.Entry<String, KeyStore.SecretKeyEntry> i: keyring.entrySet())
+                ks.setEntry(i.getKey(), i.getValue(), new KeyStore.PasswordProtection(null));
+            SaveKeystore(ks);
+        } catch (KeyStoreException | IOException e) {
+            log.log(Level.SEVERE, "Cannot set GPG key content", e);
+            return false;
+        }
+        return true;
+    }
+
+    private @NonNull Map<String, KeyStore.SecretKeyEntry> GetGPGKeyContent() throws FileNotFoundException {
+        if (!HasGPGKey())
+            throw new FileNotFoundException("GPG key");
+        KeyStore ks = GetKeystore(true);
+        if (ks == null)
+            throw new FileNotFoundException("GPG key store");
+        HashMap<String, KeyStore.SecretKeyEntry> keys = new HashMap<>();
+        try {
+            Enumeration<String> aliases = ks.aliases();
+            while (aliases.hasMoreElements()) {
+                String keyId = aliases.nextElement();
+                keys.put(keyId, (KeyStore.SecretKeyEntry) ks.getEntry(keyId, null));
+            }
+            return keys;
+        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
+            log.log(Level.SEVERE, "KeyStore Exception: " +e.getMessage(), e);
+            throw new FileNotFoundException("GPG key store");
+        }
+    }
+
+    @Override
+    public void ExportGPGKeyContent(OutputStream out) throws IOException {
+        try {
+            new PGPSecretKeyRingCollection(Collections.singleton(GetGPGKeyRing())).encode(out);
+        } catch (PGPException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public @NonNull PGPSecretKeyRing GetGPGKeyRing() throws IOException {
+        final PGPSecretKeyRingCollection pgpSec;
+
+        try {
+            Map<String, KeyStore.SecretKeyEntry> secretKeys = GetGPGKeyContent();
+            ArrayList<PGPSecretKey> keys = new ArrayList<>();
+            for (Map.Entry<String, KeyStore.SecretKeyEntry> keyData: secretKeys.entrySet()) {
+                PGPSecretKeyRingCollection key = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new ByteArrayInputStream(keyData.getValue().getSecretKey().getEncoded())), new JcaKeyFingerprintCalculator());
+                for (Iterator<PGPSecretKeyRing> it = key.getKeyRings(); it.hasNext(); ) {
+                    for (Iterator<PGPSecretKey> it1 = it.next().getSecretKeys(); it1.hasNext(); ) {
+                        PGPSecretKey pgpSecKey = it1.next();
+                        log.severe(String.format("Found key in load: %X", pgpSecKey.getKeyID()));
+                        keys.add(pgpSecKey);
+                    }
+                }
+            }
+            pgpSec = new PGPSecretKeyRingCollection(Collections.singleton(new PGPSecretKeyRing(keys)));
+        } catch (PGPException e) {
+            throw new GPGUtil.MalformedKeyException(e);
+        }
+        Iterator<PGPSecretKeyRing> rIt = pgpSec.getKeyRings();
+        if (rIt.hasNext())
+            return rIt.next();
+        throw new NoSuchElementException("GPG key not found");
+    }
+
+    @Override
+    public @NonNull PGPSecretKey GetGPGSecretKey() throws IOException {
+        return GetGPGKeyRing().getSecretKey();
+    }
+
+    @Override
+    public @NonNull PGPSecretKey GetGPGSecretKey(long keyId) throws IOException {
+        PGPSecretKeyRing kr = GetGPGKeyRing();
+        PGPSecretKey key = kr.getSecretKey(keyId);
+        if (key == null)
+            throw new IOException(String.format("Key %X", keyId));
+        return key;
+    }
+
+    @Override
+    public void RemoveGpgKey() {
+        File f = new File(PathUtils.GetGPGKeyFile(fContext));
+        if (f.exists() && !f.delete()) {
+            log.severe("Cannot remove GPG file");
+        }
+    }
+}