From a53da491c09fc7db814d4c2358ffe5dc9fe888bc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 14 May 2014 15:37:55 +0200 Subject: new savekeyring operation (mostly stub) --- .../keychain/pgp/PgpConversionHelper.java | 24 -- .../keychain/pgp/PgpImportExport.java | 12 +- .../keychain/pgp/PgpKeyOperation.java | 338 ++++++++++++++++++++- .../keychain/pgp/UncachedKeyRing.java | 49 ++- .../keychain/pgp/UncachedPublicKey.java | 21 +- .../keychain/pgp/UncachedSecretKeyRing.java | 28 +- .../keychain/pgp/WrappedKeyRing.java | 7 + .../keychain/pgp/WrappedSecretKeyRing.java | 4 + .../keychain/pgp/WrappedSignature.java | 77 +++-- .../keychain/provider/KeychainDatabase.java | 34 +-- .../keychain/provider/ProviderHelper.java | 265 +++++----------- .../keychain/service/KeychainIntentService.java | 29 +- .../keychain/service/PassphraseCacheService.java | 48 +-- .../keychain/service/SaveKeyringParcel.java | 9 +- .../keychain/ui/ViewKeyShareFragment.java | 4 + .../ui/dialog/PassphraseDialogFragment.java | 2 +- 16 files changed, 595 insertions(+), 356 deletions(-) (limited to 'OpenKeychain') 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 63e2ff2f2..ea3c72fd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java @@ -21,8 +21,6 @@ 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; @@ -122,26 +120,4 @@ public class PgpConversionHelper { return new UncachedSecretKey(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); - } - } 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 e858012f5..268906037 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -257,7 +257,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); @@ -287,18 +288,13 @@ public class PgpImportExport { public int storeKeyRingInCache(UncachedKeyRing ring, UncachedKeyRing secretRing) { int status; try { - // TODO make sure these are correctly typed! - PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) ring.getRing(); - PGPSecretKeyRing secretKeyRing = null; - if(secretRing != null) { - secretKeyRing = (PGPSecretKeyRing) secretRing.getRing(); - } + UncachedSecretKeyRing secretKeyRing = null; // see what type we have. we can either have a secret + public keyring, or just public if (secretKeyRing != null) { mProviderHelper.saveKeyRing(ring, secretRing); status = RETURN_OK; } else { - mProviderHelper.saveKeyRing(publicKeyRing); + mProviderHelper.saveKeyRing(ring); status = RETURN_OK; } } catch (IOException e) { 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 f49e0af4b..3e296edb9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -35,6 +35,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; 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.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; @@ -49,6 +50,8 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; 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; import java.io.IOException; @@ -60,9 +63,11 @@ 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; +import java.util.List; import java.util.TimeZone; /** @@ -644,7 +649,7 @@ public class PgpKeyOperation { for(String uid : new IterableIterator(secretKeyRing.getPublicKey().getUserIDs())) { for(PGPSignature sig : new IterableIterator( - secretKeyRing.getPublicKey().getSignaturesForID(uid))) { + secretKeyRing.getPublicKey().getSignaturesForId(uid))) { Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); } @@ -655,7 +660,7 @@ public class PgpKeyOperation { for(String uid : new IterableIterator(publicKeyRing.getPublicKey().getUserIDs())) { for(PGPSignature sig : new IterableIterator( - publicKeyRing.getPublicKey().getSignaturesForID(uid))) { + publicKeyRing.getPublicKey().getSignaturesForId(uid))) { Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); } @@ -668,6 +673,335 @@ public class PgpKeyOperation { } + public Pair 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 secretKeys = new ArrayList(); + ArrayList publicKeys = new ArrayList(); + + // 3. Copy subkeys + // - Generate revocation if requested + // - Copy old cert, or generate new if change requested + for (PGPSecretKey sKey : new IterableIterator(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( + 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(masterPublicKey.getUserIDs())) { + // - Copy old cert, or generate new if primary user id status changed + boolean certified = false, revoked = false; + for (PGPSignature sig : new IterableIterator( + 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(masterPublicKey.getUserIDs())) { + // - Copy old cert, or generate new if primary user id status changed + boolean certified = false, revoked = false; + for (PGPSignature sig : new IterableIterator( + masterPublicKey.getSignaturesForID(userId))) { + } + } + + for (PGPPublicKey newKey : publicKeys) { + PGPPublicKey oldKey = pKR.getPublicKey(newKey.getKeyID()); + for (PGPSignature sig : new IterableIterator( + 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(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. + * + * @param certificationKey Certifying key + * @param publicKey public key to certify + * @param userIds User IDs to certify, must not be null or empty + * @param passphrase Passphrase of the secret key + * @return A keyring with added certifications + */ + public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey, + List userIds, String passphrase) + throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, + PGPException, SignatureException { + + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator; + { + + if (certificationKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_no_signature_key); + } + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key); + } + + // TODO: SHA256 fixed? + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey); + } + + { // supply signatureGenerator with a SubpacketVector + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketVector packetVector = spGen.generate(); + signatureGenerator.setHashedSubpackets(packetVector); + } + + // fetch public key ring, add the certification and return it + for (String userId : new IterableIterator(userIds.iterator())) { + PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); + } + + return publicKey; + } + /** * Simple static subclass that stores two values. *

diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 06f890fb4..8bacb32c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -1,8 +1,9 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; @@ -13,6 +14,8 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; import java.util.List; import java.util.Vector; @@ -26,6 +29,10 @@ public class UncachedKeyRing { mIsSecret = ring instanceof PGPSecretKeyRing; } + public long getMasterKeyId() { + return mRing.getPublicKey().getKeyID(); + } + /* TODO don't use this */ @Deprecated public PGPKeyRing getRing() { @@ -36,6 +43,21 @@ public class UncachedKeyRing { return new UncachedPublicKey(mRing.getPublicKey()); } + public Iterator getPublicKeys() { + final Iterator it = mRing.getPublicKeys(); + return new Iterator() { + public void remove() { + it.remove(); + } + public UncachedPublicKey next() { + return new UncachedPublicKey(it.next()); + } + public boolean hasNext() { + return it.hasNext(); + } + }; + } + public boolean isSecret() { return mIsSecret; } @@ -50,6 +72,15 @@ public class UncachedKeyRing { public static UncachedKeyRing decodePubkeyFromData(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) { @@ -58,13 +89,14 @@ public class UncachedKeyRing { // get first object in block Object obj; - if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPPublicKeyRing) { - return new UncachedKeyRing((PGPPublicKeyRing) obj); + if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPKeyRing) { + // the constructor will take care of the public/secret part + return new UncachedKeyRing((PGPKeyRing) obj); } else { - throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!"); + throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); } } else { - throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!"); + throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); } } @@ -89,4 +121,11 @@ public class UncachedKeyRing { 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(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 5dbe4b316..e3db03bf6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -13,6 +13,7 @@ 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 { @@ -173,8 +174,24 @@ public class UncachedPublicKey { return mPublicKey.getFingerprint(); } - // Note that this method has package visibility - no access outside the pgp package! - PGPPublicKey getPublicKey() { + // 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 getSignaturesForId(String userId) { + final Iterator it = mPublicKey.getSignaturesForID(userId); + return new Iterator() { + 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/UncachedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKeyRing.java index bda9ebbcf..ca784fbde 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKeyRing.java @@ -1,19 +1,33 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.util.IterableIterator; -public class UncachedSecretKeyRing { +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; - final PGPSecretKeyRing mSecretRing; +public class UncachedSecretKeyRing extends UncachedKeyRing { UncachedSecretKeyRing(PGPSecretKeyRing secretRing) { - mSecretRing = secretRing; + super(secretRing); } - // Breaking the pattern here, for key import! - // TODO reduce this from public to default visibility! - public PGPSecretKeyRing getSecretKeyRing() { - return mSecretRing; + public ArrayList getAvailableSubkeys() { + ArrayList result = new ArrayList(); + // then, mark exactly the keys we have available + for (PGPSecretKey sub : new IterableIterator( + ((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/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java index 94eb91420..6e7b2a49e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java @@ -5,6 +5,9 @@ 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; + public abstract class WrappedKeyRing extends KeyRing { private final boolean mHasAnySecret; @@ -76,6 +79,10 @@ public abstract class WrappedKeyRing extends KeyRing { } } + public void encode(OutputStream stream) throws IOException { + getRing().encode(stream); + } + abstract PGPKeyRing getRing(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java index c94b7dfba..656430969 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java @@ -120,4 +120,8 @@ public class WrappedSecretKeyRing extends WrappedKeyRing { }); } + public UncachedSecretKeyRing getUncached() { + return new UncachedSecretKeyRing(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 index cdadbca7f..9f26439d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -5,6 +5,7 @@ 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; @@ -14,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.security.SignatureException; +import java.util.Date; public class WrappedSignature { @@ -33,16 +35,57 @@ public class WrappedSignature { 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.getPublicKey()); + mSig.init(contentVerifierBuilderProvider, key); } catch(PGPException e) { throw new PgpGeneralException(e); } @@ -74,30 +117,9 @@ public class WrappedSignature { } } - public boolean isRevocation() { - return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON); - } - - 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(); - } - - /** Verify a signature for this pubkey, after it has been initialized by the signer using - * initSignature(). This method should probably move into a wrapped PGPSignature class - * at some point. - */ - public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException { + protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { try { - return mSig.verifyCertification(uid, key.getPublicKey()); + return mSig.verifyCertification(uid, key); } catch (SignatureException e) { throw new PgpGeneralException("Error!", e); } catch (PGPException e) { @@ -105,6 +127,13 @@ public class WrappedSignature { } } + 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 68726d3e0..ed8171587 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -23,11 +23,9 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; @@ -256,6 +254,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { }.getReadableDatabase(); Cursor cursor = null; + ProviderHelper providerHelper = new ProviderHelper(context); + try { // we insert in two steps: first, all public keys that have secret keys cursor = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS (" @@ -266,14 +266,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - else if (ring instanceof PGPSecretKeyRing) - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - else { - Log.e(Constants.TAG, "Unknown blob data type!"); + try { + UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); + providerHelper.saveKeyRing(ring); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error decoding keyring blob!"); } } } @@ -293,14 +290,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) { - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - } else if (ring instanceof PGPSecretKeyRing) { - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - } else { - Log.e(Constants.TAG, "Unknown blob data type!"); + try { + UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); + providerHelper.saveKeyRing(ring); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error decoding keyring blob!"); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 67d11f9f0..b6f75e00d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -23,30 +23,21 @@ import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; import android.support.v4.util.LongSparseArray; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.S2K; -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.PGPSignature; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; @@ -60,7 +51,6 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -147,19 +137,26 @@ public class ProviderHelper { return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - @Deprecated - public LongSparseArray getPGPKeyRings(Uri queryUri) { + private LongSparseArray getUncachedMasterKeys(Uri queryUri) { Cursor cursor = mContentResolver.query(queryUri, new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA}, null, null, null); - LongSparseArray result = new LongSparseArray(cursor.getCount()); + LongSparseArray result = + new LongSparseArray(cursor.getCount()); try { if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); byte[] data = cursor.getBlob(1); if (data != null) { - result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); + try { + result.put(masterKeyId, + UncachedKeyRing.decodePubkeyFromData(data).getPublicKey()); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error parsing keyring, skipping."); + } catch(IOException e) { + Log.e(Constants.TAG, "IO error, skipping keyring"); + } } } while (cursor.moveToNext()); } finally { @@ -194,12 +191,13 @@ public class ProviderHelper { private KeyRing getWrappedKeyRing(Uri queryUri, boolean secret) throws NotFoundException { Cursor cursor = mContentResolver.query(queryUri, - new String[] { - // we pick from cache only information that is not easily available from keyrings - KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED, - // and of course, ring data - secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA - }, null, null, null); + new String[]{ + // we pick from cache only information that is not easily available from keyrings + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED, + // and of course, ring data + secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA + }, null, null, null + ); try { if (cursor != null && cursor.moveToFirst()) { @@ -219,37 +217,18 @@ public class ProviderHelper { } } - @Deprecated - public PGPKeyRing getPGPKeyRing(Uri queryUri) throws NotFoundException { - LongSparseArray result = getPGPKeyRings(queryUri); - if (result.size() == 0) { - throw new NotFoundException("PGPKeyRing object not found!"); - } else { - return result.valueAt(0); - } - } - - /** - * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId - */ - @Deprecated - public PGPSecretKeyRing getPGPSecretKeyRing(long masterKeyId) throws NotFoundException { - Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); - return (PGPSecretKeyRing) getPGPKeyRing(queryUri); - } - /** * Saves PGPPublicKeyRing with its keys and userIds in DB */ @SuppressWarnings("unchecked") - public void saveKeyRing(PGPPublicKeyRing keyRing) throws IOException { - PGPPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyID(); + public void saveKeyRing(UncachedKeyRing keyRing) throws IOException { + UncachedPublicKey masterKey = keyRing.getPublicKey(); + long masterKeyId = masterKey.getKeyId(); // IF there is a secret key, preserve it! - PGPSecretKeyRing secretRing = null; + UncachedSecretKeyRing secretRing = null; try { - secretRing = getPGPSecretKeyRing(masterKeyId); + secretRing = getWrappedSecretKeyRing(masterKeyId).getUncached(); } catch (NotFoundException e) { Log.e(Constants.TAG, "key not found!"); } @@ -272,36 +251,38 @@ public class ProviderHelper { ArrayList operations = new ArrayList(); int rank = 0; - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + for (UncachedPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { operations.add(buildPublicKeyOperations(masterKeyId, key, rank)); ++rank; } // get a list of owned secret keys, for verification filtering - LongSparseArray allKeyRings = getPGPKeyRings(KeyRingData.buildSecretKeyRingUri()); + LongSparseArray allKeyRings = + getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri()); // special case: available secret keys verify themselves! - if (secretRing != null) - allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing); + if (secretRing != null) { + allKeyRings.put(secretRing.getMasterKeyId(), secretRing.getPublicKey()); + } // classify and order user ids. primary are moved to the front, revoked to the back, // otherwise the order in the keyfile is preserved. List uids = new ArrayList(); - for (String userId : new IterableIterator(masterKey.getUserIDs())) { + for (String userId : new IterableIterator( + masterKey.getUnorderedUserIds().iterator())) { UserIdItem item = new UserIdItem(); uids.add(item); item.userId = userId; // look through signatures for this specific key - for (PGPSignature cert : new IterableIterator( - masterKey.getSignaturesForID(userId))) { - long certId = cert.getKeyID(); + for (WrappedSignature cert : new IterableIterator( + masterKey.getSignaturesForId(userId))) { + long certId = cert.getKeyId(); try { // self signature if (certId == masterKeyId) { - cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME), masterKey); - if (!cert.verifyCertification(userId, masterKey)) { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, userId)) { // not verified?! dang! TODO notify user? this is kinda serious... Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!"); continue; @@ -310,31 +291,22 @@ public class ProviderHelper { if (item.selfCert == null || item.selfCert.getCreationTime().before(cert.getCreationTime())) { item.selfCert = cert; - item.isPrimary = cert.getHashedSubPackets().isPrimaryUserID(); - item.isRevoked = - cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION; + item.isPrimary = cert.isPrimaryUserId(); + item.isRevoked = cert.isRevocation(); } } // verify signatures from known private keys if (allKeyRings.indexOfKey(certId) >= 0) { - // mark them as verified - cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME), - allKeyRings.get(certId).getPublicKey()); - if (cert.verifyCertification(userId, masterKey)) { + cert.init(allKeyRings.get(certId)); + if (cert.verifySignature(masterKey, userId)) { item.trustedCerts.add(cert); } } - } catch (SignatureException e) { - Log.e(Constants.TAG, "Signature verification failed! " - + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID()) - + " from " - + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e); - } catch (PGPException e) { + } catch (PgpGeneralException e) { Log.e(Constants.TAG, "Signature verification failed! " - + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID()) + + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyId()) + " from " - + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e); + + PgpKeyHelper.convertKeyIdToHex(cert.getKeyId()), e); } } } @@ -380,8 +352,8 @@ public class ProviderHelper { String userId; boolean isPrimary = false; boolean isRevoked = false; - PGPSignature selfCert; - List trustedCerts = new ArrayList(); + WrappedSignature selfCert; + List trustedCerts = new ArrayList(); @Override public int compareTo(UserIdItem o) { @@ -401,18 +373,8 @@ public class ProviderHelper { * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring * is already in the database! */ - public void saveKeyRing(UncachedSecretKeyRing wrappedRing) throws IOException { - // TODO split up getters - PGPSecretKeyRing keyRing = wrappedRing.getSecretKeyRing(); - saveKeyRing(keyRing); - } - - /** - * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring - * is already in the database! - */ - public void saveKeyRing(PGPSecretKeyRing keyRing) throws IOException { - long masterKeyId = keyRing.getSecretKey().getKeyID(); + public void saveKeyRing(UncachedSecretKeyRing keyRing) throws IOException { + long masterKeyId = keyRing.getMasterKeyId(); { Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); @@ -424,14 +386,10 @@ public class ProviderHelper { values.put(Keys.HAS_SECRET, 1); // then, mark exactly the keys we have available - for (PGPSecretKey sub : new IterableIterator(keyRing.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) { - mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[]{ - Long.toString(sub.getKeyID()) - }); - } + for (Long sub : new IterableIterator(keyRing.getAvailableSubkeys().iterator())) { + mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[] { + Long.toString(sub) + }); } // this implicitly leaves all keys which were not in the secret key ring // with has_secret = 0 @@ -449,11 +407,6 @@ public class ProviderHelper { } - public void saveKeyRing(UncachedKeyRing ring) throws IOException { - PGPPublicKeyRing pubRing = (PGPPublicKeyRing) ring.getRing(); - saveKeyRing(pubRing); - } - /** * Saves (or updates) a pair of public and secret KeyRings in the database */ @@ -464,32 +417,32 @@ public class ProviderHelper { mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null); // save public keyring - saveKeyRing((PGPPublicKeyRing) pubRing.getRing()); - saveKeyRing((PGPSecretKeyRing) secRing.getRing()); + saveKeyRing(pubRing); + saveKeyRing(secRing); } /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private ContentProviderOperation - buildPublicKeyOperations(long masterKeyId, PGPPublicKey key, int rank) throws IOException { + buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException { ContentValues values = new ContentValues(); values.put(Keys.MASTER_KEY_ID, masterKeyId); values.put(Keys.RANK, rank); - values.put(Keys.KEY_ID, key.getKeyID()); + values.put(Keys.KEY_ID, key.getKeyId()); values.put(Keys.KEY_SIZE, key.getBitStrength()); values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.FINGERPRINT, key.getFingerprint()); - values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key))); - values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key))); - values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.isRevoked()); + values.put(Keys.CAN_CERTIFY, key.canCertify()); + values.put(Keys.CAN_SIGN, key.canSign()); + values.put(Keys.CAN_ENCRYPT, key.canEncrypt()); + values.put(Keys.IS_REVOKED, key.maybeRevoked()); - values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); - Date expiryDate = PgpKeyHelper.getExpiryDate(key); + values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000); + Date expiryDate = key.getExpiryTime(); if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); } @@ -503,11 +456,12 @@ public class ProviderHelper { * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private ContentProviderOperation - buildCertOperations(long masterKeyId, int rank, PGPSignature cert, int verified) throws IOException { + buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, int verified) + throws IOException { ContentValues values = new ContentValues(); values.put(Certs.MASTER_KEY_ID, masterKeyId); values.put(Certs.RANK, rank); - values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID()); + values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId()); values.put(Certs.TYPE, cert.getSignatureType()); values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); values.put(Certs.VERIFIED, verified); @@ -535,23 +489,11 @@ public class ProviderHelper { return ContentProviderOperation.newInsert(uri).withValues(values).build(); } - private String getKeyRingAsArmoredString(byte[] data) throws IOException { - Object keyRing = null; - if (data != null) { - keyRing = PgpConversionHelper.BytesToPGPKeyRing(data); - } + private String getKeyRingAsArmoredString(byte[] data) throws IOException, PgpGeneralException { + UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = new ArmoredOutputStream(bos); - aos.setHeader("Version", PgpHelper.getFullVersion(mContext)); - - if (keyRing instanceof PGPSecretKeyRing) { - aos.write(((PGPSecretKeyRing) keyRing).getEncoded()); - } else if (keyRing instanceof PGPPublicKeyRing) { - aos.write(((PGPPublicKeyRing) keyRing).getEncoded()); - } - aos.close(); - + keyRing.encodeArmored(bos, PgpHelper.getFullVersion(mContext)); String armoredKey = bos.toString("UTF-8"); Log.d(Constants.TAG, "armoredKey:" + armoredKey); @@ -560,77 +502,12 @@ public class ProviderHelper { } public String getKeyRingAsArmoredString(Uri uri) - throws NotFoundException, IOException { + throws NotFoundException, IOException, PgpGeneralException { byte[] data = (byte[]) getGenericData( uri, KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB); return getKeyRingAsArmoredString(data); } - /** - * TODO: currently not used, but will be needed to upload many keys at once! - * - * @param masterKeyIds - * @return - * @throws IOException - */ - public ArrayList getKeyRingsAsArmoredString(long[] masterKeyIds) - throws IOException { - ArrayList output = new ArrayList(); - - if (masterKeyIds == null || masterKeyIds.length == 0) { - Log.e(Constants.TAG, "No master keys given!"); - return output; - } - - // Build a cursor for the selected masterKeyIds - Cursor cursor; - { - String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < masterKeyIds.length; ++i) { - if (i != 0) { - inMasterKeyList += ", "; - } - inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]); - } - inMasterKeyList += ")"; - - cursor = mContentResolver.query(KeyRingData.buildPublicKeyRingUri(), new String[]{ - KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA - }, inMasterKeyList, null, null); - } - - try { - if (cursor != null) { - int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID); - int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); - if (cursor.moveToFirst()) { - do { - Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); - - byte[] data = cursor.getBlob(dataCol); - - // get actual keyring data blob and write it to ByteArrayOutputStream - try { - output.add(getKeyRingAsArmoredString(data)); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); - } - } while (cursor.moveToNext()); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - if (output.size() > 0) { - return output; - } else { - return null; - } - } - public ArrayList getRegisteredApiApps() { Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index c2fc4334a..69eab9d4e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -26,16 +26,13 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import org.spongycastle.bcpg.sig.KeyFlags; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.pgp.UncachedSecretKey; import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.WrappedSecretKey; import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing; @@ -594,21 +591,21 @@ public class KeychainIntentService extends IntentService buf = keyOperations.createKey(Constants.choice.algorithm.rsa, 4096, passphrase, true); os.write(buf); - keyUsageList.add(KeyFlags.CERTIFY_OTHER); + keyUsageList.add(UncachedSecretKey.CERTIFY_OTHER); keysCreated++; setProgress(keysCreated, keysTotal); buf = keyOperations.createKey(Constants.choice.algorithm.rsa, 4096, passphrase, false); os.write(buf); - keyUsageList.add(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); + keyUsageList.add(UncachedSecretKey.ENCRYPT_COMMS | UncachedSecretKey.ENCRYPT_STORAGE); keysCreated++; setProgress(keysCreated, keysTotal); buf = keyOperations.createKey(Constants.choice.algorithm.rsa, 4096, passphrase, false); os.write(buf); - keyUsageList.add(KeyFlags.SIGN_DATA); + keyUsageList.add(UncachedSecretKey.SIGN_DATA); keysCreated++; setProgress(keysCreated, keysTotal); @@ -749,23 +746,15 @@ public class KeychainIntentService extends IntentService byte[] downloadedKeyBytes = server.get(keybaseId).getBytes(); // create PGPKeyRing object based on downloaded armored key - PGPKeyRing downloadedKey = null; + UncachedKeyRing downloadedKey = null; BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes)); 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) { - - if (obj instanceof PGPKeyRing) { - downloadedKey = (PGPKeyRing) obj; - } else { - throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); - } + List rings = UncachedKeyRing.fromStream(bufferedInput); + if(rings.isEmpty()) { + throw new PgpGeneralException("No keys in result!"); } + downloadedKey = rings.get(0); } // save key bytes in entry object for doing the diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 17ba9df5c..d42bae67a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -34,12 +34,6 @@ import android.os.Messenger; import android.os.RemoteException; import android.support.v4.util.LongSparseArray; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing; @@ -48,7 +42,6 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; -import java.util.Iterator; /** * This service runs in its own process, but is available to all other processes as the main @@ -191,7 +184,8 @@ public class PassphraseCacheService extends Service { // get cached passphrase String cachedPassphrase = mPassphraseCache.get(keyId); if (cachedPassphrase == null) { - // this is an error + Log.d(TAG, "Passphrase not (yet) cached, returning null"); + // not really an error, just means the passphrase is not cached but not empty either return null; } @@ -206,44 +200,6 @@ public class PassphraseCacheService extends Service { } } - @Deprecated - public static boolean hasPassphrase(PGPSecretKeyRing secretKeyRing) { - PGPSecretKey secretKey = null; - boolean foundValidKey = false; - for (Iterator keys = secretKeyRing.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; - } - } - - /** - * Checks if key has a passphrase. - * - * @param secretKeyId - * @return true if it has a passphrase - */ - @Deprecated - public static boolean hasPassphrase(Context context, long secretKeyId) - throws ProviderHelper.NotFoundException { - return new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase(); - } - /** * Register BroadcastReceiver that is unregistered when service is destroyed. This * BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index fffcdacc8..3514ab2e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -13,7 +13,8 @@ import java.util.HashMap; * * All changes are done in a differential manner. Besides the two key * identification attributes, all attributes may be null, which indicates no - * change to the keyring. + * change to the keyring. This is also the reason why boxed values are used + * instead of primitives in the subclasses. * * Application of operations in the backend should be fail-fast, which means an * error in any included operation (for example revocation of a non-existent @@ -45,10 +46,12 @@ public class SaveKeyringParcel implements Parcelable { // performance gain for using Parcelable here would probably be negligible, // use Serializable instead. public static class SubkeyAdd implements Serializable { + public final int mAlgorithm; public final int mKeysize; public final int mFlags; public final Long mExpiry; - public SubkeyAdd(int keysize, int flags, long expiry) { + public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) { + mAlgorithm = algorithm; mKeysize = keysize; mFlags = flags; mExpiry = expiry; @@ -59,7 +62,7 @@ public class SaveKeyringParcel implements Parcelable { public final long mKeyId; public final Integer mFlags; public final Long mExpiry; - public SubkeyChange(long keyId, int flags, long expiry) { + public SubkeyChange(long keyId, Integer flags, Long expiry) { mKeyId = keyId; mFlags = flags; mExpiry = expiry; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java index b3655133d..a264a804f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyShareFragment.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -190,6 +191,9 @@ public class ViewKeyShareFragment extends LoaderFragment implements } startActivity(Intent.createChooser(sendIntent, title)); } + } catch (PgpGeneralException e) { + Log.e(Constants.TAG, "error processing key!", e); + AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show(); } catch (IOException e) { Log.e(Constants.TAG, "error processing key!", e); AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index e7bcd2bc0..5f47ee13c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -102,7 +102,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor // check if secret key has a passphrase if (!(secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none)) { try { - if (new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) { + if (!new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) { throw new PgpGeneralException("No passphrase! No passphrase dialog needed!"); } } catch(ProviderHelper.NotFoundException e) { -- cgit v1.2.3