|
|
@@ -2,292 +2,23 @@ package info.knacki.pass.io;
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
|
-import org.bouncycastle.bcpg.ArmoredOutputStream;
|
|
|
-import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
|
|
-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.PGPPBEEncryptedData;
|
|
|
-import org.bouncycastle.openpgp.PGPPrivateKey;
|
|
|
-import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
|
|
-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.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.JcaPGPDigestCalculatorProviderBuilder;
|
|
|
-import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
|
|
-import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
|
|
-import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
|
|
-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.io.OutputStream;
|
|
|
import java.nio.charset.Charset;
|
|
|
-import java.security.SecureRandom;
|
|
|
-import java.util.Date;
|
|
|
-import java.util.Iterator;
|
|
|
-import java.util.logging.Level;
|
|
|
-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;
|
|
|
+ private final Context fContext;
|
|
|
|
|
|
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<PGPPrivateKeyAndPass> onResponse) {
|
|
|
- try {
|
|
|
- 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);
|
|
|
- }
|
|
|
- catch (PGPException e) {
|
|
|
- // Wrong password
|
|
|
- log.log(Level.INFO, e.getMessage() +" (wrong password ?)", e);
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- private boolean FindSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, final GitInterface.OnResponseListener<PGPPrivateKeyAndPass> onResponse) throws PGPException {
|
|
|
- final PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
|
|
|
-
|
|
|
- if (pgpSecKey != null) {
|
|
|
- if (TryPassword(pgpSecKey, "", onResponse))
|
|
|
- return true;
|
|
|
- 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 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 it = ((PGPEncryptedDataList) po).getEncryptedDataObjects(); it.hasNext();) {
|
|
|
- PGPEncryptedData ped = (PGPEncryptedData) it.next();
|
|
|
-
|
|
|
- if (ped instanceof PGPPublicKeyEncryptedData) {
|
|
|
- InputStream ret = getFirstLiteralDataInputStream(((PGPPublicKeyEncryptedData) ped).getDataStream(new BcPublicKeyDataDecryptorFactory(pKey.key)), pKey);
|
|
|
-
|
|
|
- if (ret != null)
|
|
|
- return ret;
|
|
|
- } 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;
|
|
|
- }
|
|
|
-
|
|
|
- private void DecryptFile(final GitInterface.OnResponseListener<byte[]> resp) throws IOException, PGPException {
|
|
|
- PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(fFile)), new JcaKeyFingerprintCalculator());
|
|
|
- Object o = pgpF.nextObject();
|
|
|
- final Iterator<?> it = ((o instanceof PGPEncryptedDataList) ? (PGPEncryptedDataList) o : (PGPEncryptedDataList) pgpF.nextObject()).getEncryptedDataObjects();
|
|
|
-
|
|
|
- while (it.hasNext()) {
|
|
|
- final PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) it.next();
|
|
|
-
|
|
|
- if (FindSecretKey(
|
|
|
- new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(fKeyFile)), new JcaKeyFingerprintCalculator()),
|
|
|
- pbe.getKeyID(),
|
|
|
- new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
|
|
|
- @Override
|
|
|
- public void onResponse(PGPPrivateKeyAndPass sKey) {
|
|
|
- byte[] output;
|
|
|
- try {
|
|
|
- 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();
|
|
|
- output = actualOutput.toByteArray();
|
|
|
- }
|
|
|
- catch (PGPException | IOException e) {
|
|
|
- resp.onError(e.getMessage(), e);
|
|
|
- return;
|
|
|
- }
|
|
|
- resp.onResponse(output);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void onError(String msg, Throwable e) {
|
|
|
- resp.onError(msg, e);
|
|
|
- }
|
|
|
- }))
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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 void CryptFile(final OutputStream fileOutStream, final byte[] data, final GitInterface.OnResponseListener<Void> resp) {
|
|
|
- try {
|
|
|
- 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();
|
|
|
-
|
|
|
- final byte[] compressedData = compressedDataStream.toByteArray();
|
|
|
- final OutputStream out = new ArmoredOutputStream(fileOutStream);
|
|
|
-
|
|
|
- final PGPSecretKey sKey = findSecretKey(new FileInputStream(fKeyFile));
|
|
|
- FindPassword(sKey, new GitInterface.OnResponseListener<PGPPrivateKeyAndPass>() {
|
|
|
- @Override
|
|
|
- public void onResponse(PGPPrivateKeyAndPass result) {
|
|
|
- PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedDataGenerator.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()).setProvider("BC"));
|
|
|
- encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(sKey.getPublicKey()).setProvider("BC"));
|
|
|
- try {
|
|
|
- OutputStream encOut = encGen.open(fileOutStream, compressedData.length);
|
|
|
-
|
|
|
- encOut.write(compressedData);
|
|
|
- encOut.close();
|
|
|
- out.close();
|
|
|
- fileOutStream.close();
|
|
|
- resp.onResponse(null);
|
|
|
- }
|
|
|
- catch (IOException | PGPException e) {
|
|
|
- resp.onError(e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void onError(String msg, Throwable e) {
|
|
|
- resp.onError(msg, e);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- catch (IOException | PGPException e) {
|
|
|
- resp.onError(e.getMessage(), e);
|
|
|
- }
|
|
|
+ fContext = ctx;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -297,9 +28,9 @@ class GPGFileInterface implements IFileInterface {
|
|
|
return;
|
|
|
}
|
|
|
try {
|
|
|
- if (fKeyFile == null)
|
|
|
+ if (!SettingsManager.HasGPGKey(fContext))
|
|
|
throw new FileNotFoundException("GPG key not set");
|
|
|
- DecryptFile(new GitInterface.OnResponseListener<byte[]>() {
|
|
|
+ GPGUtil.DecryptFile(fContext, fPasswordGetter, fFile, new GitInterface.OnResponseListener<byte[]>() {
|
|
|
@Override
|
|
|
public void onResponse(byte[] result) {
|
|
|
resp.onResponse(new String(result, Charset.defaultCharset()));
|
|
|
@@ -318,9 +49,9 @@ class GPGFileInterface implements IFileInterface {
|
|
|
@Override
|
|
|
public void WriteFile(String content, GitInterface.OnResponseListener<Void> resp) {
|
|
|
try {
|
|
|
- if (fKeyFile == null)
|
|
|
+ if (!SettingsManager.HasGPGKey(fContext))
|
|
|
throw new FileNotFoundException("GPG key not set");
|
|
|
- CryptFile(new FileOutputStream(fFile), content.getBytes(), resp);
|
|
|
+ GPGUtil.CryptFile(fContext, fPasswordGetter, new FileOutputStream(fFile), content.getBytes(), resp);
|
|
|
}
|
|
|
catch (Throwable e) {
|
|
|
resp.onError(e.getMessage(), e);
|