|
|
@@ -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;
|
|
|
}
|
|
|
}
|