diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
17 files changed, 1690 insertions, 753 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java new file mode 100644 index 000000000..dc0c722b9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -0,0 +1,36 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; + +/** An abstract KeyRing. + * + * This is an abstract class for all KeyRing constructs. It serves as a common + * denominator of available information, two implementations wrapping the same + * keyring should in all cases agree on the output of all methods described + * here. + * + * @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing + * @see org.sufficientlysecure.keychain.provider.CachedPublicKeyRing + * + */ +public abstract class KeyRing { + + abstract public long getMasterKeyId() throws PgpGeneralException; + + abstract public String getPrimaryUserId() throws PgpGeneralException; + + abstract public boolean isRevoked() throws PgpGeneralException; + + abstract public boolean canCertify() throws PgpGeneralException; + + abstract public long getEncryptId() throws PgpGeneralException; + + abstract public boolean hasEncrypt() throws PgpGeneralException; + + abstract public long getSignId() throws PgpGeneralException; + + abstract public boolean hasSign() throws PgpGeneralException; + + abstract public int getVerified() throws PgpGeneralException; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java index 86fba979c..591ccdc8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java @@ -21,49 +21,25 @@ import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureList; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; - public class PgpConversionHelper { /** - * Convert from byte[] to PGPKeyRing - * - * @param keysBytes - * @return - */ - public static PGPKeyRing BytesToPGPKeyRing(byte[] keysBytes) { - PGPObjectFactory factory = new PGPObjectFactory(keysBytes); - PGPKeyRing keyRing = null; - try { - if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) { - Log.e(Constants.TAG, "No keys given!"); - } - } catch (IOException e) { - Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e); - } - - return keyRing; - } - - /** * Convert from byte[] to ArrayList<PGPSecretKey> * * @param keysBytes * @return */ - public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) { + public static ArrayList<UncachedSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) { PGPObjectFactory factory = new PGPObjectFactory(keysBytes); Object obj = null; - ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>(); + ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>(); try { while ((obj = factory.nextObject()) != null) { PGPSecretKey secKey = null; @@ -72,7 +48,7 @@ public class PgpConversionHelper { if (secKey == null) { Log.e(Constants.TAG, "No keys given!"); } - keys.add(secKey); + keys.add(new UncachedSecretKey(secKey)); } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings PGPSecretKeyRing keyRing = null; keyRing = (PGPSecretKeyRing) obj; @@ -82,7 +58,7 @@ public class PgpConversionHelper { @SuppressWarnings("unchecked") Iterator<PGPSecretKey> itr = keyRing.getSecretKeys(); while (itr.hasNext()) { - keys.add(itr.next()); + keys.add(new UncachedSecretKey(itr.next())); } } } @@ -100,7 +76,7 @@ public class PgpConversionHelper { * @param keyBytes * @return */ - public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) { + public static UncachedSecretKey BytesToPGPSecretKey(byte[] keyBytes) { PGPObjectFactory factory = new PGPObjectFactory(keyBytes); Object obj = null; try { @@ -121,80 +97,7 @@ public class PgpConversionHelper { secKey = keyRing.getSecretKey(); } - return secKey; - } - - /** - * Convert from byte[] to PGPSignature - * - * @param sigBytes - * @return - */ - public static PGPSignature BytesToPGPSignature(byte[] sigBytes) { - PGPObjectFactory factory = new PGPObjectFactory(sigBytes); - PGPSignatureList signatures = null; - try { - if ((signatures = (PGPSignatureList) factory.nextObject()) == null || signatures.isEmpty()) { - Log.e(Constants.TAG, "No signatures given!"); - return null; - } - } catch (IOException e) { - Log.e(Constants.TAG, "Error while converting to PGPSignature!", e); - return null; - } - - return signatures.get(0); - } - - /** - * Convert from ArrayList<PGPSecretKey> to byte[] - * - * @param keys - * @return - */ - public static byte[] PGPSecretKeyArrayListToBytes(ArrayList<PGPSecretKey> keys) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - for (PGPSecretKey key : keys) { - try { - key.encode(os); - } catch (IOException e) { - Log.e(Constants.TAG, "Error while converting ArrayList<PGPSecretKey> to byte[]!", e); - } - } - - return os.toByteArray(); - } - - /** - * Convert from PGPSecretKey to byte[] - * - * @param key - * @return - */ - public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) { - try { - return key.getEncoded(); - } catch (IOException e) { - Log.e(Constants.TAG, "Encoding failed", e); - - return null; - } - } - - /** - * Convert from PGPSecretKeyRing to byte[] - * - * @param keyRing - * @return - */ - public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) { - try { - return keyRing.getEncoded(); - } catch (IOException e) { - Log.e(Constants.TAG, "Encoding failed", e); - - return null; - } + return new UncachedSecretKey(secKey); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 506c161ba..c009d1b5c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -18,10 +18,7 @@ package org.sufficientlysecure.keychain.pgp; -import android.net.Uri; - import org.spongycastle.bcpg.ArmoredInputStream; -import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; @@ -31,29 +28,19 @@ import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPOnePassSignature; import org.spongycastle.openpgp.PGPOnePassSignatureList; import org.spongycastle.openpgp.PGPPBEEncryptedData; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.InputData; @@ -67,7 +54,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.SignatureException; import java.util.Iterator; -import java.util.Map; import java.util.Set; /** @@ -248,7 +234,7 @@ public class PgpDecryptVerify { PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; PGPPBEEncryptedData encryptedDataSymmetric = null; - PGPSecretKey secretEncryptionKey = null; + WrappedSecretKey secretEncryptionKey = null; Iterator<?> it = enc.getEncryptedDataObjects(); boolean asymmetricPacketFound = false; boolean symmetricPacketFound = false; @@ -260,15 +246,12 @@ public class PgpDecryptVerify { PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - long masterKeyId; - PGPSecretKeyRing secretKeyRing; + WrappedSecretKeyRing secretKeyRing; try { - // get master key id for this encryption key id - masterKeyId = mProviderHelper.getMasterKeyId( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(encData.getKeyID())) - ); // get actual keyring object based on master key id - secretKeyRing = mProviderHelper.getPGPSecretKeyRing(masterKeyId); + secretKeyRing = mProviderHelper.getWrappedSecretKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(encData.getKeyID()) + ); } catch (ProviderHelper.NotFoundException e) { // continue with the next packet in the while loop continue; @@ -278,13 +261,14 @@ public class PgpDecryptVerify { continue; } // get subkey which has been used for this encryption packet - secretEncryptionKey = secretKeyRing.getSecretKey(encData.getKeyID()); + secretEncryptionKey = secretKeyRing.getSubKey(encData.getKeyID()); if (secretEncryptionKey == null) { // continue with the next packet in the while loop continue; } /* secret key exists in database! */ + long masterKeyId = secretEncryptionKey.getRing().getMasterKeyId(); // allow only specific keys for decryption? if (mAllowedKeyIds != null) { @@ -359,23 +343,17 @@ public class PgpDecryptVerify { } else if (asymmetricPacketFound) { currentProgress += 5; updateProgress(R.string.progress_extracting_key, currentProgress, 100); - PGPPrivateKey privateKey; try { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - mPassphrase.toCharArray()); - privateKey = secretEncryptionKey.extractPrivateKey(keyDecryptor); - } catch (PGPException e) { - throw new WrongPassphraseException(); - } - if (privateKey == null) { + if (!secretEncryptionKey.unlock(mPassphrase)) { + throw new WrongPassphraseException(); + } + } catch(PgpGeneralException e) { throw new KeyExtractionException(); } currentProgress += 5; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); + PublicKeyDataDecryptorFactory decryptorFactory = secretEncryptionKey.getDecryptorFactory(); clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); @@ -388,10 +366,10 @@ public class PgpDecryptVerify { PGPObjectFactory plainFact = new PGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); - PGPOnePassSignature signature = null; OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); - PGPPublicKey signatureKey = null; int signatureIndex = -1; + WrappedPublicKeyRing signingRing = null; + WrappedPublicKey signingKey = null; if (dataChunk instanceof PGPCompressedData) { updateProgress(R.string.progress_decompressing_data, currentProgress, 100); @@ -403,6 +381,8 @@ public class PgpDecryptVerify { currentProgress += 10; } + PGPOnePassSignature signature = null; + if (dataChunk instanceof PGPOnePassSignatureList) { updateProgress(R.string.progress_processing_signature, currentProgress, 100); @@ -410,19 +390,13 @@ public class PgpDecryptVerify { // go through all signatures // and find out for which signature we have a key in our database - Long masterKeyId = null; - String primaryUserId = null; for (int i = 0; i < sigList.size(); ++i) { try { - Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( - Long.toString(sigList.get(i).getKeyID())); - Map<String, Object> data = mProviderHelper.getGenericData(uri, - new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID }, - new int[] { ProviderHelper.FIELD_TYPE_INTEGER, - ProviderHelper.FIELD_TYPE_STRING } + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getWrappedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) ); - masterKeyId = (Long) data.get(KeyRings.MASTER_KEY_ID); - primaryUserId = (String) data.get(KeyRings.USER_ID); + signingKey = signingRing.getSubkey(sigKeyId); signatureIndex = i; } catch (ProviderHelper.NotFoundException e) { Log.d(Constants.TAG, "key not found!"); @@ -430,43 +404,24 @@ public class PgpDecryptVerify { } } - if (masterKeyId != null) { + if (signingKey != null) { // key found in our database! signature = sigList.get(signatureIndex); - PGPPublicKeyRing publicKeyRing = null; - try { - publicKeyRing = mProviderHelper - .getPGPPublicKeyRing(masterKeyId); - } catch (ProviderHelper.NotFoundException e) { - // can't happen - } - - // get the subkey which has been used to generate this signature - signatureKey = publicKeyRing.getPublicKey(signature.getKeyID()); - signatureResultBuilder.signatureAvailable(true); signatureResultBuilder.knownKey(true); - signatureResultBuilder.userId(primaryUserId); - signatureResultBuilder.keyId(masterKeyId); + signatureResultBuilder.keyId(signingRing.getMasterKeyId()); + try { + signatureResultBuilder.userId(signingRing.getPrimaryUserId()); + } catch(PgpGeneralException e) { + Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId()); + } + signatureResultBuilder.signatureKeyCertified(signingRing.getVerified() > 0); JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - signature.init(contentVerifierBuilderProvider, signatureKey); - - // get certification status of this key - boolean isSignatureKeyCertified; - try { - Object data = mProviderHelper.getGenericData( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), - KeyRings.VERIFIED, - ProviderHelper.FIELD_TYPE_INTEGER); - isSignatureKeyCertified = ((Long) data > 0); - } catch (ProviderHelper.NotFoundException e) { - isSignatureKeyCertified = false; - } - signatureResultBuilder.signatureKeyCertified(isSignatureKeyCertified); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); } else { // no key in our database -> return "unknown pub key" status including the first key id if (!sigList.isEmpty()) { @@ -541,7 +496,7 @@ public class PgpDecryptVerify { // Verify signature and check binding signatures boolean validSignature = signature.verify(messageSignature); - boolean validKeyBinding = verifyKeyBinding(messageSignature, signatureKey); + boolean validKeyBinding = signingRing.verifySubkeyBinding(signingKey); signatureResultBuilder.validSignature(validSignature); signatureResultBuilder.validKeyBinding(validKeyBinding); @@ -617,22 +572,19 @@ public class PgpDecryptVerify { throw new InvalidDataException(); } + WrappedPublicKeyRing signingRing = null; + WrappedPublicKey signingKey = null; + int signatureIndex = -1; + // go through all signatures // and find out for which signature we have a key in our database - Long masterKeyId = null; - String primaryUserId = null; - int signatureIndex = 0; for (int i = 0; i < sigList.size(); ++i) { try { - Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( - Long.toString(sigList.get(i).getKeyID())); - Map<String, Object> data = mProviderHelper.getGenericData(uri, - new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID }, - new int[] { ProviderHelper.FIELD_TYPE_INTEGER, - ProviderHelper.FIELD_TYPE_STRING } + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getWrappedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) ); - masterKeyId = (Long) data.get(KeyRings.MASTER_KEY_ID); - primaryUserId = (String) data.get(KeyRings.USER_ID); + signingKey = signingRing.getSubkey(sigKeyId); signatureIndex = i; } catch (ProviderHelper.NotFoundException e) { Log.d(Constants.TAG, "key not found!"); @@ -641,44 +593,25 @@ public class PgpDecryptVerify { } PGPSignature signature = null; - PGPPublicKey signatureKey = null; - if (masterKeyId != null) { + + if (signingKey != null) { // key found in our database! signature = sigList.get(signatureIndex); - PGPPublicKeyRing publicKeyRing = null; - try { - publicKeyRing = mProviderHelper - .getPGPPublicKeyRing(masterKeyId); - } catch (ProviderHelper.NotFoundException e) { - // can't happen - } - - // get the subkey which has been used to generate this signature - signatureKey = publicKeyRing.getPublicKey(signature.getKeyID()); - signatureResultBuilder.signatureAvailable(true); signatureResultBuilder.knownKey(true); - signatureResultBuilder.userId(primaryUserId); - signatureResultBuilder.keyId(masterKeyId); + signatureResultBuilder.keyId(signingRing.getMasterKeyId()); + try { + signatureResultBuilder.userId(signingRing.getPrimaryUserId()); + } catch(PgpGeneralException e) { + Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId()); + } + signatureResultBuilder.signatureKeyCertified(signingRing.getVerified() > 0); JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - signature.init(contentVerifierBuilderProvider, signatureKey); - - // get certification status of this key - boolean isSignatureKeyCertified; - try { - Object data = mProviderHelper.getGenericData( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), - KeyRings.VERIFIED, - ProviderHelper.FIELD_TYPE_INTEGER); - isSignatureKeyCertified = ((Long) data > 0); - } catch (ProviderHelper.NotFoundException e) { - isSignatureKeyCertified = false; - } - signatureResultBuilder.signatureKeyCertified(isSignatureKeyCertified); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); } else { // no key in our database -> return "unknown pub key" status including the first key id if (!sigList.isEmpty()) { @@ -710,7 +643,7 @@ public class PgpDecryptVerify { // Verify signature and check binding signatures boolean validSignature = signature.verify(); - boolean validKeyBinding = verifyKeyBinding(signature, signatureKey); + boolean validKeyBinding = signingRing.verifySubkeyBinding(signingKey); signatureResultBuilder.validSignature(validSignature); signatureResultBuilder.validKeyBinding(validKeyBinding); @@ -722,113 +655,6 @@ public class PgpDecryptVerify { return result; } - private boolean verifyKeyBinding(PGPSignature signature, PGPPublicKey signatureKey) { - long signatureKeyId = signature.getKeyID(); - boolean validKeyBinding = false; - - PGPPublicKey mKey = null; - try { - PGPPublicKeyRing signKeyRing = mProviderHelper.getPGPPublicKeyRingWithKeyId( - signatureKeyId); - mKey = signKeyRing.getPublicKey(); - } catch (ProviderHelper.NotFoundException e) { - Log.d(Constants.TAG, "key not found"); - } - - if (signature.getKeyID() != mKey.getKeyID()) { - validKeyBinding = verifyKeyBinding(mKey, signatureKey); - } else { //if the key used to make the signature was the master key, no need to check binding sigs - validKeyBinding = true; - } - return validKeyBinding; - } - - private boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { - boolean validSubkeyBinding = false; - boolean validTempSubkeyBinding = false; - boolean validPrimaryKeyBinding = false; - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - Iterator<PGPSignature> itr = signingPublicKey.getSignatures(); - - while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? - //gpg has an invalid subkey binding error on key import I think, but doesn't shout - //about keys without subkey signing. Can't get it to import a slightly broken one - //either, so we will err on bad subkey binding here. - PGPSignature sig = itr.next(); - if (sig.getKeyID() == masterPublicKey.getKeyID() && - sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { - //check and if ok, check primary key binding. - try { - sig.init(contentVerifierBuilderProvider, masterPublicKey); - validTempSubkeyBinding = sig.verifyCertification(masterPublicKey, signingPublicKey); - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - - if (validTempSubkeyBinding) { - validSubkeyBinding = true; - } - if (validTempSubkeyBinding) { - validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(), - masterPublicKey, signingPublicKey); - if (validPrimaryKeyBinding) { - break; - } - validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(), - masterPublicKey, signingPublicKey); - if (validPrimaryKeyBinding) { - break; - } - } - } - } - return (validSubkeyBinding & validPrimaryKeyBinding); - } - - private boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts, - PGPPublicKey masterPublicKey, - PGPPublicKey signingPublicKey) { - boolean validPrimaryKeyBinding = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureList eSigList; - - if (pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { - try { - eSigList = pkts.getEmbeddedSignatures(); - } catch (IOException e) { - return false; - } catch (PGPException e) { - return false; - } - for (int j = 0; j < eSigList.size(); ++j) { - PGPSignature emSig = eSigList.get(j); - if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { - try { - emSig.init(contentVerifierBuilderProvider, signingPublicKey); - validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey); - if (validPrimaryKeyBinding) { - break; - } - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - } - } - } - - return validPrimaryKeyBinding; - } - /** * Mostly taken from ClearSignedFileProcessor in Bouncy Castle * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index f64282f5f..1e58c188f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -24,20 +24,14 @@ import android.os.Environment; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.util.Log; @@ -62,7 +56,6 @@ public class PgpImportExport { private ProviderHelper mProviderHelper; public static final int RETURN_OK = 0; - public static final int RETURN_ERROR = -1; public static final int RETURN_BAD = -2; public static final int RETURN_UPDATED = 1; @@ -100,12 +93,12 @@ public class PgpImportExport { } } - public boolean uploadKeyRingToServer(HkpKeyserver server, PGPPublicKeyRing keyring) { + public boolean uploadKeyRingToServer(HkpKeyserver server, WrappedPublicKeyRing keyring) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; try { aos = new ArmoredOutputStream(bos); - aos.write(keyring.getEncoded()); + keyring.encode(aos); aos.close(); String armoredKey = bos.toString("UTF-8"); @@ -133,7 +126,7 @@ public class PgpImportExport { /** * Imports keys from given data. If keyIds is given only those are imported */ - public Bundle importKeyRings(List<ImportKeysListEntry> entries) + public Bundle importKeyRings(List<ParcelableKeyRing> entries) throws PgpGeneralException, PGPException, IOException { Bundle returnData = new Bundle(); @@ -144,37 +137,26 @@ public class PgpImportExport { int badKeys = 0; int position = 0; - try { - for (ImportKeysListEntry entry : entries) { - Object obj = PgpConversionHelper.BytesToPGPKeyRing(entry.getBytes()); - - if (obj instanceof PGPKeyRing) { - PGPKeyRing keyring = (PGPKeyRing) obj; - - int status = storeKeyRingInCache(keyring); - - if (status == RETURN_ERROR) { - throw new PgpGeneralException( - mContext.getString(R.string.error_saving_keys)); - } - - // update the counts to display to the user at the end - if (status == RETURN_UPDATED) { - ++oldKeys; - } else if (status == RETURN_OK) { - ++newKeys; - } else if (status == RETURN_BAD) { - ++badKeys; - } - } else { - Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); - } - - position++; - updateProgress(position / entries.size() * 100, 100); + for (ParcelableKeyRing entry : entries) { + try { + UncachedKeyRing key = entry.getUncachedKeyRing(); + + mProviderHelper.savePublicKeyRing(key); + /*switch(status) { + case RETURN_UPDATED: oldKeys++; break; + case RETURN_OK: newKeys++; break; + case RETURN_BAD: badKeys++; break; + }*/ + // TODO proper import feedback + newKeys += 1; + + } catch (PgpGeneralException e) { + Log.e(Constants.TAG, "Encountered bad key on import!", e); + ++badKeys; } - } catch (Exception e) { - Log.e(Constants.TAG, "Exception on parsing key file!", e); + // update progress + position++; + updateProgress(position / entries.size() * 100, 100); } returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys); @@ -211,9 +193,11 @@ public class PgpImportExport { updateProgress(progress * 100 / masterKeyIdsSize, 100); try { - PGPPublicKeyRing publicKeyRing = mProviderHelper.getPGPPublicKeyRing(pubKeyMasterId); + WrappedPublicKeyRing ring = mProviderHelper.getWrappedPublicKeyRing( + KeychainContract.KeyRings.buildGenericKeyRingUri(pubKeyMasterId) + ); - publicKeyRing.encode(arOutStream); + ring.encode(arOutStream); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); // TODO: inform user? @@ -237,7 +221,8 @@ public class PgpImportExport { updateProgress(progress * 100 / masterKeyIdsSize, 100); try { - PGPSecretKeyRing secretKeyRing = mProviderHelper.getPGPSecretKeyRing(secretKeyMasterId); + WrappedSecretKeyRing secretKeyRing = + mProviderHelper.getWrappedSecretKeyRing(secretKeyMasterId); secretKeyRing.encode(arOutStream); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); @@ -259,53 +244,4 @@ public class PgpImportExport { return returnData; } - @SuppressWarnings("unchecked") - public int storeKeyRingInCache(PGPKeyRing keyring) { - int status = RETURN_ERROR; - try { - if (keyring instanceof PGPSecretKeyRing) { - PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring; - boolean save = true; - - for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>( - secretKeyRing.getSecretKeys())) { - if (!testSecretKey.isMasterKey()) { - if (testSecretKey.isPrivateKeyEmpty()) { - // this is bad, something is very wrong... - save = false; - status = RETURN_BAD; - } - } - } - - if (save) { - // TODO: preserve certifications - // (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?) - PGPPublicKeyRing newPubRing = null; - for (PGPPublicKey key : new IterableIterator<PGPPublicKey>( - secretKeyRing.getPublicKeys())) { - if (newPubRing == null) { - newPubRing = new PGPPublicKeyRing(key.getEncoded(), - new JcaKeyFingerprintCalculator()); - } - newPubRing = PGPPublicKeyRing.insertPublicKey(newPubRing, key); - } - if (newPubRing != null) { - mProviderHelper.saveKeyRing(newPubRing); - } - mProviderHelper.saveKeyRing(secretKeyRing); - status = RETURN_OK; - } - } else if (keyring instanceof PGPPublicKeyRing) { - PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; - mProviderHelper.saveKeyRing(publicKeyRing); - status = RETURN_OK; - } - } catch (IOException e) { - status = RETURN_ERROR; - } - - return status; - } - } 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 f90250f57..b25c38f1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -52,14 +52,12 @@ public class PgpKeyHelper { private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); + @Deprecated public static Date getCreationDate(PGPPublicKey key) { return key.getCreationTime(); } - public static Date getCreationDate(PGPSecretKey key) { - return key.getPublicKey().getCreationTime(); - } - + @Deprecated public static Date getExpiryDate(PGPPublicKey key) { Date creationDate = getCreationDate(key); if (key.getValidDays() == 0) { @@ -73,185 +71,6 @@ public class PgpKeyHelper { return calendar.getTime(); } - public static Date getExpiryDate(PGPSecretKey key) { - return getExpiryDate(key.getPublicKey()); - } - - public static boolean isExpired(PGPPublicKey key) { - Date creationDate = getCreationDate(key); - Date expiryDate = getExpiryDate(key); - Date now = new Date(); - if (now.compareTo(creationDate) >= 0 - && (expiryDate == null || now.compareTo(expiryDate) <= 0)) { - return false; - } - return true; - } - - @SuppressWarnings("unchecked") - public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) { - long cnt = 0; - if (keyRing == null) { - return null; - } - for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { - if (cnt == num) { - return key; - } - cnt++; - } - - return null; - } - - @SuppressWarnings("unchecked") - private static Vector<PGPPublicKey> getEncryptKeys(PGPPublicKeyRing keyRing) { - Vector<PGPPublicKey> encryptKeys = new Vector<PGPPublicKey>(); - - for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) { - if (isEncryptionKey(key)) { - encryptKeys.add(key); - } - } - - return encryptKeys; - } - - @SuppressWarnings("unchecked") - private static Vector<PGPSecretKey> getSigningKeys(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>(); - - for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { - if (isSigningKey(key)) { - signingKeys.add(key); - } - } - - return signingKeys; - } - - @SuppressWarnings("unchecked") - private static Vector<PGPSecretKey> getCertificationKeys(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>(); - - for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { - if (isCertificationKey(key)) { - signingKeys.add(key); - } - } - - return signingKeys; - } - - private static Vector<PGPPublicKey> getUsableEncryptKeys(PGPPublicKeyRing keyRing) { - Vector<PGPPublicKey> usableKeys = new Vector<PGPPublicKey>(); - Vector<PGPPublicKey> encryptKeys = getEncryptKeys(keyRing); - PGPPublicKey masterKey = null; - for (int i = 0; i < encryptKeys.size(); ++i) { - PGPPublicKey key = encryptKeys.get(i); - if (!isExpired(key) && !key.isRevoked()) { - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - private static Vector<PGPSecretKey> getUsableCertificationKeys(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>(); - Vector<PGPSecretKey> signingKeys = getCertificationKeys(keyRing); - PGPSecretKey masterKey = null; - for (int i = 0; i < signingKeys.size(); ++i) { - PGPSecretKey key = signingKeys.get(i); - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - private static Vector<PGPSecretKey> getUsableSigningKeys(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>(); - Vector<PGPSecretKey> signingKeys = getSigningKeys(keyRing); - PGPSecretKey masterKey = null; - for (int i = 0; i < signingKeys.size(); ++i) { - PGPSecretKey key = signingKeys.get(i); - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - - public static PGPPublicKey getFirstEncryptSubkey(PGPPublicKeyRing keyRing) { - Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - Log.e(Constants.TAG, "encryptKeys is null!"); - return null; - } - return encryptKeys.get(0); - } - - public static PGPSecretKey getFirstCertificationSubkey(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> signingKeys = getUsableCertificationKeys(keyRing); - if (signingKeys.size() == 0) { - return null; - } - return signingKeys.get(0); - } - - public static PGPSecretKey getFirstSigningSubkey(PGPSecretKeyRing keyRing) { - Vector<PGPSecretKey> signingKeys = getUsableSigningKeys(keyRing); - if (signingKeys.size() == 0) { - return null; - } - return signingKeys.get(0); - } - - public static int getKeyUsage(PGPSecretKey key) { - return getKeyUsage(key.getPublicKey()); - } - - @SuppressWarnings("unchecked") - private static int getKeyUsage(PGPPublicKey key) { - int usage = 0; - if (key.getVersion() >= 4) { - for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - if (hashed != null) { - usage |= hashed.getKeyFlags(); - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - if (unhashed != null) { - usage |= unhashed.getKeyFlags(); - } - } - } - return usage; - } - @SuppressWarnings("unchecked") public static boolean isEncryptionKey(PGPPublicKey key) { if (!key.isEncryptionKey()) { @@ -293,10 +112,6 @@ public class PgpKeyHelper { return false; } - public static boolean isEncryptionKey(PGPSecretKey key) { - return isEncryptionKey(key.getPublicKey()); - } - @SuppressWarnings("unchecked") public static boolean isSigningKey(PGPPublicKey key) { if (key.getVersion() <= 3) { @@ -328,10 +143,6 @@ public class PgpKeyHelper { return false; } - public static boolean isSigningKey(PGPSecretKey key) { - return isSigningKey(key.getPublicKey()); - } - @SuppressWarnings("unchecked") public static boolean isCertificationKey(PGPPublicKey key) { if (key.getVersion() <= 3) { @@ -358,48 +169,6 @@ public class PgpKeyHelper { return false; } - public static boolean isAuthenticationKey(PGPSecretKey key) { - return isAuthenticationKey(key.getPublicKey()); - } - - @SuppressWarnings("unchecked") - public static boolean isAuthenticationKey(PGPPublicKey key) { - if (key.getVersion() <= 3) { - return true; - } - - for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - - if (hashed != null && (hashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) { - return true; - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - - if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) { - return true; - } - } - - return false; - } - - public static boolean isCertificationKey(PGPSecretKey key) { - return isCertificationKey(key.getPublicKey()); - } - - public static String getAlgorithmInfo(Context context, PGPPublicKey key) { - return getAlgorithmInfo(context, key.getAlgorithm(), key.getBitStrength()); - } - - public static String getAlgorithmInfo(Context context, PGPSecretKey key) { - return getAlgorithmInfo(context, key.getPublicKey()); - } - /** * TODO: Only used in HkpKeyServer. Get rid of this one! */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 9dd9f660b..44fc4c8c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -48,8 +48,8 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; +import org.sufficientlysecure.keychain.service.OldSaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Primes; @@ -63,6 +63,7 @@ import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.SignatureException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Iterator; @@ -124,7 +125,7 @@ public class PgpKeyOperation { */ // TODO: key flags? - public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, + public byte[] createKey(int algorithmChoice, int keySize, String passphrase, boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, PgpGeneralMsgIdException, InvalidAlgorithmParameterException { @@ -188,43 +189,23 @@ public class PgpKeyOperation { PGPEncryptedData.CAST5, sha1Calc) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), - sha1Calc, isMasterKey, keyEncryptor); - } - - public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassphrase, - String newPassphrase) - throws IOException, PGPException, NoSuchProviderException { - - updateProgress(R.string.progress_building_key, 0, 100); - if (oldPassphrase == null) { - oldPassphrase = ""; - } - if (newPassphrase == null) { - newPassphrase = ""; + try { + return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), + sha1Calc, isMasterKey, keyEncryptor).getEncoded(); + } catch(IOException e) { + throw new PgpGeneralMsgIdException(R.string.error_encoding); } - - PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword( - keyRing, - new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()), - new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey() - .getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray())); - - return newKeyRing; - } - public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildNewSecretKey( - SaveKeyringParcel saveParcel) + public Pair<UncachedKeyRing,UncachedKeyRing> buildNewSecretKey( + OldSaveKeyringParcel saveParcel) throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { int usageId = saveParcel.keysUsages.get(0); boolean canSign; String mainUserId = saveParcel.userIds.get(0); - PGPSecretKey masterKey = saveParcel.keys.get(0); + PGPSecretKey masterKey = saveParcel.keys.get(0).getSecretKeyExternal(); // this removes all userIds and certifications previously attached to the masterPublicKey PGPPublicKey masterPublicKey = masterKey.getPublicKey(); @@ -299,7 +280,7 @@ public class PgpKeyOperation { for (int i = 1; i < saveParcel.keys.size(); ++i) { updateProgress(40 + 40 * (i - 1) / (saveParcel.keys.size() - 1), 100); - PGPSecretKey subKey = saveParcel.keys.get(i); + PGPSecretKey subKey = saveParcel.keys.get(i).getSecretKeyExternal(); PGPPublicKey subPublicKey = subKey.getPublicKey(); PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() @@ -357,17 +338,19 @@ public class PgpKeyOperation { PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); - return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(secretKeyRing, publicKeyRing); + return new Pair(new UncachedKeyRing(secretKeyRing), new UncachedKeyRing(publicKeyRing)); } - public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing mKR, - PGPPublicKeyRing pKR, - SaveKeyringParcel saveParcel) + public Pair<UncachedKeyRing, UncachedKeyRing> buildSecretKey(WrappedSecretKeyRing wmKR, + WrappedPublicKeyRing wpKR, + OldSaveKeyringParcel saveParcel) throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { + PGPSecretKeyRing mKR = wmKR.getRing(); + PGPPublicKeyRing pKR = wpKR.getRing(); + updateProgress(R.string.progress_building_key, 0, 100); - PGPSecretKey masterKey = saveParcel.keys.get(0); if (saveParcel.oldPassphrase == null) { saveParcel.oldPassphrase = ""; @@ -399,12 +382,12 @@ public class PgpKeyOperation { */ if (saveParcel.deletedKeys != null) { - for (PGPSecretKey dKey : saveParcel.deletedKeys) { - mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey); + for (UncachedSecretKey dKey : saveParcel.deletedKeys) { + mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey.getSecretKeyExternal()); } } - masterKey = mKR.getSecretKey(); + PGPSecretKey masterKey = mKR.getSecretKey(); PGPPublicKey masterPublicKey = masterKey.getPublicKey(); int usageId = saveParcel.keysUsages.get(0); @@ -564,7 +547,7 @@ public class PgpKeyOperation { for (int i = 1; i < saveParcel.keys.size(); ++i) { updateProgress(40 + 50 * i / saveParcel.keys.size(), 100); if (saveParcel.moddedKeys[i]) { - PGPSecretKey subKey = saveParcel.keys.get(i); + PGPSecretKey subKey = saveParcel.keys.get(i).getSecretKeyExternal(); PGPPublicKey subPublicKey = subKey.getPublicKey(); PBESecretKeyDecryptor keyDecryptor2; @@ -667,7 +650,7 @@ public class PgpKeyOperation { for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) { for(PGPSignature sig : new IterableIterator<PGPSignature>( - secretKeyRing.getPublicKey().getSignaturesForID(uid))) { + secretKeyRing.getPublicKey().getSignaturesForId(uid))) { Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); } @@ -678,7 +661,7 @@ public class PgpKeyOperation { for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) { for(PGPSignature sig : new IterableIterator<PGPSignature>( - publicKeyRing.getPublicKey().getSignaturesForID(uid))) { + publicKeyRing.getPublicKey().getSignaturesForId(uid))) { Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); } @@ -686,10 +669,287 @@ public class PgpKeyOperation { */ - return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(mKR, pKR); + return new Pair<UncachedKeyRing,UncachedKeyRing>(new UncachedKeyRing(pKR), + new UncachedKeyRing(mKR)); } + public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing sKR, + PGPPublicKeyRing pKR, + SaveKeyringParcel saveParcel, + String passphrase) + throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { + + updateProgress(R.string.progress_building_key, 0, 100); + + // sort these, so we can use binarySearch later on + Arrays.sort(saveParcel.revokeSubKeys); + Arrays.sort(saveParcel.revokeUserIds); + + /* + * What's gonna happen here: + * + * 1. Unlock private key + * + * 2. Create new secret key ring + * + * 3. Copy subkeys + * - Generate revocation if requested + * - Copy old cert, or generate new if change requested + * + * 4. Generate and add new subkeys + * + * 5. Copy user ids + * - Generate revocation if requested + * - Copy old cert, or generate new if primary user id status changed + * + * 6. Add new user ids + * + * 7. Generate PublicKeyRing from SecretKeyRing + * + * 8. Return pair (PublicKeyRing,SecretKeyRing) + * + */ + + // 1. Unlock private key + updateProgress(R.string.progress_building_key, 0, 100); + + PGPPublicKey masterPublicKey = sKR.getPublicKey(); + PGPPrivateKey masterPrivateKey; { + PGPSecretKey masterKey = sKR.getSecretKey(); + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); + } + + // 2. Create new secret key ring + updateProgress(R.string.progress_certifying_master_key, 20, 100); + + // Note we do NOT use PGPKeyRingGeneraor, it's just one level too high and does stuff + // we want to do manually. Instead, we simply use a list of secret keys. + ArrayList<PGPSecretKey> secretKeys = new ArrayList<PGPSecretKey>(); + ArrayList<PGPPublicKey> publicKeys = new ArrayList<PGPPublicKey>(); + + // 3. Copy subkeys + // - Generate revocation if requested + // - Copy old cert, or generate new if change requested + for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { + PGPPublicKey pKey = sKey.getPublicKey(); + if (Arrays.binarySearch(saveParcel.revokeSubKeys, sKey.getKeyID()) >= 0) { + // add revocation signature to key, if there is none yet + if (!pKey.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION).hasNext()) { + // generate revocation signature + } + } + if (saveParcel.changeSubKeys.containsKey(sKey.getKeyID())) { + // change subkey flags? + SaveKeyringParcel.SubkeyChange change = saveParcel.changeSubKeys.get(sKey.getKeyID()); + // remove old subkey binding signature(s?) + for (PGPSignature sig : new IterableIterator<PGPSignature>( + pKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING))) { + pKey = PGPPublicKey.removeCertification(pKey, sig); + } + + // generate and add new signature + PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, + sKey, pKey, change.mFlags, change.mExpiry, passphrase); + pKey = PGPPublicKey.addCertification(pKey, sig); + } + secretKeys.add(PGPSecretKey.replacePublicKey(sKey, pKey)); + publicKeys.add(pKey); + } + + // 4. Generate and add new subkeys + // TODO + + // 5. Copy user ids + for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) { + // - Copy old cert, or generate new if primary user id status changed + boolean certified = false, revoked = false; + for (PGPSignature sig : new IterableIterator<PGPSignature>( + masterPublicKey.getSignaturesForID(userId))) { + // We know there are only revocation and certification types in here. + switch(sig.getSignatureType()) { + case PGPSignature.CERTIFICATION_REVOCATION: + revoked = true; + continue; + + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + // Already got one? Remove this one, then. + if (certified) { + masterPublicKey = PGPPublicKey.removeCertification( + masterPublicKey, userId, sig); + continue; + } + boolean primary = userId.equals(saveParcel.changePrimaryUserId); + // Generate a new one under certain circumstances + if (saveParcel.changePrimaryUserId != null && + sig.getHashedSubPackets().isPrimaryUserID() != primary) { + PGPSignature cert = generateUserIdSignature( + masterPrivateKey, masterPublicKey, userId, primary); + PGPPublicKey.addCertification(masterPublicKey, userId, cert); + } + certified = true; + } + } + // - Generate revocation if requested + if (!revoked && Arrays.binarySearch(saveParcel.revokeUserIds, userId) >= 0) { + PGPSignature cert = generateRevocationSignature(masterPrivateKey, + masterPublicKey, userId); + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); + } + } + + // 6. Add new user ids + for(String userId : saveParcel.addUserIds) { + PGPSignature cert = generateUserIdSignature(masterPrivateKey, + masterPublicKey, userId, userId.equals(saveParcel.changePrimaryUserId)); + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); + } + + // 7. Generate PublicKeyRing from SecretKeyRing + updateProgress(R.string.progress_building_master_key, 30, 100); + PGPSecretKeyRing ring = new PGPSecretKeyRing(secretKeys); + + // Copy all non-self uid certificates + for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) { + // - Copy old cert, or generate new if primary user id status changed + boolean certified = false, revoked = false; + for (PGPSignature sig : new IterableIterator<PGPSignature>( + masterPublicKey.getSignaturesForID(userId))) { + } + } + + for (PGPPublicKey newKey : publicKeys) { + PGPPublicKey oldKey = pKR.getPublicKey(newKey.getKeyID()); + for (PGPSignature sig : new IterableIterator<PGPSignature>( + oldKey.getSignatures())) { + } + } + + // If requested, set new passphrase + if (saveParcel.newPassphrase != null) { + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build() + .get(HashAlgorithmTags.SHA1); + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + // Build key encryptor based on new passphrase + PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + saveParcel.newPassphrase.toCharArray()); + + sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew); + } + + // 8. Return pair (PublicKeyRing,SecretKeyRing) + + return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(sKR, pKR); + + } + + private static PGPSignature generateUserIdSignature( + PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary) + throws IOException, PGPException, SignatureException { + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + pKey.getAlgorithm(), PGPUtil.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + subHashedPacketsGen.setSignatureCreationTime(false, new Date()); + subHashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); + subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); + subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + subHashedPacketsGen.setPrimaryUserID(false, primary); + sGen.setHashedSubpackets(subHashedPacketsGen.generate()); + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + return sGen.generateCertification(userId, pKey); + } + + private static PGPSignature generateRevocationSignature( + PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) + throws IOException, PGPException, SignatureException { + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + pKey.getAlgorithm(), PGPUtil.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + subHashedPacketsGen.setSignatureCreationTime(false, new Date()); + sGen.setHashedSubpackets(subHashedPacketsGen.generate()); + sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); + return sGen.generateCertification(userId, pKey); + } + + private static PGPSignature generateSubkeyBindingSignature( + PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, + PGPSecretKey sKey, PGPPublicKey pKey, + int flags, Long expiry, String passphrase) + throws PgpGeneralMsgIdException, IOException, PGPException, SignatureException { + + // date for signing + Date todayDate = new Date(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + // If this key can sign, we need a primary key binding signature + if ((flags & KeyFlags.SIGN_DATA) != 0) { + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + passphrase.toCharArray()); + PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); + + // cross-certify signing keys + PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + subHashedPacketsGen.setSignatureCreationTime(false, todayDate); + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + pKey.getAlgorithm(), PGPUtil.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); + sGen.setHashedSubpackets(subHashedPacketsGen.generate()); + PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey); + unhashedPacketsGen.setEmbeddedSignature(false, certification); + } + + PGPSignatureSubpacketGenerator hashedPacketsGen; + { + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setSignatureCreationTime(false, todayDate); + hashedPacketsGen.setKeyFlags(false, flags); + } + + if (expiry != null) { + Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + creationDate.setTime(pKey.getCreationTime()); + // note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c + // here we purposefully ignore partial days in each date - long type has + // no fractional part! + long numDays = (expiry / 86400000) - + (creationDate.getTimeInMillis() / 86400000); + if (numDays <= 0) { + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); + } + hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis()); + } else { + hashedPacketsGen.setKeyExpirationTime(false, 0); + } + + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + pKey.getAlgorithm(), PGPUtil.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + sGen.setUnhashedSubpackets(unhashedPacketsGen.generate()); + + return sGen.generateCertification(masterPublicKey, pKey); + + } + + /** * Certify the given pubkeyid with the given masterkeyid. * 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 665dc82cc..4cb92c368 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -25,25 +25,14 @@ import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; import org.spongycastle.openpgp.PGPLiteralDataGenerator; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; import org.spongycastle.openpgp.PGPV3SignatureGenerator; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -277,20 +266,17 @@ public class PgpSignEncrypt { } /* Get keys for signature generation for later usage */ - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - String signingUserId = null; + WrappedSecretKey signingKey = null; if (enableSignature) { + WrappedSecretKeyRing signingKeyRing; try { - signingKeyRing = mProviderHelper.getPGPSecretKeyRing(mSignatureMasterKeyId); - signingUserId = (String) mProviderHelper.getUnifiedData(mSignatureMasterKeyId, - KeychainContract.KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING); + signingKeyRing = mProviderHelper.getWrappedSecretKeyRing(mSignatureMasterKeyId); } catch (ProviderHelper.NotFoundException e) { throw new NoSigningKeyException(); } - signingKey = PgpKeyHelper.getFirstSigningSubkey(signingKeyRing); - if (signingKey == null) { + try { + signingKey = signingKeyRing.getSigningSubKey(); + } catch(PgpGeneralException e) { throw new NoSigningKeyException(); } @@ -300,10 +286,9 @@ public class PgpSignEncrypt { updateProgress(R.string.progress_extracting_signature_key, 0, 100); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mSignaturePassphrase.toCharArray()); - signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { + try { + signingKey.unlock(mSignaturePassphrase); + } catch (PgpGeneralException e) { throw new KeyExtractionException(); } } @@ -331,13 +316,12 @@ public class PgpSignEncrypt { // Asymmetric encryption for (long id : mEncryptionMasterKeyIds) { try { - PGPPublicKeyRing keyRing = mProviderHelper.getPGPPublicKeyRing(id); - PGPPublicKey key = PgpKeyHelper.getFirstEncryptSubkey(keyRing); - if (key != null) { - JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = - new JcePublicKeyKeyEncryptionMethodGenerator(key); - cPk.addMethod(pubKeyEncryptionGenerator); - } + WrappedPublicKeyRing keyRing = mProviderHelper.getWrappedPublicKeyRing( + KeyRings.buildUnifiedKeyRingUri(id)); + WrappedPublicKey key = keyRing.getEncryptionSubKey(); + cPk.addMethod(key.getPubKeyEncryptionGenerator()); + } catch (PgpGeneralException e) { + Log.e(Constants.TAG, "key not found!", e); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } @@ -351,29 +335,18 @@ public class PgpSignEncrypt { if (enableSignature) { updateProgress(R.string.progress_preparing_signature, 10, 100); - // content signer based on signing key algorithm and chosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( - signingKey.getPublicKey().getAlgorithm(), mSignatureHashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - int signatureType; - if (mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption) { - // for sign-only ascii text - signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; - } else { - signatureType = PGPSignature.BINARY_DOCUMENT; - } - - if (mSignatureForceV3) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(signatureType, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(signatureType, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, signingUserId); - signatureGenerator.setHashedSubpackets(spGen.generate()); + try { + boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; + if (mSignatureForceV3) { + signatureV3Generator = signingKey.getV3SignatureGenerator( + mSignatureHashAlgorithm,cleartext); + } else { + signatureGenerator = signingKey.getSignatureGenerator( + mSignatureHashAlgorithm, cleartext); + } + } catch (PgpGeneralException e) { + // TODO throw correct type of exception (which shouldn't be PGPException) + throw new KeyExtractionException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java new file mode 100644 index 000000000..02e5411ca --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -0,0 +1,171 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPUtil; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +/** Wrapper around PGPKeyRing class, to be constructed from bytes. + * + * This class and its relatives UncachedPublicKey and UncachedSecretKey are + * used to move around pgp key rings in non crypto related (UI, mostly) code. + * It should be used for simple inspection only until it saved in the database, + * all actual crypto operations should work with WrappedKeyRings exclusively. + * + * This class is also special in that it can hold either the PGPPublicKeyRing + * or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are + * treated equally for most purposes in UI code. It is up to the programmer to + * take care of the differences. + * + * @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing + * @see org.sufficientlysecure.keychain.pgp.UncachedPublicKey + * @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey + * + */ +public class UncachedKeyRing { + + final PGPKeyRing mRing; + final boolean mIsSecret; + + UncachedKeyRing(PGPKeyRing ring) { + mRing = ring; + mIsSecret = ring instanceof PGPSecretKeyRing; + } + + public long getMasterKeyId() { + return mRing.getPublicKey().getKeyID(); + } + + /* TODO don't use this */ + @Deprecated + public PGPKeyRing getRing() { + return mRing; + } + + public UncachedPublicKey getPublicKey() { + return new UncachedPublicKey(mRing.getPublicKey()); + } + + public Iterator<UncachedPublicKey> getPublicKeys() { + final Iterator<PGPPublicKey> it = mRing.getPublicKeys(); + return new Iterator<UncachedPublicKey>() { + public void remove() { + it.remove(); + } + public UncachedPublicKey next() { + return new UncachedPublicKey(it.next()); + } + public boolean hasNext() { + return it.hasNext(); + } + }; + } + + /** Returns the dynamic (though final) property if this is a secret keyring or not. */ + public boolean isSecret() { + return mIsSecret; + } + + public byte[] getEncoded() throws IOException { + return mRing.getEncoded(); + } + + public byte[] getFingerprint() { + return mRing.getPublicKey().getFingerprint(); + } + + public static UncachedKeyRing decodePublicFromData(byte[] data) + throws PgpGeneralException, IOException { + UncachedKeyRing ring = decodeFromData(data); + if(ring.isSecret()) { + throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!"); + } + return ring; + } + + public static UncachedKeyRing decodeFromData(byte[] data) + throws PgpGeneralException, IOException { + BufferedInputStream bufferedInput = + new BufferedInputStream(new ByteArrayInputStream(data)); + if (bufferedInput.available() > 0) { + InputStream in = PGPUtil.getDecoderStream(bufferedInput); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); + + // get first object in block + Object obj; + if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPKeyRing) { + return new UncachedKeyRing((PGPKeyRing) obj); + } else { + throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); + } + } else { + throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); + } + } + + public static List<UncachedKeyRing> fromStream(InputStream stream) + throws PgpGeneralException, IOException { + + PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream)); + + List<UncachedKeyRing> result = new Vector<UncachedKeyRing>(); + + // go through all objects in this block + Object obj; + while ((obj = objectFactory.nextObject()) != null) { + Log.d(Constants.TAG, "Found class: " + obj.getClass()); + + if (obj instanceof PGPKeyRing) { + result.add(new UncachedKeyRing((PGPKeyRing) obj)); + } else { + Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); + } + } + return result; + } + + public void encodeArmored(OutputStream out, String version) throws IOException { + ArmoredOutputStream aos = new ArmoredOutputStream(out); + aos.setHeader("Version", version); + aos.write(mRing.getEncoded()); + aos.close(); + } + + public ArrayList<Long> getAvailableSubkeys() { + if(!isSecret()) { + throw new RuntimeException("Tried to find available subkeys from non-secret keys. " + + "This is a programming error and should never happen!"); + } + + ArrayList<Long> result = new ArrayList<Long>(); + // then, mark exactly the keys we have available + for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>( + ((PGPSecretKeyRing) mRing).getSecretKeys())) { + S2K s2k = sub.getS2K(); + // Set to 1, except if the encryption type is GNU_DUMMY_S2K + if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) { + result.add(sub.getKeyID()); + } + } + return result; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java new file mode 100644 index 000000000..e3db03bf6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -0,0 +1,197 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.IterableIterator; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; + +public class UncachedPublicKey { + protected final PGPPublicKey mPublicKey; + private Integer mCacheUsage = null; + + public UncachedPublicKey(PGPPublicKey key) { + mPublicKey = key; + } + + public long getKeyId() { + return mPublicKey.getKeyID(); + } + + /** The revocation signature is NOT checked here, so this may be false! */ + public boolean maybeRevoked() { + return mPublicKey.isRevoked(); + } + + public Date getCreationTime() { + return mPublicKey.getCreationTime(); + } + + public Date getExpiryTime() { + Date creationDate = getCreationTime(); + if (mPublicKey.getValidDays() == 0) { + // no expiry + return null; + } + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.DATE, mPublicKey.getValidDays()); + + return calendar.getTime(); + } + + public boolean isExpired() { + Date creationDate = mPublicKey.getCreationTime(); + Date expiryDate = mPublicKey.getValidSeconds() > 0 + ? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null; + + Date now = new Date(); + return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); + } + + public boolean isMasterKey() { + return mPublicKey.isMasterKey(); + } + + public int getAlgorithm() { + return mPublicKey.getAlgorithm(); + } + + public int getBitStrength() { + return mPublicKey.getBitStrength(); + } + + public String getPrimaryUserId() { + for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) { + for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) { + if (sig.getHashedSubPackets() != null + && sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) { + try { + // make sure it's actually valid + sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey); + if (sig.verifyCertification(userId, mPublicKey)) { + return userId; + } + } catch (Exception e) { + // nothing bad happens, the key is just not considered the primary key id + } + } + + } + } + return null; + } + + public ArrayList<String> getUnorderedUserIds() { + ArrayList<String> userIds = new ArrayList<String>(); + for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) { + userIds.add(userId); + } + return userIds; + } + + public boolean isElGamalEncrypt() { + return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT; + } + + public boolean isDSA() { + return getAlgorithm() == PGPPublicKey.DSA; + } + + @SuppressWarnings("unchecked") + public int getKeyUsage() { + if(mCacheUsage == null) { + mCacheUsage = 0; + if (mPublicKey.getVersion() >= 4) { + for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignatures())) { + if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) { + continue; + } + + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + if (hashed != null) { + mCacheUsage |= hashed.getKeyFlags(); + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + if (unhashed != null) { + mCacheUsage |= unhashed.getKeyFlags(); + } + } + } + } + return mCacheUsage; + } + + public boolean canAuthenticate() { + return mPublicKey.getVersion() <= 3 || (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0; + } + + public boolean canCertify() { + return mPublicKey.getVersion() <= 3 || (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0; + } + + public boolean canEncrypt() { + if (!mPublicKey.isEncryptionKey()) { + return false; + } + + // special cases + if (mPublicKey.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { + return true; + } + + if (mPublicKey.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { + return true; + } + + return mPublicKey.getVersion() <= 3 || + (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0; + + } + + public boolean canSign() { + // special case + if (mPublicKey.getAlgorithm() == PGPPublicKey.RSA_SIGN) { + return true; + } + + return mPublicKey.getVersion() <= 3 || (getKeyUsage() & KeyFlags.SIGN_DATA) != 0; + } + + public byte[] getFingerprint() { + return mPublicKey.getFingerprint(); + } + + // TODO This method should have package visibility - no access outside the pgp package! + // (It's still used in ProviderHelper at this point) + public PGPPublicKey getPublicKey() { + return mPublicKey; + } + + public Iterator<WrappedSignature> getSignaturesForId(String userId) { + final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId); + return new Iterator<WrappedSignature>() { + public void remove() { + it.remove(); + } + public WrappedSignature next() { + return new WrappedSignature(it.next()); + } + public boolean hasNext() { + return it.hasNext(); + } + }; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java new file mode 100644 index 000000000..0e14a7fd3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java @@ -0,0 +1,33 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPSecretKey; + +import java.io.IOException; +import java.io.OutputStream; + +public class UncachedSecretKey extends UncachedPublicKey { + + public static final int CERTIFY_OTHER = KeyFlags.CERTIFY_OTHER; + public static final int SIGN_DATA = KeyFlags.SIGN_DATA; + public static final int ENCRYPT_COMMS = KeyFlags.ENCRYPT_COMMS; + public static final int ENCRYPT_STORAGE = KeyFlags.ENCRYPT_STORAGE; + public static final int AUTHENTICATION = KeyFlags.AUTHENTICATION; + + final PGPSecretKey mSecretKey; + + public UncachedSecretKey(PGPSecretKey secretKey) { + super(secretKey.getPublicKey()); + mSecretKey = secretKey; + } + + @Deprecated + public PGPSecretKey getSecretKeyExternal() { + return mSecretKey; + } + + public void encodeSecretKey(OutputStream os) throws IOException { + mSecretKey.encode(os); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java new file mode 100644 index 000000000..2b6049894 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java @@ -0,0 +1,97 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.IterableIterator; + +import java.io.IOException; +import java.io.OutputStream; + +/** A generic wrapped PGPKeyRing object. + * + * This class provides implementations for all basic getters which both + * PublicKeyRing and SecretKeyRing have in common. To make the wrapped keyring + * class typesafe in implementing subclasses, the field is stored in the + * implementing class, providing properly typed access through the getRing + * getter method. + * + */ +public abstract class WrappedKeyRing extends KeyRing { + + private final boolean mHasAnySecret; + private final int mVerified; + + WrappedKeyRing(boolean hasAnySecret, int verified) { + mHasAnySecret = hasAnySecret; + mVerified = verified; + } + + public long getMasterKeyId() { + return getRing().getPublicKey().getKeyID(); + } + + public boolean hasAnySecret() { + return mHasAnySecret; + } + + public int getVerified() { + return mVerified; + } + + public String getPrimaryUserId() throws PgpGeneralException { + return (String) getRing().getPublicKey().getUserIDs().next(); + }; + + public boolean isRevoked() throws PgpGeneralException { + // Is the master key revoked? + return getRing().getPublicKey().isRevoked(); + } + + public boolean canCertify() throws PgpGeneralException { + return getRing().getPublicKey().isEncryptionKey(); + } + + public long getEncryptId() throws PgpGeneralException { + for(PGPPublicKey key : new IterableIterator<PGPPublicKey>(getRing().getPublicKeys())) { + if(PgpKeyHelper.isEncryptionKey(key)) { + return key.getKeyID(); + } + } + throw new PgpGeneralException("No valid encryption key found!"); + } + + public boolean hasEncrypt() throws PgpGeneralException { + try { + getEncryptId(); + return true; + } catch(PgpGeneralException e) { + return false; + } + } + + public long getSignId() throws PgpGeneralException { + for(PGPPublicKey key : new IterableIterator<PGPPublicKey>(getRing().getPublicKeys())) { + if(PgpKeyHelper.isSigningKey(key)) { + return key.getKeyID(); + } + } + throw new PgpGeneralException("No valid signing key found!"); + } + + public boolean hasSign() throws PgpGeneralException { + try { + getSignId(); + return true; + } catch (PgpGeneralException e) { + return false; + } + } + + public void encode(OutputStream stream) throws IOException { + getRing().encode(stream); + } + + abstract PGPKeyRing getRing(); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKey.java new file mode 100644 index 000000000..69a4fbdee --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKey.java @@ -0,0 +1,39 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.util.IterableIterator; + +/** Wrapper for a PGPPublicKey. + * + * The methods implemented in this class are a thin layer over + * UncachedPublicKey. The difference between the two classes is that objects of + * this class can only be obtained from a WrappedKeyRing, and that it stores a + * back reference to its parent as well. A method which works with + * WrappedPublicKey is therefore guaranteed to work on a KeyRing which is + * stored in the database. + * + */ +public class WrappedPublicKey extends UncachedPublicKey { + + // this is the parent key ring + final KeyRing mRing; + + WrappedPublicKey(KeyRing ring, PGPPublicKey key) { + super(key); + mRing = ring; + } + + public IterableIterator<String> getUserIds() { + return new IterableIterator<String>(mPublicKey.getUserIDs()); + } + + public KeyRing getKeyRing() { + return mRing; + } + + JcePublicKeyKeyEncryptionMethodGenerator getPubKeyEncryptionGenerator() { + return new JcePublicKeyKeyEncryptionMethodGenerator(mPublicKey); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java new file mode 100644 index 000000000..99dc99436 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java @@ -0,0 +1,192 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Iterator; + +public class WrappedPublicKeyRing extends WrappedKeyRing { + + private PGPPublicKeyRing mRing; + private final byte[] mPubKey; + + public WrappedPublicKeyRing(byte[] blob, boolean hasAnySecret, int verified) { + super(hasAnySecret, verified); + mPubKey = blob; + } + + PGPPublicKeyRing getRing() { + if(mRing == null) { + PGPObjectFactory factory = new PGPObjectFactory(mPubKey); + PGPKeyRing keyRing = null; + try { + if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) { + Log.e(Constants.TAG, "No keys given!"); + } + } catch (IOException e) { + Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e); + } + + mRing = (PGPPublicKeyRing) keyRing; + } + return mRing; + } + + public void encode(ArmoredOutputStream stream) throws IOException { + getRing().encode(stream); + } + + public WrappedPublicKey getSubkey() { + return new WrappedPublicKey(this, getRing().getPublicKey()); + } + + public WrappedPublicKey getSubkey(long id) { + return new WrappedPublicKey(this, getRing().getPublicKey(id)); + } + + /** Getter that returns the subkey that should be used for signing. */ + WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException { + PGPPublicKey key = getRing().getPublicKey(getEncryptId()); + if(key != null) { + WrappedPublicKey cKey = new WrappedPublicKey(this, key); + if(!cKey.canEncrypt()) { + throw new PgpGeneralException("key error"); + } + return cKey; + } + // TODO handle with proper exception + throw new PgpGeneralException("no encryption key available"); + } + + public boolean verifySubkeyBinding(WrappedPublicKey cachedSubkey) { + boolean validSubkeyBinding = false; + boolean validTempSubkeyBinding = false; + boolean validPrimaryKeyBinding = false; + + PGPPublicKey masterKey = getRing().getPublicKey(); + PGPPublicKey subKey = cachedSubkey.getPublicKey(); + + // Is this the master key? Match automatically, then. + if(Arrays.equals(masterKey.getFingerprint(), subKey.getFingerprint())) { + return true; + } + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + Iterator<PGPSignature> itr = subKey.getSignatures(); + + while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? + //gpg has an invalid subkey binding error on key import I think, but doesn't shout + //about keys without subkey signing. Can't get it to import a slightly broken one + //either, so we will err on bad subkey binding here. + PGPSignature sig = itr.next(); + if (sig.getKeyID() == masterKey.getKeyID() && + sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { + //check and if ok, check primary key binding. + try { + sig.init(contentVerifierBuilderProvider, masterKey); + validTempSubkeyBinding = sig.verifyCertification(masterKey, subKey); + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + + if (validTempSubkeyBinding) { + validSubkeyBinding = true; + } + if (validTempSubkeyBinding) { + validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(), + masterKey, subKey); + if (validPrimaryKeyBinding) { + break; + } + validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(), + masterKey, subKey); + if (validPrimaryKeyBinding) { + break; + } + } + } + } + return validSubkeyBinding && validPrimaryKeyBinding; + + } + + static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts, + PGPPublicKey masterPublicKey, + PGPPublicKey signingPublicKey) { + boolean validPrimaryKeyBinding = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureList eSigList; + + if (pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { + try { + eSigList = pkts.getEmbeddedSignatures(); + } catch (IOException e) { + return false; + } catch (PGPException e) { + return false; + } + for (int j = 0; j < eSigList.size(); ++j) { + PGPSignature emSig = eSigList.get(j); + if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { + try { + emSig.init(contentVerifierBuilderProvider, signingPublicKey); + validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey); + if (validPrimaryKeyBinding) { + break; + } + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + } + } + } + + return validPrimaryKeyBinding; + } + + public IterableIterator<WrappedPublicKey> iterator() { + final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); + return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() { + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public WrappedPublicKey next() { + return new WrappedPublicKey(WrappedPublicKeyRing.this, it.next()); + } + + @Override + public void remove() { + it.remove(); + } + }); + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java new file mode 100644 index 000000000..ef8044a9b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java @@ -0,0 +1,200 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +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.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +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 java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.List; + +/** Wrapper for a PGPSecretKey. + * + * This object can only be obtained from a WrappedSecretKeyRing, and stores a + * back reference to its parent. + * + * This class represents known secret keys which are stored in the database. + * All "crypto operations using a known secret key" should be implemented in + * this class, to ensure on type level that these operations are performed on + * properly imported secret keys only. + * + */ +public class WrappedSecretKey extends WrappedPublicKey { + + private final PGPSecretKey mSecretKey; + private PGPPrivateKey mPrivateKey = null; + + WrappedSecretKey(WrappedSecretKeyRing ring, PGPSecretKey key) { + super(ring, key.getPublicKey()); + mSecretKey = key; + } + + public WrappedSecretKeyRing getRing() { + return (WrappedSecretKeyRing) mRing; + } + + /** Returns the wrapped PGPSecretKeyRing. + * This function is for compatibility only, should not be used anymore and will be removed + */ + @Deprecated + public PGPSecretKey getKeyExternal() { + return mSecretKey; + } + + public boolean unlock(String passphrase) throws PgpGeneralException { + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + return false; + } + if(mPrivateKey == null) { + throw new PgpGeneralException("error extracting key"); + } + return true; + } + + public PGPSignatureGenerator getSignatureGenerator(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 { + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(signatureType, mPrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, mRing.getPrimaryUserId()); + 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) { + throw new PgpGeneralException("Error initializing signature!", e); + } + } + + public PublicKeyDataDecryptorFactory getDecryptorFactory() { + if(mPrivateKey == null) { + throw new PrivateKeyNotUnlockedException(); + } + return new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey); + } + + /** + * Certify the given pubkeyid with the given masterkeyid. + * + * @param publicKeyRing Keyring to add certification to. + * @param userIds User IDs to certify, must not be null or empty + * @return A keyring with added certifications + */ + public UncachedKeyRing certifyUserIds(WrappedPublicKeyRing publicKeyRing, List<String> userIds) + throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, + PGPException, SignatureException { + + if(mPrivateKey == null) { + throw new PrivateKeyNotUnlockedException(); + } + + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator; + { + // TODO: SHA256 fixed? + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey); + } + + { // supply signatureGenerator with a SubpacketVector + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketVector packetVector = spGen.generate(); + signatureGenerator.setHashedSubpackets(packetVector); + } + + // get the master subkey (which we certify for) + PGPPublicKey publicKey = publicKeyRing.getSubkey().getPublicKey(); + + // fetch public key ring, add the certification and return it + for (String userId : new IterableIterator<String>(userIds.iterator())) { + PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); + } + + PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey); + + return new UncachedKeyRing(ring); + } + + static class PrivateKeyNotUnlockedException extends RuntimeException { + // this exception is a programming error which happens when an operation which requires + // the private key is called without a previous call to unlock() + } + + public UncachedSecretKey getUncached() { + return new UncachedSecretKey(mSecretKey); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java new file mode 100644 index 000000000..91d4286f4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java @@ -0,0 +1,141 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.security.NoSuchProviderException; +import java.util.Iterator; + +public class WrappedSecretKeyRing extends WrappedKeyRing { + + private PGPSecretKeyRing mRing; + + public WrappedSecretKeyRing(byte[] blob, boolean isRevoked, int verified) + { + super(isRevoked, verified); + PGPObjectFactory factory = new PGPObjectFactory(blob); + PGPKeyRing keyRing = null; + try { + if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) { + Log.e(Constants.TAG, "No keys given!"); + } + } catch (IOException e) { + Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e); + } + + mRing = (PGPSecretKeyRing) keyRing; + } + + PGPSecretKeyRing getRing() { + return mRing; + } + + public WrappedSecretKey getSubKey() { + return new WrappedSecretKey(this, mRing.getSecretKey()); + } + + public WrappedSecretKey getSubKey(long id) { + return new WrappedSecretKey(this, mRing.getSecretKey(id)); + } + + /** Getter that returns the subkey that should be used for signing. */ + WrappedSecretKey getSigningSubKey() throws PgpGeneralException { + PGPSecretKey key = mRing.getSecretKey(getSignId()); + if(key != null) { + WrappedSecretKey cKey = new WrappedSecretKey(this, key); + if(!cKey.canSign()) { + throw new PgpGeneralException("key error"); + } + return cKey; + } + // TODO handle with proper exception + throw new PgpGeneralException("no signing key available"); + } + + public boolean hasPassphrase() { + PGPSecretKey secretKey = null; + boolean foundValidKey = false; + for (Iterator keys = mRing.getSecretKeys(); keys.hasNext(); ) { + secretKey = (PGPSecretKey) keys.next(); + if (!secretKey.isPrivateKeyEmpty()) { + foundValidKey = true; + break; + } + } + if(!foundValidKey) { + return false; + } + + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider("SC").build("".toCharArray()); + PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor); + return testKey == null; + } catch(PGPException e) { + // this means the crc check failed -> passphrase required + return true; + } + } + + public UncachedKeyRing changeSecretKeyPassphrase(String oldPassphrase, + String newPassphrase) + throws IOException, PGPException, NoSuchProviderException { + + if (oldPassphrase == null) { + oldPassphrase = ""; + } + if (newPassphrase == null) { + newPassphrase = ""; + } + + PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword( + mRing, + new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()), + new JcePBESecretKeyEncryptorBuilder(mRing.getSecretKey() + .getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray())); + + return new UncachedKeyRing(newKeyRing); + + } + + public IterableIterator<WrappedSecretKey> iterator() { + final Iterator<PGPSecretKey> it = mRing.getSecretKeys(); + return new IterableIterator<WrappedSecretKey>(new Iterator<WrappedSecretKey>() { + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public WrappedSecretKey next() { + return new WrappedSecretKey(WrappedSecretKeyRing.this, it.next()); + } + + @Override + public void remove() { + it.remove(); + } + }); + } + + public UncachedKeyRing getUncached() { + return new UncachedKeyRing(mRing); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java new file mode 100644 index 000000000..1b7a5e8ba --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -0,0 +1,161 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.RevocationReason; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.Date; + +/** OpenKeychain wrapper around PGPSignature objects. + * + * This is a mostly simple wrapper around a single bouncycastle PGPSignature + * object. It exposes high level getters for all relevant information, methods + * for verification of various signatures (uid binding, subkey binding, generic + * bytes), and a static method for construction from bytes. + * + */ +public class WrappedSignature { + + public static final int DEFAULT_CERTIFICATION = PGPSignature.DEFAULT_CERTIFICATION; + public static final int NO_CERTIFICATION = PGPSignature.NO_CERTIFICATION; + public static final int CASUAL_CERTIFICATION = PGPSignature.CASUAL_CERTIFICATION; + public static final int POSITIVE_CERTIFICATION = PGPSignature.POSITIVE_CERTIFICATION; + public static final int CERTIFICATION_REVOCATION = PGPSignature.CERTIFICATION_REVOCATION; + + final PGPSignature mSig; + + protected WrappedSignature(PGPSignature sig) { + mSig = sig; + } + + public long getKeyId() { + return mSig.getKeyID(); + } + + public int getSignatureType() { + return mSig.getSignatureType(); + } + + public int getKeyAlgorithm() { + return mSig.getKeyAlgorithm(); + } + + public Date getCreationTime() { + return mSig.getCreationTime(); + } + + public byte[] getEncoded() throws IOException { + return mSig.getEncoded(); + } + + public boolean isRevocation() { + return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON); + } + + public boolean isPrimaryUserId() { + return mSig.getHashedSubPackets().isPrimaryUserID(); + } + + public String getRevocationReason() throws PgpGeneralException { + if(!isRevocation()) { + throw new PgpGeneralException("Not a revocation signature."); + } + SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket( + SignatureSubpacketTags.REVOCATION_REASON); + // For some reason, this is missing in SignatureSubpacketInputStream:146 + if (!(p instanceof RevocationReason)) { + p = new RevocationReason(false, p.getData()); + } + return ((RevocationReason) p).getRevocationDescription(); + } + + public void init(WrappedPublicKey key) throws PgpGeneralException { + init(key.getPublicKey()); + } + + public void init(UncachedPublicKey key) throws PgpGeneralException { + init(key.getPublicKey()); + } + + protected void init(PGPPublicKey key) throws PgpGeneralException { + try { + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + mSig.init(contentVerifierBuilderProvider, key); + } catch(PGPException e) { + throw new PgpGeneralException(e); + } + } + + public void update(byte[] data, int offset, int length) throws PgpGeneralException { + try { + mSig.update(data, offset, length); + } catch(SignatureException e) { + throw new PgpGeneralException(e); + } + } + + public void update(byte data) throws PgpGeneralException { + try { + mSig.update(data); + } catch(SignatureException e) { + throw new PgpGeneralException(e); + } + } + + public boolean verify() throws PgpGeneralException { + try { + return mSig.verify(); + } catch(SignatureException e) { + throw new PgpGeneralException(e); + } catch(PGPException e) { + throw new PgpGeneralException(e); + } + } + + protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { + try { + return mSig.verifyCertification(uid, key); + } catch (SignatureException e) { + throw new PgpGeneralException("Error!", e); + } catch (PGPException e) { + throw new PgpGeneralException("Error!", e); + } + } + + public boolean verifySignature(UncachedPublicKey key, String uid) throws PgpGeneralException { + return verifySignature(key.getPublicKey(), uid); + } + public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException { + return verifySignature(key.getPublicKey(), uid); + } + + public static WrappedSignature fromBytes(byte[] data) { + PGPObjectFactory factory = new PGPObjectFactory(data); + PGPSignatureList signatures = null; + try { + if ((signatures = (PGPSignatureList) factory.nextObject()) == null || signatures.isEmpty()) { + Log.e(Constants.TAG, "No signatures given!"); + return null; + } + } catch (IOException e) { + Log.e(Constants.TAG, "Error while converting to PGPSignature!", e); + return null; + } + + return new WrappedSignature(signatures.get(0)); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java index 37d21eea4..f37a61852 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java @@ -27,4 +27,7 @@ public class PgpGeneralException extends Exception { public PgpGeneralException(String message, Throwable cause) { super(message, cause); } + public PgpGeneralException(Throwable cause) { + super(cause); + } } |