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