aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
diff options
context:
space:
mode:
authorTim Bray <timbray@gmail.com>2014-11-07 12:28:27 -0800
committerTim Bray <timbray@gmail.com>2014-11-07 12:28:27 -0800
commitc05441667e151dceb6f5874b290d70a53258061b (patch)
tree789d951f241001e3e20d64f1e83e1d0c8a61d892 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
parenta798d95411e9e62c783675d09005ff4908c74a42 (diff)
downloadopen-keychain-c05441667e151dceb6f5874b290d70a53258061b.tar.gz
open-keychain-c05441667e151dceb6f5874b290d70a53258061b.tar.bz2
open-keychain-c05441667e151dceb6f5874b290d70a53258061b.zip
Moved from WebView to Spannables, some proof cleanup too
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java162
1 files changed, 161 insertions, 1 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
index 4f086c2a6..4161f2928 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
@@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
@@ -83,6 +84,8 @@ public class PgpDecryptVerify extends BaseOperation {
private Set<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
private byte[] mDecryptedSessionKey;
+ private String mRequiredSignerFingerprint;
+ private boolean mSignedLiteralData;
private PgpDecryptVerify(Builder builder) {
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
@@ -96,6 +99,8 @@ public class PgpDecryptVerify extends BaseOperation {
this.mAllowedKeyIds = builder.mAllowedKeyIds;
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
+ this.mSignedLiteralData = builder.mSignedLiteralData;
+ this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
}
public static class Builder {
@@ -112,6 +117,8 @@ public class PgpDecryptVerify extends BaseOperation {
private Set<Long> mAllowedKeyIds = null;
private boolean mDecryptMetadataOnly = false;
private byte[] mDecryptedSessionKey = null;
+ private String mRequiredSignerFingerprint = null;
+ private boolean mSignedLiteralData = false;
public Builder(Context context, ProviderHelper providerHelper,
Progressable progressable,
@@ -123,6 +130,24 @@ public class PgpDecryptVerify extends BaseOperation {
mOutStream = outStream;
}
+ /**
+ * This is used when verifying signed literals to check that they are signed with
+ * the required key
+ */
+ public Builder setRequiredSignerFingerprint(String fingerprint) {
+ mRequiredSignerFingerprint = fingerprint;
+ return this;
+ }
+
+ /**
+ * This is to force a mode where the message is just the signature key id and
+ * then a literal data packet; used in Keybase.io proofs
+ */
+ public Builder setSignedLiteralData(boolean signedLiteralData) {
+ mSignedLiteralData = signedLiteralData;
+ return this;
+ }
+
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
mAllowSymmetricDecryption = allowSymmetricDecryption;
return this;
@@ -174,7 +199,9 @@ public class PgpDecryptVerify extends BaseOperation {
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
- if (aIn.isClearText()) {
+ if (mSignedLiteralData) {
+ return verifySignedLiteralData(aIn, 0);
+ } else if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
return verifyCleartextSignature(aIn, 0);
}
@@ -196,6 +223,139 @@ public class PgpDecryptVerify extends BaseOperation {
}
/**
+ * Verify Keybase.io style signed literal data
+ */
+ private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_VL, indent);
+
+ // thinking that the proof-fetching operation is going to take most of the time
+ updateProgress(R.string.progress_reading_data, 75, 100);
+
+ PGPObjectFactory pgpF = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());
+ Object o = pgpF.nextObject();
+ if (o instanceof PGPCompressedData) {
+ log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
+
+ pgpF = new PGPObjectFactory(((PGPCompressedData) o).getDataStream(), new JcaKeyFingerprintCalculator());
+ o = pgpF.nextObject();
+ updateProgress(R.string.progress_decompressing_data, 80, 100);
+ }
+
+ // all we want to see is a OnePassSignatureList followed by LiteralData
+ if (!(o instanceof PGPOnePassSignatureList)) {
+ log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
+ PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o;
+
+ // go through all signatures (should be just one), make sure we have
+ // the key and it matches the one we’re looking for
+ CanonicalizedPublicKeyRing signingRing = null;
+ CanonicalizedPublicKey signingKey = null;
+ int signatureIndex = -1;
+ for (int i = 0; i < sigList.size(); ++i) {
+ try {
+ long sigKeyId = sigList.get(i).getKeyID();
+ signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
+ );
+ signingKey = signingRing.getPublicKey(sigKeyId);
+ signatureIndex = i;
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.d(Constants.TAG, "key not found, trying next signature...");
+ }
+ }
+
+ // there has to be a key, and it has to be the right one
+ if (signingKey == null) {
+ log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
+ Log.d(Constants.TAG, "Failed to find key in signed-literal message");
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
+
+ CanonicalizedPublicKey encryptKey = signingKey;
+ try {
+ encryptKey = signingRing.getEncryptionSubKey();
+ } catch (PgpKeyNotFoundException e) {
+ }
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingKey.getFingerprint());
+ String cryptFingerprint = KeyFormattingUtils.convertFingerprintToHex(encryptKey.getFingerprint());
+ if (!(mRequiredSignerFingerprint.equals(fingerprint) || mRequiredSignerFingerprint.equals(cryptFingerprint))) {
+ log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
+ Log.d(Constants.TAG, "Key mismatch; wanted " + mRequiredSignerFingerprint +
+ " got " + fingerprint + "/" + cryptFingerprint);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
+
+ OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
+
+ PGPOnePassSignature signature = sigList.get(signatureIndex);
+ signatureResultBuilder.initValid(signingRing, signingKey);
+
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
+
+ o = pgpF.nextObject();
+
+ if (!(o instanceof PGPLiteralData)) {
+ log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
+
+ PGPLiteralData literalData = (PGPLiteralData) o;
+
+ log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
+ updateProgress(R.string.progress_decrypting, 85, 100);
+
+ InputStream dataIn = literalData.getInputStream();
+
+ int length;
+ byte[] buffer = new byte[1 << 16];
+ while ((length = dataIn.read(buffer)) > 0) {
+ mOutStream.write(buffer, 0, length);
+ signature.update(buffer, 0, length);
+ }
+
+ updateProgress(R.string.progress_verifying_signature, 95, 100);
+ log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1);
+
+ PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
+ PGPSignature messageSignature = signatureList.get(signatureIndex);
+
+ // these are not cleartext signatures!
+ // TODO: what about binary signatures?
+ signatureResultBuilder.setSignatureOnly(false);
+
+ // Verify signature and check binding signatures
+ boolean validSignature = signature.verify(messageSignature);
+ if (validSignature) {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
+ } else {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
+ }
+ signatureResultBuilder.setValidSignature(validSignature);
+
+ if (!signatureResultBuilder.isValidSignature()) {
+ log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ log.add(LogType.MSG_VL_OK, indent);
+
+ // Return a positive result, with metadata and verification info
+ DecryptVerifyResult result =
+ new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
+ result.setSignatureResult(signatureResultBuilder.build());
+ return result;
+ }
+
+
+ /**
* Decrypt and/or verifies binary or ascii armored pgp
*/
private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException {