Experimental API support for detached signatures (not tested)

This commit is contained in:
Dominik Schürmann 2015-01-08 14:48:13 +01:00
parent 6570483fab
commit 1c4b8c193d
6 changed files with 119 additions and 45 deletions

View File

@ -37,6 +37,7 @@ public class SignEncryptResult extends OperationResult {
int mNfcAlgo; int mNfcAlgo;
Date mNfcTimestamp; Date mNfcTimestamp;
String mNfcPassphrase; String mNfcPassphrase;
byte[] mDetachedSignature;
public long getKeyIdPassphraseNeeded() { public long getKeyIdPassphraseNeeded() {
return mKeyIdPassphraseNeeded; return mKeyIdPassphraseNeeded;
@ -54,6 +55,10 @@ public class SignEncryptResult extends OperationResult {
mNfcPassphrase = passphrase; mNfcPassphrase = passphrase;
} }
public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
}
public long getNfcKeyId() { public long getNfcKeyId() {
return mNfcKeyId; return mNfcKeyId;
} }
@ -74,6 +79,10 @@ public class SignEncryptResult extends OperationResult {
return mNfcPassphrase; return mNfcPassphrase;
} }
public byte[] getDetachedSignature() {
return mDetachedSignature;
}
public boolean isPending() { public boolean isPending() {
return (mResult & RESULT_PENDING) == RESULT_PENDING; return (mResult & RESULT_PENDING) == RESULT_PENDING;
} }
@ -87,6 +96,7 @@ public class SignEncryptResult extends OperationResult {
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null; mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
mNfcAlgo = source.readInt(); mNfcAlgo = source.readInt();
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null; mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
} }
public int describeContents() { public int describeContents() {
@ -108,6 +118,12 @@ public class SignEncryptResult extends OperationResult {
} else { } else {
dest.writeInt(0); dest.writeInt(0);
} }
if (mDetachedSignature != null) {
dest.writeInt(1);
dest.writeByteArray(mDetachedSignature);
} else {
dest.writeInt(0);
}
} }
public static final Creator<SignEncryptResult> CREATOR = new Creator<SignEncryptResult>() { public static final Creator<SignEncryptResult> CREATOR = new Creator<SignEncryptResult>() {

View File

@ -247,7 +247,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
int signatureType; int signatureType;
if (cleartext) { if (cleartext) {
// for sign-only ascii text // for sign-only ascii text (cleartext signature)
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
} else { } else {
signatureType = PGPSignature.BINARY_DOCUMENT; signatureType = PGPSignature.BINARY_DOCUMENT;

View File

@ -50,6 +50,7 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -78,7 +79,8 @@ public class PgpSignEncrypt extends BaseOperation {
private int mSignatureHashAlgorithm; private int mSignatureHashAlgorithm;
private String mSignaturePassphrase; private String mSignaturePassphrase;
private long mAdditionalEncryptId; private long mAdditionalEncryptId;
private boolean mCleartextInput; private boolean mCleartextSignature;
private boolean mDetachedSignature;
private String mOriginalFilename; private String mOriginalFilename;
private boolean mFailOnMissingEncryptionKeyIds; private boolean mFailOnMissingEncryptionKeyIds;
@ -113,7 +115,8 @@ public class PgpSignEncrypt extends BaseOperation {
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
this.mSignaturePassphrase = builder.mSignaturePassphrase; this.mSignaturePassphrase = builder.mSignaturePassphrase;
this.mAdditionalEncryptId = builder.mAdditionalEncryptId; this.mAdditionalEncryptId = builder.mAdditionalEncryptId;
this.mCleartextInput = builder.mCleartextInput; this.mCleartextSignature = builder.mCleartextSignature;
this.mDetachedSignature = builder.mDetachedSignature;
this.mNfcSignedHash = builder.mNfcSignedHash; this.mNfcSignedHash = builder.mNfcSignedHash;
this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
this.mOriginalFilename = builder.mOriginalFilename; this.mOriginalFilename = builder.mOriginalFilename;
@ -140,7 +143,8 @@ public class PgpSignEncrypt extends BaseOperation {
private int mSignatureHashAlgorithm = 0; private int mSignatureHashAlgorithm = 0;
private String mSignaturePassphrase = null; private String mSignaturePassphrase = null;
private long mAdditionalEncryptId = Constants.key.none; private long mAdditionalEncryptId = Constants.key.none;
private boolean mCleartextInput = false; private boolean mCleartextSignature = false;
private boolean mDetachedSignature = false;
private String mOriginalFilename = ""; private String mOriginalFilename = "";
private byte[] mNfcSignedHash = null; private byte[] mNfcSignedHash = null;
private Date mNfcCreationTimestamp = null; private Date mNfcCreationTimestamp = null;
@ -222,14 +226,13 @@ public class PgpSignEncrypt extends BaseOperation {
return this; return this;
} }
/** public Builder setCleartextSignature(boolean cleartextSignature) {
* TODO: test this option! mCleartextSignature = cleartextSignature;
* return this;
* @param cleartextInput }
* @return
*/ public Builder setDetachedSignature(boolean detachedSignature) {
public Builder setCleartextInput(boolean cleartextInput) { mDetachedSignature = detachedSignature;
mCleartextInput = cleartextInput;
return this; return this;
} }
@ -408,7 +411,7 @@ public class PgpSignEncrypt extends BaseOperation {
updateProgress(R.string.progress_preparing_signature, 4, 100); updateProgress(R.string.progress_preparing_signature, 4, 100);
try { try {
boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; boolean cleartext = mCleartextSignature && mEnableAsciiArmorOutput && !enableEncryption;
signatureGenerator = signingKey.getSignatureGenerator( signatureGenerator = signingKey.getSignatureGenerator(
mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp);
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
@ -424,6 +427,9 @@ public class PgpSignEncrypt extends BaseOperation {
OutputStream encryptionOut = null; OutputStream encryptionOut = null;
BCPGOutputStream bcpgOut; BCPGOutputStream bcpgOut;
ByteArrayOutputStream detachedByteOut = null;
BCPGOutputStream detachedBcpgOut = null;
try { try {
if (enableEncryption) { if (enableEncryption) {
@ -452,7 +458,7 @@ public class PgpSignEncrypt extends BaseOperation {
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
char literalDataFormatTag; char literalDataFormatTag;
if (mCleartextInput) { if (mCleartextSignature) {
literalDataFormatTag = PGPLiteralData.UTF8; literalDataFormatTag = PGPLiteralData.UTF8;
} else { } else {
literalDataFormatTag = PGPLiteralData.BINARY; literalDataFormatTag = PGPLiteralData.BINARY;
@ -482,7 +488,7 @@ public class PgpSignEncrypt extends BaseOperation {
literalGen.close(); literalGen.close();
indent -= 1; indent -= 1;
} else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) { } else if (enableSignature && mCleartextSignature && mEnableAsciiArmorOutput) {
/* cleartext signature: sign-only of ascii text */ /* cleartext signature: sign-only of ascii text */
updateProgress(R.string.progress_signing, 8, 100); updateProgress(R.string.progress_signing, 8, 100);
@ -517,11 +523,44 @@ public class PgpSignEncrypt extends BaseOperation {
armorOut.endClearText(); armorOut.endClearText();
pOut = new BCPGOutputStream(armorOut); pOut = new BCPGOutputStream(armorOut);
} else if (enableSignature && !mCleartextInput) { } else if (enableSignature && mDetachedSignature) {
/* detached signature */
updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_SE_SIGNING, indent);
InputStream in = mData.getInputStream();
// handle output stream separately for detached signatures
detachedByteOut = new ByteArrayOutputStream();
OutputStream detachedOut = detachedByteOut;
if (mEnableAsciiArmorOutput) {
detachedOut = new ArmoredOutputStream(detachedOut);
}
detachedBcpgOut = new BCPGOutputStream(detachedOut);
long alreadyWritten = 0;
int length;
byte[] buffer = new byte[1 << 16];
while ((length = in.read(buffer)) > 0) {
// pipe input stream directly into output stream, no changes to data
mOutStream.write(buffer, 0, length);
signatureGenerator.update(buffer, 0, length);
alreadyWritten += length;
if (mData.getSize() > 0) {
long progress = 100 * alreadyWritten / mData.getSize();
progressScaler.setProgress((int) progress, 100);
}
}
pOut = null;
} else if (enableSignature && !mCleartextSignature && !mDetachedSignature) {
/* sign-only binary (files/data stream) */ /* sign-only binary (files/data stream) */
updateProgress(R.string.progress_signing, 8, 100); updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_SE_ENCRYPTING, indent); log.add(LogType.MSG_SE_SIGNING, indent);
InputStream in = mData.getInputStream(); InputStream in = mData.getInputStream();
@ -556,13 +595,18 @@ public class PgpSignEncrypt extends BaseOperation {
literalGen.close(); literalGen.close();
} else { } else {
pOut = null; pOut = null;
// TODO: Is this log right?
log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent); log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent);
} }
if (enableSignature) { if (enableSignature) {
updateProgress(R.string.progress_generating_signature, 95, 100); updateProgress(R.string.progress_generating_signature, 95, 100);
try { try {
signatureGenerator.generate().encode(pOut); if (detachedBcpgOut != null) {
signatureGenerator.generate().encode(detachedBcpgOut);
} else {
signatureGenerator.generate().encode(pOut);
}
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_SE_PENDING_NFC, indent); log.add(LogType.MSG_SE_PENDING_NFC, indent);
@ -607,10 +651,22 @@ public class PgpSignEncrypt extends BaseOperation {
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
log.add(LogType.MSG_SE_OK, indent); log.add(LogType.MSG_SE_OK, indent);
return new SignEncryptResult(SignEncryptResult.RESULT_OK, log); SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_OK, log);
if (detachedByteOut != null) {
try {
detachedByteOut.flush();
detachedByteOut.close();
} catch (IOException e) {
// silently catch
}
result.setDetachedSignature(detachedByteOut.toByteArray());
}
return result;
} }
/**
* Remove whitespaces on line endings
*/
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
final PGPSignatureGenerator pSignatureGenerator) final PGPSignatureGenerator pSignatureGenerator)
throws IOException, SignatureException { throws IOException, SignatureException {

View File

@ -222,9 +222,10 @@ public class OpenPgpService extends RemoteService {
} }
private Intent signImpl(Intent data, ParcelFileDescriptor input, private Intent signImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AccountSettings accSettings) { ParcelFileDescriptor output, AccountSettings accSettings,
boolean cleartextSign) {
try { try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
if (nfcSignedHash != null) { if (nfcSignedHash != null) {
@ -284,6 +285,8 @@ public class OpenPgpService extends RemoteService {
inputData, os inputData, os
); );
builder.setEnableAsciiArmorOutput(asciiArmor) builder.setEnableAsciiArmorOutput(asciiArmor)
.setCleartextSignature(cleartextSign)
.setDetachedSignature(!cleartextSign)
.setVersionHeader(PgpHelper.getVersionForHeader(this)) .setVersionHeader(PgpHelper.getVersionForHeader(this))
.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) .setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
.setSignatureMasterKeyId(accSettings.getKeyId()) .setSignatureMasterKeyId(accSettings.getKeyId())
@ -291,9 +294,6 @@ public class OpenPgpService extends RemoteService {
.setSignaturePassphrase(passphrase) .setSignaturePassphrase(passphrase)
.setNfcState(nfcSignedHash, nfcCreationDate); .setNfcState(nfcSignedHash, nfcCreationDate);
// TODO: currently always assume cleartext input, no sign-only of binary currently!
builder.setCleartextInput(true);
// execute PGP operation! // execute PGP operation!
SignEncryptResult pgpResult = builder.build().execute(); SignEncryptResult pgpResult = builder.build().execute();
@ -313,20 +313,20 @@ public class OpenPgpService extends RemoteService {
"Encountered unhandled type of pending action not supported by API!"); "Encountered unhandled type of pending action not supported by API!");
} }
} else if (pgpResult.success()) { } else if (pgpResult.success()) {
// see end of method Intent result = new Intent();
if (!cleartextSign) {
result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature());
}
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} else { } else {
LogEntryParcel errorMsg = pgpResult.getLog().getLast(); LogEntryParcel errorMsg = pgpResult.getLog().getLast();
throw new Exception(getString(errorMsg.mType.getMsgId())); throw new Exception(getString(errorMsg.mType.getMsgId()));
} }
} finally { } finally {
is.close(); is.close();
os.close(); os.close();
} }
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) { } catch (Exception e) {
Log.d(Constants.TAG, "signImpl", e); Log.d(Constants.TAG, "signImpl", e);
Intent result = new Intent(); Intent result = new Intent();
@ -444,7 +444,9 @@ public class OpenPgpService extends RemoteService {
"Encountered unhandled type of pending action not supported by API!"); "Encountered unhandled type of pending action not supported by API!");
} }
} else if (pgpResult.success()) { } else if (pgpResult.success()) {
// see end of method Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} else { } else {
LogEntryParcel errorMsg = pgpResult.getLog().getLast(); LogEntryParcel errorMsg = pgpResult.getLog().getLast();
throw new Exception(getString(errorMsg.mType.getMsgId())); throw new Exception(getString(errorMsg.mType.getMsgId()));
@ -454,10 +456,6 @@ public class OpenPgpService extends RemoteService {
is.close(); is.close();
os.close(); os.close();
} }
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) { } catch (Exception e) {
Log.d(Constants.TAG, "encryptAndSignImpl", e); Log.d(Constants.TAG, "encryptAndSignImpl", e);
Intent result = new Intent(); Intent result = new Intent();
@ -482,7 +480,6 @@ public class OpenPgpService extends RemoteService {
os = new ParcelFileDescriptor.AutoCloseOutputStream(output); os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
} }
Intent result = new Intent();
try { try {
String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
long inputLength = is.available(); long inputLength = is.available();
@ -522,6 +519,7 @@ public class OpenPgpService extends RemoteService {
"Encountered unhandled type of pending action not supported by API!"); "Encountered unhandled type of pending action not supported by API!");
} }
} else if (pgpResult.success()) { } else if (pgpResult.success()) {
Intent result = new Intent();
OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult(); OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult();
if (signatureResult != null) { if (signatureResult != null) {
@ -557,6 +555,9 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
} }
} }
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} else { } else {
LogEntryParcel errorMsg = pgpResult.getLog().getLast(); LogEntryParcel errorMsg = pgpResult.getLog().getLast();
throw new Exception(getString(errorMsg.mType.getMsgId())); throw new Exception(getString(errorMsg.mType.getMsgId()));
@ -567,9 +568,6 @@ public class OpenPgpService extends RemoteService {
os.close(); os.close();
} }
} }
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) { } catch (Exception e) {
Log.d(Constants.TAG, "decryptAndVerifyImpl", e); Log.d(Constants.TAG, "decryptAndVerifyImpl", e);
Intent result = new Intent(); Intent result = new Intent();
@ -718,8 +716,13 @@ public class OpenPgpService extends RemoteService {
} }
String action = data.getAction(); String action = data.getAction();
if (OpenPgpApi.ACTION_SIGN.equals(action)) { if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) {
return signImpl(data, input, output, accSettings); return signImpl(data, input, output, accSettings, true);
} else if (OpenPgpApi.ACTION_SIGN.equals(action)) {
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
return signImpl(data, input, output, accSettings, true);
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) {
return signImpl(data, input, output, accSettings, false);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, accSettings, false); return encryptAndSignImpl(data, input, output, accSettings, false);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {

View File

@ -33,7 +33,6 @@ import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.FileHelper;
@ -474,7 +473,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// this assumes that the bytes are cleartext (valid for current implementation!) // this assumes that the bytes are cleartext (valid for current implementation!)
if (source == IO_BYTES) { if (source == IO_BYTES) {
builder.setCleartextInput(true); builder.setCleartextSignature(true);
} }
SignEncryptResult result = builder.build().execute(); SignEncryptResult result = builder.build().execute();

@ -1 +1 @@
Subproject commit 89b0a3bd140dd178595c031beaa27747575d7ac8 Subproject commit f712a26ab68eb0f978722cfa69a7e9b5d05c80ca