generalize nfc crypto input structure

This commit is contained in:
Vincent Breitmoser 2015-03-18 18:25:44 +01:00
parent 4499caef1e
commit aca54e31ea
18 changed files with 883 additions and 235 deletions

View file

@ -136,7 +136,7 @@ public class PgpEncryptDecryptTest {
InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput();
PgpSignEncryptInputParcel b = new setSignatureTimestamp();
b.setSymmetricPassphrase(mPassphrase);
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
@ -220,7 +220,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput();
PgpSignEncryptInputParcel b = new setSignatureTimestamp();
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
@ -301,7 +301,7 @@ public class PgpEncryptDecryptTest {
InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput();
PgpSignEncryptInputParcel b = new setSignatureTimestamp();
b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(),
mStaticRing2.getMasterKeyId()
@ -393,7 +393,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput();
PgpSignEncryptInputParcel b = new setSignatureTimestamp();
b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(),
@ -475,7 +475,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput();
PgpSignEncryptInputParcel b = new setSignatureTimestamp();
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);

View file

@ -680,6 +680,12 @@
android:taskAffinity=":Nfc"
android:allowTaskReparenting="true" />
<activity
android:name=".ui.NfcOperationActivity"
android:launchMode="singleTop"
android:taskAffinity=":Nfc"
android:allowTaskReparenting="true" />
<!--<activity-->
<!--android:name=".ui.NfcIntentActivity"-->
<!--android:launchMode="singleTop">-->

View file

@ -14,8 +14,12 @@ import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.Provider;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* This class is based on JcaPGPContentSignerBuilder.
@ -31,31 +35,27 @@ public class NfcSyncPGPContentSignerBuilder
private int keyAlgorithm;
private long keyID;
private byte[] signedHash;
private Date creationTimestamp;
private Map signedHashes;
public static class NfcInteractionNeeded extends RuntimeException
{
public byte[] hashToSign;
public Date creationTimestamp;
public int hashAlgo;
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp)
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo)
{
super("NFC interaction required!");
this.hashToSign = hashToSign;
this.hashAlgo = hashAlgo;
this.creationTimestamp = creationTimestamp;
}
}
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, byte[] signedHash, Date creationTimestamp)
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, Map signedHashes)
{
this.keyAlgorithm = keyAlgorithm;
this.hashAlgorithm = hashAlgorithm;
this.keyID = keyID;
this.signedHash = signedHash;
this.creationTimestamp = creationTimestamp;
this.signedHashes = signedHashes;
}
public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
@ -125,14 +125,14 @@ public class NfcSyncPGPContentSignerBuilder
}
public byte[] getSignature() {
if (signedHash != null) {
// we already have the signed hash from a previous execution, return this!
return signedHash;
} else {
// catch this when signatureGenerator.generate() is executed and divert digest to card,
// when doing the operation again reuse creationTimestamp (this will be hashed)
throw new NfcInteractionNeeded(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp);
byte[] digest = digestCalculator.getDigest();
ByteBuffer buf = ByteBuffer.wrap(digest);
if (signedHashes.containsKey(buf)) {
return (byte[]) signedHashes.get(buf);
}
// catch this when signatureGenerator.generate() is executed and divert digest to card,
// when doing the operation again reuse creationTimestamp (this will be hashed)
throw new NfcInteractionNeeded(digest, getHashAlgorithm());
}
public byte[] getDigest()

View file

@ -35,7 +35,6 @@ public class PgpSignEncryptResult extends OperationResult {
long mNfcKeyId;
byte[] mNfcHash;
int mNfcAlgo;
Date mNfcTimestamp;
String mNfcPassphrase;
byte[] mDetachedSignature;
@ -47,11 +46,10 @@ public class PgpSignEncryptResult extends OperationResult {
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
}
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) {
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, String passphrase) {
mNfcKeyId = nfcKeyId;
mNfcHash = nfcHash;
mNfcAlgo = nfcAlgo;
mNfcTimestamp = nfcTimestamp;
mNfcPassphrase = passphrase;
}
@ -71,10 +69,6 @@ public class PgpSignEncryptResult extends OperationResult {
return mNfcAlgo;
}
public Date getNfcTimestamp() {
return mNfcTimestamp;
}
public String getNfcPassphrase() {
return mNfcPassphrase;
}
@ -95,7 +89,6 @@ public class PgpSignEncryptResult extends OperationResult {
super(source);
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
mNfcAlgo = source.readInt();
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
}
@ -112,12 +105,6 @@ public class PgpSignEncryptResult extends OperationResult {
dest.writeInt(0);
}
dest.writeInt(mNfcAlgo);
if (mNfcTimestamp != null) {
dest.writeInt(1);
dest.writeLong(mNfcTimestamp.getTime());
} else {
dest.writeInt(0);
}
if (mDetachedSignature != null) {
dest.writeInt(1);
dest.writeByteArray(mDetachedSignature);

View file

@ -21,6 +21,9 @@ import android.os.Parcel;
import java.util.ArrayList;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
public class SignEncryptResult extends OperationResult {
ArrayList<PgpSignEncryptResult> mResults;

View file

@ -42,10 +42,13 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.Log;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Wrapper for a PGPSecretKey.
@ -183,13 +186,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return PgpConstants.sPreferredHashAlgorithms;
}
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash,
Date nfcCreationTimestamp) {
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
Map<ByteBuffer,byte[]> signedHashes) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
// use synchronous "NFC based" SignerBuilder
return new NfcSyncPGPContentSignerBuilder(
mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp)
mSecretKey.getKeyID(), signedHashes)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} else {
// content signer based on signing key algorithm and chosen hash algorithm
@ -200,28 +203,24 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
byte[] nfcSignedHash, Date nfcCreationTimestamp)
Map<ByteBuffer,byte[]> signedHashes, Date creationTimestamp)
throws PgpGeneralException {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (nfcSignedHash != null && nfcCreationTimestamp == null) {
throw new PgpGeneralException("Got nfc hash without timestamp!!");
}
// We explicitly create a signature creation timestamp in this place.
// That way, we can inject an artificial one from outside, ie the one
// used in previous runs of this function.
if (nfcCreationTimestamp == null) {
if (creationTimestamp == null) {
// to sign using nfc PgpSignEncrypt is executed two times.
// the first time it stops to return the PendingIntent for nfc connection and signing the hash
// the second time the signed hash is used.
// to get the same hash we cache the timestamp for the second round!
nfcCreationTimestamp = new Date();
creationTimestamp = new Date();
}
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo,
nfcSignedHash, nfcCreationTimestamp);
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes);
int signatureType;
if (cleartext) {
@ -237,7 +236,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
spGen.setSignatureCreationTime(false, creationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch (PgpKeyNotFoundException | PGPException e) {
@ -267,8 +266,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
* @param userIds User IDs to certify
* @return A keyring with added certifications
*/
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds,
byte[] nfcSignedHash, Date nfcCreationTimestamp) {
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing,
List<String> userIds,
HashMap<ByteBuffer,byte[]> signedHashes, Date creationTimestamp) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
@ -283,7 +283,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureGenerator signatureGenerator;
{
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
try {
@ -296,9 +296,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
if (nfcCreationTimestamp != null) {
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
if (creationTimestamp != null) {
spGen.setSignatureCreationTime(false, creationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
}
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
@ -331,7 +331,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
* @return A keyring with added certifications
*/
public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing,
List<WrappedUserAttribute> userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) {
List<WrappedUserAttribute> userAttributes,
HashMap<ByteBuffer,byte[]> signedHashes, Date creationTimestamp) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
@ -346,7 +347,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureGenerator signatureGenerator;
{
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
try {
@ -359,9 +360,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
if (nfcCreationTimestamp != null) {
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
if (creationTimestamp != null) {
spGen.setSignatureCreationTime(false, creationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
}
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);

View file

@ -20,10 +20,17 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Map;
public class PgpSignEncryptInput {
import android.os.Parcel;
import android.os.Parcelable;
public class PgpSignEncryptInputParcel implements Parcelable {
protected String mVersionHeader = null;
protected boolean mEnableAsciiArmorOutput = false;
@ -36,13 +43,71 @@ public class PgpSignEncryptInput {
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
protected String mSignaturePassphrase = null;
protected long mAdditionalEncryptId = Constants.key.none;
protected byte[] mNfcSignedHash = null;
protected Date mNfcCreationTimestamp = null;
protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset;
protected boolean mCleartextSignature;
protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false;
protected CryptoInputParcel mCryptoInput = new CryptoInputParcel(null);
public PgpSignEncryptInputParcel() {
}
PgpSignEncryptInputParcel(Parcel source) {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mVersionHeader = source.readString();
mEnableAsciiArmorOutput = source.readInt() == 1;
mCompressionId = source.readInt();
mEncryptionMasterKeyIds = source.createLongArray();
mSymmetricPassphrase = source.readString();
mSymmetricEncryptionAlgorithm = source.readInt();
mSignatureMasterKeyId = source.readLong();
mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
mSignatureHashAlgorithm = source.readInt();
mSignaturePassphrase = source.readString();
mAdditionalEncryptId = source.readLong();
mFailOnMissingEncryptionKeyIds = source.readInt() == 1;
mCharset = source.readString();
mCleartextSignature = source.readInt() == 1;
mDetachedSignature = source.readInt() == 1;
mHiddenRecipients = source.readInt() == 1;
mCryptoInput = source.readParcelable(PgpSignEncryptInputParcel.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVersionHeader);
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
dest.writeInt(mCompressionId);
dest.writeLongArray(mEncryptionMasterKeyIds);
dest.writeString(mSymmetricPassphrase);
dest.writeInt(mSymmetricEncryptionAlgorithm);
dest.writeLong(mSignatureMasterKeyId);
if (mSignatureSubKeyId != null) {
dest.writeInt(1);
dest.writeLong(mSignatureSubKeyId);
} else {
dest.writeInt(0);
}
dest.writeInt(mSignatureHashAlgorithm);
dest.writeString(mSignaturePassphrase);
dest.writeLong(mAdditionalEncryptId);
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
dest.writeString(mCharset);
dest.writeInt(mCleartextSignature ? 1 : 0);
dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0);
dest.writeParcelable(mCryptoInput, 0);
}
public String getCharset() {
return mCharset;
@ -56,19 +121,11 @@ public class PgpSignEncryptInput {
return mFailOnMissingEncryptionKeyIds;
}
public Date getNfcCreationTimestamp() {
return mNfcCreationTimestamp;
}
public byte[] getNfcSignedHash() {
return mNfcSignedHash;
}
public long getAdditionalEncryptId() {
return mAdditionalEncryptId;
}
public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) {
public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) {
mAdditionalEncryptId = additionalEncryptId;
return this;
}
@ -77,7 +134,7 @@ public class PgpSignEncryptInput {
return mSignaturePassphrase;
}
public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) {
public PgpSignEncryptInputParcel setSignaturePassphrase(String signaturePassphrase) {
mSignaturePassphrase = signaturePassphrase;
return this;
}
@ -86,7 +143,7 @@ public class PgpSignEncryptInput {
return mSignatureHashAlgorithm;
}
public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) {
public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) {
mSignatureHashAlgorithm = signatureHashAlgorithm;
return this;
}
@ -95,7 +152,7 @@ public class PgpSignEncryptInput {
return mSignatureSubKeyId;
}
public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) {
public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) {
mSignatureSubKeyId = signatureSubKeyId;
return this;
}
@ -104,7 +161,7 @@ public class PgpSignEncryptInput {
return mSignatureMasterKeyId;
}
public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) {
public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) {
mSignatureMasterKeyId = signatureMasterKeyId;
return this;
}
@ -113,7 +170,7 @@ public class PgpSignEncryptInput {
return mSymmetricEncryptionAlgorithm;
}
public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this;
}
@ -122,7 +179,7 @@ public class PgpSignEncryptInput {
return mSymmetricPassphrase;
}
public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) {
public PgpSignEncryptInputParcel setSymmetricPassphrase(String symmetricPassphrase) {
mSymmetricPassphrase = symmetricPassphrase;
return this;
}
@ -131,7 +188,7 @@ public class PgpSignEncryptInput {
return mEncryptionMasterKeyIds;
}
public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
return this;
}
@ -140,7 +197,7 @@ public class PgpSignEncryptInput {
return mCompressionId;
}
public PgpSignEncryptInput setCompressionId(int compressionId) {
public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
mCompressionId = compressionId;
return this;
}
@ -153,28 +210,22 @@ public class PgpSignEncryptInput {
return mVersionHeader;
}
public PgpSignEncryptInput setVersionHeader(String versionHeader) {
public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) {
mVersionHeader = versionHeader;
return this;
}
public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
return this;
}
public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
return this;
}
public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) {
mNfcSignedHash = signedHash;
mNfcCreationTimestamp = creationTimestamp;
return this;
}
public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
this.mCleartextSignature = cleartextSignature;
return this;
}
@ -183,7 +234,7 @@ public class PgpSignEncryptInput {
return mCleartextSignature;
}
public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) {
public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) {
this.mDetachedSignature = detachedSignature;
return this;
}
@ -192,7 +243,7 @@ public class PgpSignEncryptInput {
return mDetachedSignature;
}
public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) {
public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) {
this.mHiddenRecipients = hiddenRecipients;
return this;
}
@ -200,5 +251,29 @@ public class PgpSignEncryptInput {
public boolean isHiddenRecipients() {
return mHiddenRecipients;
}
public PgpSignEncryptInputParcel setCryptoInput(CryptoInputParcel cryptoInput) {
mCryptoInput = cryptoInput;
return this;
}
public Map<ByteBuffer, byte[]> getCryptoData() {
return mCryptoInput.getCryptoData();
}
public Date getSignatureTime() {
return mCryptoInput.getSignatureTime();
}
public static final Creator<PgpSignEncryptInputParcel> CREATOR = new Creator<PgpSignEncryptInputParcel>() {
public PgpSignEncryptInputParcel createFromParcel(final Parcel source) {
return new PgpSignEncryptInputParcel(source);
}
public PgpSignEncryptInputParcel[] newArray(final int size) {
return new PgpSignEncryptInputParcel[size];
}
};
}

View file

@ -44,6 +44,8 @@ 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.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@ -71,7 +73,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
*
* For a high-level operation based on URIs, see SignEncryptOperation.
*
* @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
* @see PgpSignEncryptInputParcel
* @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
*
@ -99,7 +101,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
/**
* Signs and/or encrypts data based on parameters of class
*/
public PgpSignEncryptResult execute(PgpSignEncryptInput input,
public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input,
InputData inputData, OutputStream outputStream) {
int indent = 0;
@ -282,7 +284,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
try {
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
signatureGenerator = signingKey.getSignatureGenerator(
input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
input.getSignatureHashAlgorithm(), cleartext,
input.getCryptoData(), input.getSignatureTime());
} catch (PgpGeneralException e) {
log.add(LogType.MSG_PSE_ERROR_NFC, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
@ -489,7 +492,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
// Note that the checked key here is the master key, not the signing key
// (although these are always the same on Yubikeys)
result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
result.setNfcData(signingKey.getKeyId(), e.hashToSign, e.hashAlgo,
input.getSignaturePassphrase());
Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
return result;
}

View file

@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.pgp;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collection;
@ -40,7 +39,7 @@ import java.util.List;
* left, which will be returned in a byte array as part of the result parcel.
*
*/
public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable {
public class SignEncryptParcel extends PgpSignEncryptInputParcel {
public ArrayList<Uri> mInputUris = new ArrayList<>();
public ArrayList<Uri> mOutputUris = new ArrayList<>();
@ -51,26 +50,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
}
public SignEncryptParcel(Parcel src) {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mVersionHeader = src.readString();
mEnableAsciiArmorOutput = src.readInt() == 1;
mCompressionId = src.readInt();
mEncryptionMasterKeyIds = src.createLongArray();
mSymmetricPassphrase = src.readString();
mSymmetricEncryptionAlgorithm = src.readInt();
mSignatureMasterKeyId = src.readLong();
mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null;
mSignatureHashAlgorithm = src.readInt();
mSignaturePassphrase = src.readString();
mAdditionalEncryptId = src.readLong();
mNfcSignedHash = src.createByteArray();
mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null;
mFailOnMissingEncryptionKeyIds = src.readInt() == 1;
mCharset = src.readString();
mCleartextSignature = src.readInt() == 1;
mDetachedSignature = src.readInt() == 1;
mHiddenRecipients = src.readInt() == 1;
super(src);
mInputUris = src.createTypedArrayList(Uri.CREATOR);
mOutputUris = src.createTypedArrayList(Uri.CREATOR);
@ -108,34 +88,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVersionHeader);
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
dest.writeInt(mCompressionId);
dest.writeLongArray(mEncryptionMasterKeyIds);
dest.writeString(mSymmetricPassphrase);
dest.writeInt(mSymmetricEncryptionAlgorithm);
dest.writeLong(mSignatureMasterKeyId);
if (mSignatureSubKeyId != null) {
dest.writeInt(1);
dest.writeLong(mSignatureSubKeyId);
} else {
dest.writeInt(0);
}
dest.writeInt(mSignatureHashAlgorithm);
dest.writeString(mSignaturePassphrase);
dest.writeLong(mAdditionalEncryptId);
dest.writeByteArray(mNfcSignedHash);
if (mNfcCreationTimestamp != null) {
dest.writeInt(1);
dest.writeLong(mNfcCreationTimestamp.getTime());
} else {
dest.writeInt(0);
}
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
dest.writeString(mCharset);
dest.writeInt(mCleartextSignature ? 1 : 0);
dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0);
super.writeToParcel(dest, flags);
dest.writeTypedList(mInputUris);
dest.writeTypedList(mOutputUris);

View file

@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEnt
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
import org.sufficientlysecure.keychain.ui.NfcActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
@ -258,11 +259,13 @@ public class OpenPgpService extends RemoteService {
}
// carefully: only set if timestamp exists
Date nfcCreationDate = null;
Date nfcCreationDate;
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp);
if (nfcCreationTimestamp != -1) {
nfcCreationDate = new Date(nfcCreationTimestamp);
} else {
nfcCreationDate = new Date();
}
// Get Input- and OutputStream from ParcelFileDescriptor
@ -275,15 +278,18 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
CryptoInputParcel cryptoInput = new CryptoInputParcel(nfcCreationDate);
cryptoInput.addCryptoData(null, nfcSignedHash); // TODO fix
// sign-only
PgpSignEncryptInput pseInput = new PgpSignEncryptInput()
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel()
.setEnableAsciiArmorOutput(asciiArmor)
.setCleartextSignature(cleartextSign)
.setDetachedSignature(!cleartextSign)
.setVersionHeader(null)
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
.setSignatureMasterKeyId(signKeyId)
.setNfcState(nfcSignedHash, nfcCreationDate);
.setCryptoInput(cryptoInput);
// execute PGP operation!
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
@ -298,7 +304,7 @@ public class OpenPgpService extends RemoteService {
// return PendingIntent to execute NFC activity
// pass through the signature creation timestamp to be used again on second execution
// of PgpSignEncrypt when we have the signed hash!
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, nfcCreationDate.getTime());
// return PendingIntent to be executed by client
Intent result = new Intent();
@ -389,7 +395,7 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength, originalFilename);
PgpSignEncryptInput pseInput = new PgpSignEncryptInput();
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
pseInput.setEnableAsciiArmorOutput(asciiArmor)
.setVersionHeader(null)
.setCompressionId(compressionId)
@ -412,16 +418,21 @@ public class OpenPgpService extends RemoteService {
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
// carefully: only set if timestamp exists
Date nfcCreationDate = null;
Date nfcCreationDate;
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
if (nfcCreationTimestamp != -1) {
nfcCreationDate = new Date(nfcCreationTimestamp);
} else {
nfcCreationDate = new Date();
}
CryptoInputParcel cryptoInput = new CryptoInputParcel(nfcCreationDate);
cryptoInput.addCryptoData(null, nfcSignedHash); // TODO fix!
// sign and encrypt
pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
.setSignatureMasterKeyId(signKeyId)
.setNfcState(nfcSignedHash, nfcCreationDate)
.setCryptoInput(cryptoInput)
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
}
@ -439,7 +450,7 @@ public class OpenPgpService extends RemoteService {
// return PendingIntent to execute NFC activity
// pass through the signature creation timestamp to be used again on second execution
// of PgpSignEncrypt when we have the signed hash!
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, 0L); // TODO fix
// return PendingIntent to be executed by client
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT,

View file

@ -26,31 +26,31 @@ import java.util.ArrayList;
import java.util.Date;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.service.input.CryptoOperationParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
/**
* This class is a a transferable representation for a number of keyrings to
* be certified.
*/
public class CertifyActionsParcel extends CryptoOperationParcel {
public class CertifyActionsParcel implements Parcelable {
// the master key id to certify with
final public long mMasterKeyId;
public CertifyLevel mLevel;
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
public CryptoInputParcel mCryptoInput;
public CertifyActionsParcel(Date operationTime, long masterKeyId) {
super(operationTime);
mMasterKeyId = masterKeyId;
mCryptoInput = new CryptoInputParcel(operationTime);
mLevel = CertifyLevel.DEFAULT;
}
public CertifyActionsParcel(Parcel source) {
super(source);
mMasterKeyId = source.readLong();
mCryptoInput = source.readParcelable(CertifyActionsParcel.class.getClassLoader());
// just like parcelables, this is meant for ad-hoc IPC only and is NOT portable!
mLevel = CertifyLevel.values()[source.readInt()];
@ -63,9 +63,8 @@ public class CertifyActionsParcel extends CryptoOperationParcel {
@Override
public void writeToParcel(Parcel destination, int flags) {
super.writeToParcel(destination, flags);
destination.writeLong(mMasterKeyId);
destination.writeParcelable(mCryptoInput, 0);
destination.writeInt(mLevel.ordinal());
destination.writeSerializable(mCertifyActions);

View file

@ -0,0 +1,81 @@
package org.sufficientlysecure.keychain.service.input;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import android.os.Parcel;
import android.os.Parcelable;
/** This is a base class for the input of crypto operations.
*
*/
public class CryptoInputParcel implements Parcelable {
Date mSignatureTime;
// this map contains both decrypted session keys and signed hashes to be
// used in the crypto operation described by this parcel.
private HashMap<ByteBuffer,byte[]> mCryptoData = new HashMap<>();
public CryptoInputParcel(Date signatureTime) {
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
}
protected CryptoInputParcel(Parcel source) {
mSignatureTime = new Date(source.readLong());
{
int count = source.readInt();
mCryptoData = new HashMap<>(count);
for (int i = 0; i < count; i++) {
byte[] key = source.createByteArray();
byte[] value = source.createByteArray();
mCryptoData.put(ByteBuffer.wrap(key), value);
}
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mSignatureTime.getTime());
dest.writeInt(mCryptoData.size());
for (HashMap.Entry<ByteBuffer,byte[]> entry : mCryptoData.entrySet()) {
dest.writeByteArray(entry.getKey().array());
dest.writeByteArray(entry.getValue());
}
}
public void addCryptoData(byte[] hash, byte[] signedHash) {
mCryptoData.put(ByteBuffer.wrap(hash), signedHash);
}
public Map<ByteBuffer, byte[]> getCryptoData() {
return Collections.unmodifiableMap(mCryptoData);
}
public Date getSignatureTime() {
return mSignatureTime;
}
public static final Creator<CryptoInputParcel> CREATOR = new Creator<CryptoInputParcel>() {
public CryptoInputParcel createFromParcel(final Parcel source) {
return new CryptoInputParcel(source);
}
public CryptoInputParcel[] newArray(final int size) {
return new CryptoInputParcel[size];
}
};
}

View file

@ -1,52 +0,0 @@
package org.sufficientlysecure.keychain.service.input;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
import android.os.Parcel;
import android.os.Parcelable;
/** This is a base class for the input of crypto operations.
*
*/
public abstract class CryptoOperationParcel implements Parcelable {
Date mOperationTime;
// this map contains both decrypted session keys and signed hashes to be
// used in the crypto operation described by this parcel.
HashMap<ByteBuffer,byte[]> mCryptoData;
protected CryptoOperationParcel(Date operationTime) {
mOperationTime = operationTime;
}
protected CryptoOperationParcel(Parcel source) {
mOperationTime = new Date(source.readLong());
{
int count = source.readInt();
mCryptoData = new HashMap<>(count);
for (int i = 0; i < count; i++) {
byte[] key = source.createByteArray();
byte[] value = source.createByteArray();
mCryptoData.put(ByteBuffer.wrap(key), value);
}
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mOperationTime.getTime());
dest.writeInt(mCryptoData.size());
for (HashMap.Entry<ByteBuffer,byte[]> entry : mCryptoData.entrySet()) {
dest.writeByteArray(entry.getKey().array());
dest.writeByteArray(entry.getValue());
}
}
}

View file

@ -0,0 +1,85 @@
package org.sufficientlysecure.keychain.service.input;
import java.util.Date;
import android.os.Parcel;
import android.os.Parcelable;
public class NfcOperationsParcel implements Parcelable {
public enum NfcOperationType {
NFC_SIGN, NFC_DECRYPT
}
public Date mSignatureTime;
public final NfcOperationType mType;
public final byte[][] mInputHash;
public final int[] mSignAlgo;
private NfcOperationsParcel(NfcOperationType type, byte[] inputHash, int signAlgo, Date signatureTime) {
mType = type;
mInputHash = new byte[][] { inputHash };
mSignAlgo = new int[] { signAlgo };
mSignatureTime = signatureTime;
}
public NfcOperationsParcel(Parcel source) {
mType = NfcOperationType.values()[source.readInt()];
{
int count = source.readInt();
mInputHash = new byte[count][];
mSignAlgo = new int[count];
for (int i = 0; i < count; i++) {
mInputHash[i] = source.createByteArray();
mSignAlgo[i] = source.readInt();
}
}
mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
}
public static NfcOperationsParcel createNfcSignOperation(
byte[] inputHash, int signAlgo, Date signatureTime) {
return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, inputHash, signAlgo, signatureTime);
}
public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) {
return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, inputHash, 0, null);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType.ordinal());
dest.writeInt(mInputHash.length);
for (int i = 0; i < mInputHash.length; i++) {
dest.writeByteArray(mInputHash[i]);
dest.writeInt(mSignAlgo[i]);
}
if (mSignatureTime != null) {
dest.writeInt(1);
dest.writeLong(mSignatureTime.getTime());
} else {
dest.writeInt(0);
}
}
public static final Creator<NfcOperationsParcel> CREATOR = new Creator<NfcOperationsParcel>() {
public NfcOperationsParcel createFromParcel(final Parcel source) {
return new NfcOperationsParcel(source);
}
public NfcOperationsParcel[] newArray(final int size) {
return new NfcOperationsParcel[size];
}
};
}

View file

@ -25,15 +25,15 @@ import android.os.Message;
import android.os.Messenger;
import android.view.View;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import java.util.Date;
public abstract class EncryptActivity extends BaseActivity {
@ -42,8 +42,6 @@ public abstract class EncryptActivity extends BaseActivity {
// For NFC data
protected String mSigningKeyPassphrase = null;
protected Date mNfcTimestamp = null;
protected byte[] mNfcHash = null;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -64,17 +62,12 @@ public abstract class EncryptActivity extends BaseActivity {
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
}
protected void startNfcSign(long keyId, String pin, byte[] hashToSign, int hashAlgo) {
// build PendingIntent for Yubikey NFC operations
Intent intent = new Intent(this, NfcActivity.class);
intent.setAction(NfcActivity.ACTION_SIGN_HASH);
protected void startNfcSign(long keyId, String pin, NfcOperationsParcel nfcOps) {
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
Intent intent = new Intent(this, NfcOperationActivity.class);
intent.putExtra(NfcOperationActivity.EXTRA_PIN, pin);
intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps);
// TODO respect keyid(?)
startActivityForResult(intent, REQUEST_CODE_NFC);
}
@ -93,8 +86,9 @@ public abstract class EncryptActivity extends BaseActivity {
case REQUEST_CODE_NFC: {
if (resultCode == RESULT_OK && data != null) {
mNfcHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
startEncrypt();
CryptoInputParcel cryptoInput =
data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
startEncrypt(cryptoInput);
return;
}
break;
@ -108,6 +102,10 @@ public abstract class EncryptActivity extends BaseActivity {
}
public void startEncrypt() {
startEncrypt(null);
}
public void startEncrypt(CryptoInputParcel cryptoInput) {
if (!inputIsValid()) {
// Notify was created by inputIsValid.
return;
@ -117,8 +115,13 @@ public abstract class EncryptActivity extends BaseActivity {
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
final SignEncryptParcel input = createEncryptBundle();
if (cryptoInput != null) {
input.setCryptoInput(cryptoInput);
}
Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle());
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in KeychainIntentService
@ -141,9 +144,13 @@ public abstract class EncryptActivity extends BaseActivity {
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
PgpSignEncryptResult.RESULT_PENDING_NFC) {
mNfcTimestamp = pgpResult.getNfcTimestamp();
startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(),
pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
NfcOperationsParcel parcel = NfcOperationsParcel.createNfcSignOperation(
pgpResult.getNfcHash(),
pgpResult.getNfcAlgo(),
input.getSignatureTime());
startNfcSign(pgpResult.getNfcKeyId(),
pgpResult.getNfcPassphrase(), parcel);
} else {
throw new RuntimeException("Unhandled pending result!");
}
@ -158,8 +165,6 @@ public abstract class EncryptActivity extends BaseActivity {
// no matter the result, reset parameters
mSigningKeyPassphrase = null;
mNfcHash = null;
mNfcTimestamp = null;
}
}
};

View file

@ -252,7 +252,6 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
data.setSignatureMasterKeyId(mSigningKeyId);
data.setSignaturePassphrase(mSigningKeyPassphrase);
data.setNfcState(mNfcHash, mNfcTimestamp);
}
return data;
}

View file

@ -232,7 +232,6 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
data.setSignatureMasterKeyId(mSigningKeyId);
data.setSignaturePassphrase(mSigningKeyPassphrase);
data.setNfcState(mNfcHash, mNfcTimestamp);
}
return data;
}

View file

@ -0,0 +1,492 @@
/**
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
*
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
*/
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.Toast;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
* NFC devices.
*
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public class NfcOperationActivity extends BaseActivity {
public static final String EXTRA_PIN = "pin";
public static final String EXTRA_NFC_OPS = "nfc_operations";
public static final String RESULT_DATA = "result_data";
private static final int TIMEOUT = 100000;
private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep;
private String mPin;
NfcOperationsParcel mNfcOperations;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Intent intent = getIntent();
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
}
Bundle data = intent.getExtras();
mNfcOperations = data.getParcelable(EXTRA_NFC_OPS);
mPin = data.getString(EXTRA_PIN);
}
@Override
protected void initLayout() {
setContentView(R.layout.nfc_activity);
}
/**
* Called when the system is about to start resuming a previous activity,
* disables NFC Foreground Dispatch
*/
public void onPause() {
super.onPause();
Log.d(Constants.TAG, "NfcOperationActivity.onPause");
disableNfcForegroundDispatch();
}
/**
* Called when the activity will start interacting with the user,
* enables NFC Foreground Dispatch
*/
public void onResume() {
super.onResume();
Log.d(Constants.TAG, "NfcOperationActivity.onResume");
enableNfcForegroundDispatch();
}
/**
* This activity is started as a singleTop activity.
* All new NFC Intents which are delivered to this activity are handled here
*/
public void onNewIntent(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
try {
handleNdefDiscoveredIntent(intent);
} catch (IOException e) {
Log.e(Constants.TAG, "Connection error!", e);
toast("Connection Error: " + e.getMessage());
setResult(RESULT_CANCELED);
finish();
}
}
}
/** Handle NFC communication and return a result.
*
* This method is called by onNewIntent above upon discovery of an NFC tag.
* It handles initialization and login to the application, subsequently
* calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
* finishes the activity with an appropiate result.
*
* On general communication, see also
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
*
* References to pages are generally related to the OpenPGP Application
* on ISO SmartCard Systems specification.
*
*/
private void handleNdefDiscoveredIntent(Intent intent) throws IOException {
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
// Connect to the detected tag, setting a couple of settings
mIsoDep = IsoDep.get(detectedTag);
mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
mIsoDep.connect();
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
// See specification, page 51
String accepted = "9000";
// Command APDU (page 51) for SELECT FILE command (page 29)
String opening =
"00" // CLA
+ "A4" // INS
+ "04" // P1
+ "00" // P2
+ "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes)
+ "00"; // Le
if ( ! card(opening).equals(accepted)) { // activate connection
toast("Opening Error!");
setResult(RESULT_CANCELED);
finish();
return;
}
// Command APDU for VERIFY command (page 32)
String login =
"00" // CLA
+ "20" // INS
+ "00" // P1
+ "82" // P2 (PW1)
+ String.format("%02x", mPin.length()) // Lc
+ Hex.toHexString(mPin.getBytes());
if ( ! card(login).equals(accepted)) { // login
toast("Wrong PIN!");
setResult(RESULT_CANCELED);
finish();
return;
}
CryptoInputParcel resultData = new CryptoInputParcel(mNfcOperations.mSignatureTime);
switch (mNfcOperations.mType) {
case NFC_DECRYPT:
for (int i = 0; i < mNfcOperations.mInputHash.length; i++) {
byte[] hash = mNfcOperations.mInputHash[i];
byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
resultData.addCryptoData(hash, decryptedSessionKey);
}
break;
case NFC_SIGN:
for (int i = 0; i < mNfcOperations.mInputHash.length; i++) {
byte[] hash = mNfcOperations.mInputHash[i];
int algo = mNfcOperations.mSignAlgo[i];
byte[] signedHash = nfcCalculateSignature(hash, algo);
resultData.addCryptoData(hash, signedHash);
}
break;
}
// give data through for new service call
Intent result = new Intent();
result.putExtra(RESULT_DATA, resultData);
setResult(RESULT_OK, result);
finish();
}
/**
* Gets the user ID
*
* @return the user id as "name <email>"
* @throws java.io.IOException
*/
public String getUserId() throws IOException {
String info = "00CA006500";
String data = "00CA005E00";
return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">";
}
/** Return the key id from application specific data stored on tag, or null
* if it doesn't exist.
*
* @param idx Index of the key to return the fingerprint from.
* @return The long key id of the requested key, or null if not found.
*/
public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException {
byte[] fp = nfcGetFingerprint(isoDep, idx);
if (fp == null) {
return null;
}
ByteBuffer buf = ByteBuffer.wrap(fp);
// skip first 12 bytes of the fingerprint
buf.position(12);
// the last eight bytes are the key id (big endian, which is default order in ByteBuffer)
return buf.getLong();
}
/** Return fingerprints of all keys from application specific data stored
* on tag, or null if data not available.
*
* @return The fingerprints of all subkeys in a contiguous byte array.
*/
public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException {
String data = "00CA006E00";
byte[] buf = isoDep.transceive(Hex.decode(data));
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
if (fptlv == null) {
return null;
}
return fptlv.mV;
}
/** Return the fingerprint from application specific data stored on tag, or
* null if it doesn't exist.
*
* @param idx Index of the key to return the fingerprint from.
* @return The fingerprint of the requested key, or null if not found.
*/
public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException {
byte[] data = nfcGetFingerprints(isoDep);
// return the master key fingerprint
ByteBuffer fpbuf = ByteBuffer.wrap(data);
byte[] fp = new byte[20];
fpbuf.position(idx*20);
fpbuf.get(fp, 0, 20);
return fp;
}
/**
* Calls to calculate the signature and returns the MPI value
*
* @param hash the hash for signing
* @return a big integer representing the MPI for the given hash
* @throws java.io.IOException
*/
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
// dsi, including Lc
String dsi;
Log.i(Constants.TAG, "Hash: " + hashAlgo);
switch (hashAlgo) {
case HashAlgorithmTags.SHA1:
if (hash.length != 20) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!");
}
dsi = "23" // Lc
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
+ "0605" + "2B0E03021A" // OID of SHA1
+ "0500" // TLV coding of ZERO
+ "0414" + getHex(hash); // 0x14 are 20 hash bytes
break;
case HashAlgorithmTags.RIPEMD160:
if (hash.length != 20) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!");
}
dsi = "233021300906052B2403020105000414" + getHex(hash);
break;
case HashAlgorithmTags.SHA224:
if (hash.length != 28) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!");
}
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
break;
case HashAlgorithmTags.SHA256:
if (hash.length != 32) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!");
}
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
break;
case HashAlgorithmTags.SHA384:
if (hash.length != 48) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!");
}
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
break;
case HashAlgorithmTags.SHA512:
if (hash.length != 64) {
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!");
}
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
break;
default:
throw new RuntimeException("Not supported hash algo!");
}
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
String apdu =
"002A9E9A" // CLA, INS, P1, P2
+ dsi // digital signature input
+ "00"; // Le
String response = card(apdu);
// split up response into signature and status
String status = response.substring(response.length()-4);
String signature = response.substring(0, response.length() - 4);
// while we are getting 0x61 status codes, retrieve more data
while (status.substring(0, 2).equals("61")) {
Log.d(Constants.TAG, "requesting more data, status " + status);
// Send GET RESPONSE command
response = card("00C00000" + status.substring(2));
status = response.substring(response.length()-4);
signature += response.substring(0, response.length()-4);
}
Log.d(Constants.TAG, "final response:" + status);
if ( ! status.equals("9000")) {
toast("Bad NFC response code: " + status);
return null;
}
// Make sure the signature we received is actually the expected number of bytes long!
if (signature.length() != 256 && signature.length() != 512) {
toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
return null;
}
return Hex.decode(signature);
}
/**
* Calls to calculate the signature and returns the MPI value
*
* @param encryptedSessionKey the encoded session key
* @return the decoded session key
* @throws java.io.IOException
*/
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
String firstApdu = "102a8086fe";
String secondApdu = "002a808603";
String le = "00";
byte[] one = new byte[254];
// leave out first byte:
System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
for (int i = 0; i < two.length; i++) {
two[i] = encryptedSessionKey[i + one.length + 1];
}
String first = card(firstApdu + getHex(one));
String second = card(secondApdu + getHex(two) + le);
String decryptedSessionKey = getDataField(second);
Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
return Hex.decode(decryptedSessionKey);
}
/**
* Prints a message to the screen
*
* @param text the text which should be contained within the toast
*/
private void toast(String text) {
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
/**
* Receive new NFC Intents to this activity only by enabling foreground dispatch.
* This can only be done in onResume!
*/
public void enableNfcForegroundDispatch() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
Intent nfcI = new Intent(this, NfcOperationActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
IntentFilter[] writeTagFilters = new IntentFilter[]{
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
};
// https://code.google.com/p/android/issues/detail?id=62918
// maybe mNfcAdapter.enableReaderMode(); ?
try {
mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
} catch (IllegalStateException e) {
Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e);
}
Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!");
}
/**
* Disable foreground dispatch in onPause!
*/
public void disableNfcForegroundDispatch() {
mNfcAdapter.disableForegroundDispatch(this);
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
}
/**
* Gets the name of the user out of the raw card output regarding card holder related data
*
* @param name the raw card holder related data from the card
* @return the name given in this data
*/
public String getName(String name) {
String slength;
int ilength;
name = name.substring(6);
slength = name.substring(0, 2);
ilength = Integer.parseInt(slength, 16) * 2;
name = name.substring(2, ilength + 2);
name = (new String(Hex.decode(name))).replace('<', ' ');
return (name);
}
/**
* Reduces the raw data from the card by four characters
*
* @param output the raw data from the card
* @return the data field of that data
*/
private String getDataField(String output) {
return output.substring(0, output.length() - 4);
}
/**
* Communicates with the OpenPgpCard via the APDU
*
* @param hex the hexadecimal APDU
* @return The answer from the card
* @throws java.io.IOException throws an exception if something goes wrong
*/
public String card(String hex) throws IOException {
return getHex(mIsoDep.transceive(Hex.decode(hex)));
}
/**
* Converts a byte array into an hex string
*
* @param raw the byte array representation
* @return the hexadecimal string representation
*/
public static String getHex(byte[] raw) {
return new String(Hex.encode(raw));
}
}