aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java84
4 files changed, 154 insertions, 30 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
index 1ba028006..20bb1f97c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
@@ -95,7 +95,7 @@ public class PgpKeyHelper {
}
/**
- * Converts fingerprint to hex (optional: with whitespaces after 4 characters)
+ * Converts fingerprint to hex
* <p/>
* Fingerprint is shown using lowercase characters. Studies have shown that humans can
* better differentiate between numbers and letters when letters are lowercase.
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
index 4cb92c368..cc34e8737 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
@@ -18,8 +18,10 @@
package org.sufficientlysecure.keychain.pgp;
+import org.openkeychain.nfc.NfcHandler;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPException;
@@ -29,6 +31,7 @@ import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@@ -38,16 +41,20 @@ import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.LinkedList;
/**
* This class uses a Builder pattern!
@@ -70,6 +77,7 @@ public class PgpSignEncrypt {
private String mSignaturePassphrase;
private boolean mEncryptToSigner;
private boolean mCleartextInput;
+ private byte[] mNfcData;
private static byte[] NEW_LINE;
@@ -100,6 +108,7 @@ public class PgpSignEncrypt {
this.mSignaturePassphrase = builder.mSignaturePassphrase;
this.mEncryptToSigner = builder.mEncryptToSigner;
this.mCleartextInput = builder.mCleartextInput;
+ this.mNfcData = builder.mNfcData;
}
public static class Builder {
@@ -122,6 +131,7 @@ public class PgpSignEncrypt {
private String mSignaturePassphrase = null;
private boolean mEncryptToSigner = false;
private boolean mCleartextInput = false;
+ private byte[] mNfcData = null;
public Builder(ProviderHelper providerHelper, String versionHeader, InputData data, OutputStream outStream) {
this.mProviderHelper = providerHelper;
@@ -130,7 +140,7 @@ public class PgpSignEncrypt {
this.mOutStream = outStream;
}
- public Builder setProgressable(Progressable progressable) {
+ public Builder setProgressable(Progressable progressable) {
mProgressable = progressable;
return this;
}
@@ -170,6 +180,12 @@ public class PgpSignEncrypt {
return this;
}
+ /**
+ * Generate old V3 signatures
+ *
+ * @param signatureForceV3
+ * @return
+ */
public Builder setSignatureForceV3(boolean signatureForceV3) {
mSignatureForceV3 = signatureForceV3;
return this;
@@ -200,6 +216,11 @@ public class PgpSignEncrypt {
return this;
}
+ public Builder setNfcData(byte[] nfcData) {
+ mNfcData = nfcData;
+ return this;
+ }
+
public PgpSignEncrypt build() {
return new PgpSignEncrypt(this);
}
@@ -227,17 +248,36 @@ public class PgpSignEncrypt {
}
}
+ public static class WrongPassphraseException extends Exception {
+ public WrongPassphraseException() {
+ }
+ }
+
public static class NoSigningKeyException extends Exception {
public NoSigningKeyException() {
}
}
+ public static class NeedNfcDataException extends Exception {
+ public byte[] mData;
+
+ public NeedNfcDataException(byte[] data) {
+ mData = data;
+ }
+ }
+
+ // TODO: remove later
+ static String convertStreamToString(java.io.InputStream is) {
+ java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
+ return s.hasNext() ? s.next() : "";
+ }
+
/**
* Signs and/or encrypts data based on parameters of class
*/
public void execute()
throws IOException, PGPException, NoSuchProviderException,
- NoSuchAlgorithmException, SignatureException, KeyExtractionException, NoSigningKeyException, NoPassphraseException {
+ NoSuchAlgorithmException, SignatureException, KeyExtractionException, NoSigningKeyException, NoPassphraseException, NeedNfcDataException, WrongPassphraseException {
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
@@ -255,16 +295,6 @@ public class PgpSignEncrypt {
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId;
}
- ArmoredOutputStream armorOut = null;
- OutputStream out;
- if (mEnableAsciiArmorOutput) {
- armorOut = new ArmoredOutputStream(mOutStream);
- armorOut.setHeader("Version", mVersionHeader);
- out = armorOut;
- } else {
- out = mOutStream;
- }
-
/* Get keys for signature generation for later usage */
WrappedSecretKey signingKey = null;
if (enableSignature) {
@@ -276,7 +306,7 @@ public class PgpSignEncrypt {
}
try {
signingKey = signingKeyRing.getSigningSubKey();
- } catch(PgpGeneralException e) {
+ } catch (PgpGeneralException e) {
throw new NoSigningKeyException();
}
@@ -287,10 +317,19 @@ public class PgpSignEncrypt {
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
try {
- signingKey.unlock(mSignaturePassphrase);
+ if (!signingKey.unlock(mSignaturePassphrase)) {
+ throw new WrongPassphraseException();
+ }
} catch (PgpGeneralException e) {
throw new KeyExtractionException();
}
+
+ // check if hash algo is supported
+ LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms();
+ if (!supported.contains(mSignatureHashAlgorithm)) {
+ // get most preferred
+ mSignatureHashAlgorithm = supported.getLast();
+ }
}
updateProgress(R.string.progress_preparing_streams, 5, 100);
@@ -339,10 +378,10 @@ public class PgpSignEncrypt {
boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption;
if (mSignatureForceV3) {
signatureV3Generator = signingKey.getV3SignatureGenerator(
- mSignatureHashAlgorithm,cleartext);
+ mSignatureHashAlgorithm, cleartext);
} else {
signatureGenerator = signingKey.getSignatureGenerator(
- mSignatureHashAlgorithm, cleartext);
+ mSignatureHashAlgorithm, cleartext, mNfcData);
}
} catch (PgpGeneralException e) {
// TODO throw correct type of exception (which shouldn't be PGPException)
@@ -350,10 +389,21 @@ public class PgpSignEncrypt {
}
}
+ ArmoredOutputStream armorOut = null;
+ OutputStream out;
+ if (mEnableAsciiArmorOutput) {
+ armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", mVersionHeader);
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+
PGPCompressedDataGenerator compressGen = null;
- OutputStream pOut;
+ OutputStream pOut = null;
OutputStream encryptionOut = null;
BCPGOutputStream bcpgOut;
+
if (enableEncryption) {
/* actual encryption */
@@ -493,7 +543,12 @@ public class PgpSignEncrypt {
if (mSignatureForceV3) {
signatureV3Generator.generate().encode(pOut);
} else {
- signatureGenerator.generate().encode(pOut);
+ try {
+ signatureGenerator.generate().encode(pOut);
+ } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
+ // this secret key diverts to a OpenPGP card, throw exception with to-be-signed hash
+ throw new NeedNfcDataException(e.hashToSign);
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index 6020fc084..5dc3c598e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -176,9 +176,12 @@ public class UncachedKeyRing {
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(
((PGPSecretKeyRing) mRing).getSecretKeys())) {
S2K s2k = sub.getS2K();
- // Set to 1, except if the encryption type is GNU_DUMMY_S2K
- if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
+ // add key, except if the private key has been stripped (GNU extension)
+ if(s2k == null || (s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY)) {
result.add(sub.getKeyID());
+ } else {
+ Log.d(Constants.TAG, "S2K GNU extension!, mode: " + s2k.getProtectionMode());
}
}
return result;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
index 98ad2b706..141f2d5eb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
@@ -1,5 +1,8 @@
package org.sufficientlysecure.keychain.pgp;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
@@ -12,19 +15,26 @@ import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/** Wrapper for a PGPSecretKey.
*
@@ -42,6 +52,11 @@ public class WrappedSecretKey extends WrappedPublicKey {
private final PGPSecretKey mSecretKey;
private PGPPrivateKey mPrivateKey = null;
+ private int mPrivateKeyState = PRIVATE_KEY_STATE_LOCKED;
+ private static int PRIVATE_KEY_STATE_LOCKED = 0;
+ private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
+ private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
+
WrappedSecretKey(WrappedSecretKeyRing ring, PGPSecretKey key) {
super(ring, key.getPublicKey());
mSecretKey = key;
@@ -59,11 +74,23 @@ public class WrappedSecretKey extends WrappedPublicKey {
return mSecretKey;
}
+ /**
+ * Returns true on right passphrase
+ */
public boolean unlock(String passphrase) throws PgpGeneralException {
+ // handle keys on OpenPGP cards like they were unlocked
+ if (mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K
+ && mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ mPrivateKeyState = PRIVATE_KEY_STATE_DIVERT_TO_CARD;
+ return true;
+ }
+
+ // try to extract keys using the passphrase
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor);
+ mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
} catch (PGPException e) {
return false;
}
@@ -73,16 +100,46 @@ public class WrappedSecretKey extends WrappedPublicKey {
return true;
}
- public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext)
+ // TODO: just a hack currently
+ public LinkedList<Integer> getSupportedHashAlgorithms() {
+ LinkedList<Integer> supported = new LinkedList<Integer>();
+
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ // TODO: only works with SHA256 ?!
+ supported.add(HashAlgorithmTags.SHA256);
+ } else {
+ supported.add(HashAlgorithmTags.MD5);
+ supported.add(HashAlgorithmTags.RIPEMD160);
+ supported.add(HashAlgorithmTags.SHA1);
+ supported.add(HashAlgorithmTags.SHA224);
+ supported.add(HashAlgorithmTags.SHA256);
+ supported.add(HashAlgorithmTags.SHA384);
+ supported.add(HashAlgorithmTags.SHA512); // preferred is latest
+ }
+
+ return supported;
+ }
+
+ public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
+ byte[] nfcSignedHash)
throws PgpGeneralException {
- if(mPrivateKey == null) {
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
- // content signer based on signing key algorithm and chosen hash algorithm
- JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
- mSecretKey.getPublicKey().getAlgorithm(), hashAlgo)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPContentSignerBuilder contentSignerBuilder;
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ // use synchronous "NFC based" SignerBuilder
+ contentSignerBuilder = new NfcSyncPGPContentSignerBuilder(
+ mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
+ mSecretKey.getKeyID(), nfcSignedHash)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ } else {
+ // content signer based on signing key algorithm and chosen hash algorithm
+ contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ mSecretKey.getPublicKey().getAlgorithm(), hashAlgo)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ }
int signatureType;
if (cleartext) {
@@ -101,13 +158,15 @@ public class WrappedSecretKey extends WrappedPublicKey {
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch(PGPException e) {
+ // TODO: simply throw PGPException!
throw new PgpGeneralException("Error initializing signature!", e);
}
}
public PGPV3SignatureGenerator getV3SignatureGenerator(int hashAlgo, boolean cleartext)
throws PgpGeneralException {
- if(mPrivateKey == null) {
+ // TODO: divert to card missing
+ if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) {
throw new PrivateKeyNotUnlockedException();
}
@@ -134,7 +193,8 @@ public class WrappedSecretKey extends WrappedPublicKey {
}
public PublicKeyDataDecryptorFactory getDecryptorFactory() {
- if(mPrivateKey == null) {
+ // TODO: divert to card missing
+ if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) {
throw new PrivateKeyNotUnlockedException();
}
return new JcePublicKeyDataDecryptorFactoryBuilder()
@@ -152,7 +212,8 @@ public class WrappedSecretKey extends WrappedPublicKey {
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
- if(mPrivateKey == null) {
+ // TODO: divert to card missing
+ if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) {
throw new PrivateKeyNotUnlockedException();
}
@@ -197,4 +258,9 @@ public class WrappedSecretKey extends WrappedPublicKey {
return new UncachedSecretKey(mSecretKey);
}
+ // HACK
+ public PGPSecretKey getSecretKey() {
+ return mSecretKey;
+ }
+
}