diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
4 files changed, 156 insertions, 133 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index af058b618..e39924f7e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.S2K; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; @@ -27,20 +29,24 @@ import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; 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.Date; +import java.util.LinkedList; import java.util.List; /** Wrapper for a PGPSecretKey. @@ -59,6 +65,11 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { 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; + CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) { super(ring, key.getPublicKey()); mSecretKey = key; @@ -68,11 +79,23 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return (CanonicalizedSecretKeyRing) mRing; } + /** + * 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; } @@ -82,16 +105,56 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { 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, Date nfcCreationTimestamp) 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) { + // 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! + if (nfcCreationTimestamp == null) { + nfcCreationTimestamp = new Date(); + } + + // use synchronous "NFC based" SignerBuilder + contentSignerBuilder = new NfcSyncPGPContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), hashAlgo, + mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + Log.d(Constants.TAG, "mSecretKey.getKeyID() "+ PgpKeyHelper.convertKeyIdToHex(mSecretKey.getKeyID())); + } 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) { @@ -107,43 +170,21 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback()); + if (nfcCreationTimestamp != null) { + spGen.setSignatureCreationTime(false, nfcCreationTimestamp); + Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); + } signatureGenerator.setHashedSubpackets(spGen.generate()); return signatureGenerator; } catch(PGPException e) { - throw new PgpGeneralException("Error initializing signature!", e); - } - } - - public PGPV3SignatureGenerator getV3SignatureGenerator(int hashAlgo, boolean cleartext) - throws PgpGeneralException { - if(mPrivateKey == null) { - 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); - - int signatureType; - if (cleartext) { - // for sign-only ascii text - signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; - } else { - signatureType = PGPSignature.BINARY_DOCUMENT; - } - - try { - PGPV3SignatureGenerator signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(signatureType, mPrivateKey); - return signatureV3Generator; - } catch(PGPException e) { + // TODO: simply throw PGPException! throw new PgpGeneralException("Error initializing signature!", e); } } public PublicKeyDataDecryptorFactory getDecryptorFactory() { - if(mPrivateKey == null) { + // TODO: divert to card missing + if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) { throw new PrivateKeyNotUnlockedException(); } return new JcePublicKeyDataDecryptorFactoryBuilder() @@ -161,7 +202,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException { - if(mPrivateKey == null) { + // TODO: divert to card missing + if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) { throw new PrivateKeyNotUnlockedException(); } @@ -206,4 +248,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return new UncachedSecretKey(mSecretKey); } + // HACK + public PGPSecretKey getSecretKey() { + return mSecretKey; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index 80889f70d..4f74a2336 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -78,8 +78,8 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { // then, mark exactly the keys we have available for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(getRing().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.getProtectionMode() != S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY)) { result.add(sub.getKeyID()); } } 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 0c640538f..459b80be2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -91,7 +91,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 d8bf0d4d9..67eced699 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -27,9 +27,9 @@ import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; import org.spongycastle.openpgp.PGPLiteralDataGenerator; 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; @@ -49,6 +49,7 @@ import java.security.NoSuchProviderException; import java.security.SignatureException; import java.util.Arrays; import java.util.Date; +import java.util.LinkedList; /** * This class uses a Builder pattern! @@ -67,11 +68,13 @@ public class PgpSignEncrypt { private int mSymmetricEncryptionAlgorithm; private long mSignatureMasterKeyId; private int mSignatureHashAlgorithm; - private boolean mSignatureForceV3; private String mSignaturePassphrase; private boolean mEncryptToSigner; private boolean mCleartextInput; + private byte[] mNfcSignedHash = null; + private Date mNfcCreationTimestamp = null; + private static byte[] NEW_LINE; static { @@ -97,10 +100,11 @@ public class PgpSignEncrypt { this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm; this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; - this.mSignatureForceV3 = builder.mSignatureForceV3; this.mSignaturePassphrase = builder.mSignaturePassphrase; this.mEncryptToSigner = builder.mEncryptToSigner; this.mCleartextInput = builder.mCleartextInput; + this.mNfcSignedHash = builder.mNfcSignedHash; + this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; } public static class Builder { @@ -119,11 +123,13 @@ public class PgpSignEncrypt { private int mSymmetricEncryptionAlgorithm = 0; private long mSignatureMasterKeyId = Constants.key.none; private int mSignatureHashAlgorithm = 0; - private boolean mSignatureForceV3 = false; private String mSignaturePassphrase = null; private boolean mEncryptToSigner = false; private boolean mCleartextInput = false; + private byte[] mNfcSignedHash = null; + private Date mNfcCreationTimestamp = null; + public Builder(ProviderHelper providerHelper, String versionHeader, InputData data, OutputStream outStream) { this.mProviderHelper = providerHelper; this.mVersionHeader = versionHeader; @@ -131,7 +137,7 @@ public class PgpSignEncrypt { this.mOutStream = outStream; } - public Builder setProgressable(Progressable progressable) { + public Builder setProgressable(Progressable progressable) { mProgressable = progressable; return this; } @@ -171,11 +177,6 @@ public class PgpSignEncrypt { return this; } - public Builder setSignatureForceV3(boolean signatureForceV3) { - mSignatureForceV3 = signatureForceV3; - return this; - } - public Builder setSignaturePassphrase(String signaturePassphrase) { mSignaturePassphrase = signaturePassphrase; return this; @@ -201,6 +202,12 @@ public class PgpSignEncrypt { return this; } + public Builder setNfcState(byte[] signedHash, Date creationTimestamp) { + mNfcSignedHash = signedHash; + mNfcCreationTimestamp = creationTimestamp; + return this; + } + public PgpSignEncrypt build() { return new PgpSignEncrypt(this); } @@ -228,17 +235,32 @@ 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[] mHashToSign; + public Date mCreationTimestamp; + + public NeedNfcDataException(byte[] hashToSign, Date creationTimestamp) { + mHashToSign = hashToSign; + mCreationTimestamp = creationTimestamp; + } + } + /** * 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) @@ -256,16 +278,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 */ CanonicalizedSecretKey signingKey = null; if (enableSignature) { @@ -277,7 +289,7 @@ public class PgpSignEncrypt { } try { signingKey = signingKeyRing.getSigningSubKey(); - } catch(PgpGeneralException e) { + } catch (PgpGeneralException e) { throw new NoSigningKeyException(); } @@ -288,10 +300,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); @@ -332,29 +353,34 @@ public class PgpSignEncrypt { /* Initialize signature generator object for later usage */ PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; if (enableSignature) { updateProgress(R.string.progress_preparing_signature, 10, 100); try { boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; - if (mSignatureForceV3) { - signatureV3Generator = signingKey.getV3SignatureGenerator( - mSignatureHashAlgorithm,cleartext); - } else { - signatureGenerator = signingKey.getSignatureGenerator( - mSignatureHashAlgorithm, cleartext); - } + signatureGenerator = signingKey.getSignatureGenerator( + mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); } catch (PgpGeneralException e) { // TODO throw correct type of exception (which shouldn't be PGPException) throw new KeyExtractionException(); } } + 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 */ @@ -368,11 +394,7 @@ public class PgpSignEncrypt { } if (enableSignature) { - if (mSignatureForceV3) { - signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); - } else { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); } PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); @@ -390,11 +412,7 @@ public class PgpSignEncrypt { // update signature buffer if signature is requested if (enableSignature) { - if (mSignatureForceV3) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } + signatureGenerator.update(buffer, 0, n); } progress += n; @@ -416,11 +434,7 @@ public class PgpSignEncrypt { final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // update signature buffer with first line - if (mSignatureForceV3) { - processLineV3(reader.readLine(), armorOut, signatureV3Generator); - } else { - processLine(reader.readLine(), armorOut, signatureGenerator); - } + processLine(reader.readLine(), armorOut, signatureGenerator); while (true) { String line = reader.readLine(); @@ -434,13 +448,8 @@ public class PgpSignEncrypt { armorOut.write(NEW_LINE); // update signature buffer with input line - if (mSignatureForceV3) { - signatureV3Generator.update(NEW_LINE); - processLineV3(line, armorOut, signatureV3Generator); - } else { - signatureGenerator.update(NEW_LINE); - processLine(line, armorOut, signatureGenerator); - } + signatureGenerator.update(NEW_LINE); + processLine(line, armorOut, signatureGenerator); } armorOut.endClearText(); @@ -460,11 +469,7 @@ public class PgpSignEncrypt { bcpgOut = new BCPGOutputStream(out); } - if (mSignatureForceV3) { - signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); - } else { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); // file name not needed, so empty string @@ -476,11 +481,7 @@ public class PgpSignEncrypt { while ((n = in.read(buffer)) > 0) { pOut.write(buffer, 0, n); - if (mSignatureForceV3) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } + signatureGenerator.update(buffer, 0, n); } literalGen.close(); @@ -491,10 +492,11 @@ public class PgpSignEncrypt { if (enableSignature) { updateProgress(R.string.progress_generating_signature, 95, 100); - if (mSignatureForceV3) { - signatureV3Generator.generate().encode(pOut); - } else { + try { signatureGenerator.generate().encode(pOut); + } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { + // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed + throw new NeedNfcDataException(e.hashToSign, e.creationTimestamp); } } @@ -544,30 +546,4 @@ public class PgpSignEncrypt { pSignatureGenerator.update(data); } - private static void processLineV3(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPV3SignatureGenerator pSignatureGenerator) - throws IOException, SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - } |