/* * Copyright (C) 2012 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.helper; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.spec.ElGamalParameterSpec; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPCompressedDataGenerator; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPEncryptedDataList; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyPair; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRingGenerator; import org.spongycastle.openpgp.PGPLiteralData; import org.spongycastle.openpgp.PGPLiteralDataGenerator; 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.PGPPublicKeyRingCollection; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureList; 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.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPDigestCalculator; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.util.HkpKeyServer; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.R; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.Environment; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.regex.Pattern; /** * TODO: * * - Separate this file into different helpers * */ public class PgpMain { static { // Define Java Security Provider to be Bouncy Castle Security.insertProviderAt(new BouncyCastleProvider(), 1); } // Not BC due to the use of Spongy Castle for Android public static final String SC = BouncyCastleProvider.PROVIDER_NAME; public static final String BOUNCY_CASTLE_PROVIDER_NAME = SC; private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] { SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, SymmetricKeyAlgorithmTags.TRIPLE_DES }; private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160 }; private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] { CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZIP }; public static Pattern PGP_MESSAGE = Pattern.compile( ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); public static Pattern PGP_SIGNED_MESSAGE = Pattern .compile( ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", Pattern.DOTALL); public static Pattern PGP_PUBLIC_KEY = Pattern.compile( ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", Pattern.DOTALL); private static String mEditPassPhrase = null; public static class PgpGeneralException extends Exception { static final long serialVersionUID = 0xf812773342L; public PgpGeneralException(String message) { super(message); } } public static class NoAsymmetricEncryptionException extends Exception { static final long serialVersionUID = 0xf812773343L; public NoAsymmetricEncryptionException() { super(); } } public static void setEditPassPhrase(String passPhrase) { mEditPassPhrase = passPhrase; } public static String getEditPassPhrase() { return mEditPassPhrase; } public static void updateProgress(ProgressDialogUpdater progress, int message, int current, int total) { if (progress != null) { progress.setProgress(message, current, total); } } public static void updateProgress(ProgressDialogUpdater progress, int current, int total) { if (progress != null) { progress.setProgress(current, total); } } /** * Creates new secret key. The returned PGPSecretKeyRing contains only one newly generated key * when this key is the new masterkey. If a masterkey is supplied in the parameters * PGPSecretKeyRing contains the masterkey and the new key as a subkey (certified by the * masterkey). * * @param context * @param algorithmChoice * @param keySize * @param passPhrase * @param masterSecretKey * @return * @throws NoSuchAlgorithmException * @throws PGPException * @throws NoSuchProviderException * @throws PgpGeneralException * @throws InvalidAlgorithmParameterException */ public static PGPSecretKeyRing createKey(Context context, int algorithmChoice, int keySize, String passPhrase, PGPSecretKey masterSecretKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, PgpGeneralException, InvalidAlgorithmParameterException { if (keySize < 512) { throw new PgpGeneralException(context.getString(R.string.error_keySizeMinimum512bit)); } if (passPhrase == null) { passPhrase = ""; } int algorithm = 0; KeyPairGenerator keyGen = null; switch (algorithmChoice) { case Id.choice.algorithm.dsa: { keyGen = KeyPairGenerator.getInstance("DSA", BOUNCY_CASTLE_PROVIDER_NAME); keyGen.initialize(keySize, new SecureRandom()); algorithm = PGPPublicKey.DSA; break; } case Id.choice.algorithm.elgamal: { if (masterSecretKey == null) { throw new PgpGeneralException( context.getString(R.string.error_masterKeyMustNotBeElGamal)); } keyGen = KeyPairGenerator.getInstance("ElGamal", BOUNCY_CASTLE_PROVIDER_NAME); BigInteger p = Primes.getBestPrime(keySize); BigInteger g = new BigInteger("2"); ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); keyGen.initialize(elParams); algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; break; } case Id.choice.algorithm.rsa: { keyGen = KeyPairGenerator.getInstance("RSA", BOUNCY_CASTLE_PROVIDER_NAME); keyGen.initialize(keySize, new SecureRandom()); algorithm = PGPPublicKey.RSA_GENERAL; break; } default: { throw new PgpGeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); } } // build new key pair PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); // define hashing and signing algos PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( HashAlgorithmTags.SHA1); // Build key encrypter and decrypter based on passphrase PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PGPEncryptedData.CAST5, sha1Calc).setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build( passPhrase.toCharArray()); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray()); PGPKeyRingGenerator ringGen = null; PGPContentSignerBuilder certificationSignerBuilder = null; if (masterSecretKey == null) { certificationSignerBuilder = new JcaPGPContentSignerBuilder(keyPair.getPublicKey() .getAlgorithm(), HashAlgorithmTags.SHA1); // build keyRing with only this one master key in it! ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, keyPair, "", sha1Calc, null, null, certificationSignerBuilder, keyEncryptor); } else { PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); PGPPrivateKey masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); certificationSignerBuilder = new JcaPGPContentSignerBuilder(masterKeyPair .getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); // build keyRing with master key and new key as subkey (certified by masterkey) ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, "", sha1Calc, null, null, certificationSignerBuilder, keyEncryptor); ringGen.addSubKey(keyPair); } PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); return secKeyRing; } public static void changeSecretKeyPassphrase(Context context, PGPSecretKeyRing keyRing, String oldPassPhrase, String newPassPhrase, ProgressDialogUpdater progress) throws IOException, PGPException, PGPException, NoSuchProviderException { updateProgress(progress, R.string.progress_buildingKey, 0, 100); if (oldPassPhrase == null) { oldPassPhrase = ""; } if (newPassPhrase == null) { newPassPhrase = ""; } PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword( keyRing, new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()), new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey() .getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray())); updateProgress(progress, R.string.progress_savingKeyRing, 50, 100); ProviderHelper.saveKeyRing(context, newKeyRing); updateProgress(progress, R.string.progress_done, 100, 100); } public static void buildSecretKey(Context context, ArrayList userIds, ArrayList keys, ArrayList keysUsages, long masterKeyId, String oldPassPhrase, String newPassPhrase, ProgressDialogUpdater progress) throws PgpGeneralException, NoSuchProviderException, PGPException, NoSuchAlgorithmException, SignatureException, IOException { Log.d(Constants.TAG, "userIds: " + userIds.toString()); updateProgress(progress, R.string.progress_buildingKey, 0, 100); if (oldPassPhrase == null) { oldPassPhrase = ""; } if (newPassPhrase == null) { newPassPhrase = ""; } updateProgress(progress, R.string.progress_preparingMasterKey, 10, 100); int usageId = keysUsages.get(0); boolean canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); String mainUserId = userIds.get(0); PGPSecretKey masterKey = keys.get(0); // this removes all userIds and certifications previously attached to the masterPublicKey PGPPublicKey tmpKey = masterKey.getPublicKey(); PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); // already done by code above: // PGPPublicKey masterPublicKey = masterKey.getPublicKey(); // // Somehow, the PGPPublicKey already has an empty certification attached to it when the // // keyRing is generated the first time, we remove that when it exists, before adding the // new // // ones // PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey, // ""); // if (masterPublicKeyRmCert != null) { // masterPublicKey = masterPublicKeyRmCert; // } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); updateProgress(progress, R.string.progress_certifyingMasterKey, 20, 100); for (String userId : userIds) { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); } // TODO: cross-certify the master key with every sub key (APG 1) PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; if (canEncrypt) { keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; } hashedPacketsGen.setKeyFlags(true, keyFlags); hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); // TODO: this doesn't work quite right yet (APG 1) // if (keyEditor.getExpiryDate() != null) { // GregorianCalendar creationDate = new GregorianCalendar(); // creationDate.setTime(getCreationDate(masterKey)); // GregorianCalendar expiryDate = keyEditor.getExpiryDate(); // long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); // if (numDays <= 0) { // throw new GeneralException( // context.getString(R.string.error_expiryMustComeAfterCreation)); // } // hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); // } updateProgress(progress, R.string.progress_buildingMasterKeyRing, 30, 100); // define hashing and signing algos PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( HashAlgorithmTags.SHA1); PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); // Build key encrypter based on passphrase PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PGPEncryptedData.CAST5, sha1Calc).setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build( newPassPhrase.toCharArray()); PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); updateProgress(progress, R.string.progress_addingSubKeys, 40, 100); for (int i = 1; i < keys.size(); ++i) { updateProgress(progress, 40 + 50 * (i - 1) / (keys.size() - 1), 100); PGPSecretKey subKey = keys.get(i); PGPPublicKey subPublicKey = subKey.getPublicKey(); PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); // TODO: now used without algorithm and creation time?! (APG 1) PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); hashedPacketsGen = new PGPSignatureSubpacketGenerator(); unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); keyFlags = 0; usageId = keysUsages.get(i); canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); if (canSign) { keyFlags |= KeyFlags.SIGN_DATA; } if (canEncrypt) { keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; } hashedPacketsGen.setKeyFlags(true, keyFlags); // TODO: this doesn't work quite right yet (APG 1) // if (keyEditor.getExpiryDate() != null) { // GregorianCalendar creationDate = new GregorianCalendar(); // creationDate.setTime(getCreationDate(masterKey)); // GregorianCalendar expiryDate = keyEditor.getExpiryDate(); // long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); // if (numDays <= 0) { // throw new GeneralException( // context.getString(R.string.error_expiryMustComeAfterCreation)); // } // hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); // } keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate()); } PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); updateProgress(progress, R.string.progress_savingKeyRing, 90, 100); ProviderHelper.saveKeyRing(context, secretKeyRing); ProviderHelper.saveKeyRing(context, publicKeyRing); updateProgress(progress, R.string.progress_done, 100, 100); } /** * TODO: implement Id.return_value.updated as status when key already existed * * @param context * @param keyring * @return */ @SuppressWarnings("unchecked") public static int storeKeyRingInCache(Context context, PGPKeyRing keyring) { int status = Integer.MIN_VALUE; // out of bounds value (Id.return_value.*) try { if (keyring instanceof PGPSecretKeyRing) { PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring; boolean save = true; for (PGPSecretKey testSecretKey : new IterableIterator( secretKeyRing.getSecretKeys())) { if (!testSecretKey.isMasterKey()) { if (PgpHelper.isSecretKeyPrivateEmpty(testSecretKey)) { // this is bad, something is very wrong... save = false; status = Id.return_value.bad; } } } if (save) { ProviderHelper.saveKeyRing(context, secretKeyRing); // TODO: remove status returns, use exceptions! status = Id.return_value.ok; } } else if (keyring instanceof PGPPublicKeyRing) { PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; ProviderHelper.saveKeyRing(context, publicKeyRing); // TODO: remove status returns, use exceptions! status = Id.return_value.ok; } } catch (IOException e) { status = Id.return_value.error; } return status; } public static boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = new ArmoredOutputStream(bos); try { aos.write(keyring.getEncoded()); aos.close(); String armouredKey = bos.toString("UTF-8"); server.add(armouredKey); return true; } catch (IOException e) { return false; } catch (AddKeyException e) { // TODO: tell the user? return false; } finally { try { bos.close(); } catch (IOException e) { } } } public static Bundle importKeyRings(Context context, InputData data, ProgressDialogUpdater progress) throws PgpGeneralException, FileNotFoundException, PGPException, IOException { Bundle returnData = new Bundle(); updateProgress(progress, R.string.progress_importingSecretKeys, 0, 100); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { throw new PgpGeneralException(context.getString(R.string.error_externalStorageNotReady)); } PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream()); // need to have access to the bufferedInput, so we can reuse it for the possible // PGPObject chunks after the first one, e.g. files with several consecutive ASCII // armour blocks BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); int newKeys = 0; int oldKeys = 0; int badKeys = 0; try { // read all available blocks... (asc files can contain many blocks with BEGIN END) while (bufferedInput.available() > 0) { InputStream in = PGPUtil.getDecoderStream(bufferedInput); PGPObjectFactory objectFactory = new PGPObjectFactory(in); // 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) { PGPKeyRing keyring = (PGPKeyRing) obj; int status = Integer.MIN_VALUE; // out of bounds value status = storeKeyRingInCache(context, keyring); if (status == Id.return_value.error) { throw new PgpGeneralException( context.getString(R.string.error_savingKeys)); } // update the counts to display to the user at the end if (status == Id.return_value.updated) { ++oldKeys; } else if (status == Id.return_value.ok) { ++newKeys; } else if (status == Id.return_value.bad) { ++badKeys; } updateProgress(progress, (int) (100 * progressIn.position() / data.getSize()), 100); } else { Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); } } } } catch (Exception e) { Log.e(Constants.TAG, "Exception on parsing key file!", e); } returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys); returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys); returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys); updateProgress(progress, R.string.progress_done, 100, 100); return returnData; } public static Bundle exportKeyRings(Context context, ArrayList keyRingMasterKeyIds, int keyType, OutputStream outStream, ProgressDialogUpdater progress) throws PgpGeneralException, FileNotFoundException, PGPException, IOException { Bundle returnData = new Bundle(); if (keyRingMasterKeyIds.size() == 1) { updateProgress(progress, R.string.progress_exportingKey, 0, 100); } else { updateProgress(progress, R.string.progress_exportingKeys, 0, 100); } if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { throw new PgpGeneralException(context.getString(R.string.error_externalStorageNotReady)); } // export public keyrings... ArmoredOutputStream outPub = new ArmoredOutputStream(outStream); outPub.setHeader("Version", getFullVersion(context)); int numKeys = 0; for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { // double the needed time if exporting both public and secret parts if (keyType == Id.type.secret_key) { updateProgress(progress, i * 100 / keyRingMasterKeyIds.size() / 2, 100); } else { updateProgress(progress, i * 100 / keyRingMasterKeyIds.size(), 100); } PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId( context, keyRingMasterKeyIds.get(i)); if (publicKeyRing != null) { publicKeyRing.encode(outPub); } ++numKeys; } outPub.close(); // if we export secret keyrings, append all secret parts after the public parts if (keyType == Id.type.secret_key) { ArmoredOutputStream outSec = new ArmoredOutputStream(outStream); outSec.setHeader("Version", getFullVersion(context)); for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { updateProgress(progress, i * 100 / keyRingMasterKeyIds.size() / 2, 100); PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( context, keyRingMasterKeyIds.get(i)); if (secretKeyRing != null) { secretKeyRing.encode(outSec); } } outSec.close(); } returnData.putInt(KeychainIntentService.RESULT_EXPORT, numKeys); updateProgress(progress, R.string.progress_done, 100, 100); return returnData; } /** * Encrypt and Sign data * * @param context * @param progress * @param data * @param outStream * @param useAsciiArmor * @param compression * @param encryptionKeyIds * @param symmetricEncryptionAlgorithm * @param encryptionPassphrase * @param signatureKeyId * @param signatureHashAlgorithm * @param signatureForceV3 * @param signaturePassphrase * @throws IOException * @throws PgpGeneralException * @throws PGPException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws SignatureException */ public static void encryptAndSign(Context context, ProgressDialogUpdater progress, InputData data, OutputStream outStream, boolean useAsciiArmor, int compression, ArrayList encryptionKeyIds, String encryptionPassphrase, int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm, boolean signatureForceV3, String signaturePassphrase) throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException { if (encryptionKeyIds == null) { encryptionKeyIds = new ArrayList(); } ArmoredOutputStream armorOut = null; OutputStream out = null; OutputStream encryptOut = null; if (useAsciiArmor) { armorOut = new ArmoredOutputStream(outStream); armorOut.setHeader("Version", getFullVersion(context)); out = armorOut; } else { out = outStream; } PGPSecretKey signingKey = null; PGPSecretKeyRing signingKeyRing = null; PGPPrivateKey signaturePrivateKey = null; if (encryptionKeyIds.size() == 0 && encryptionPassphrase == null) { throw new PgpGeneralException( context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); } if (signatureKeyId != Id.key.none) { signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); signingKey = PgpHelper.getSigningKey(context, signatureKeyId); if (signingKey == null) { throw new PgpGeneralException(context.getString(R.string.error_signatureFailed)); } if (signaturePassphrase == null) { throw new PgpGeneralException( context.getString(R.string.error_noSignaturePassPhrase)); } updateProgress(progress, R.string.progress_extractingSignatureKey, 0, 100); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { throw new PgpGeneralException( context.getString(R.string.error_couldNotExtractPrivateKey)); } } updateProgress(progress, R.string.progress_preparingStreams, 5, 100); // encrypt and compress input file content JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder( symmetricEncryptionAlgorithm).setProvider(BOUNCY_CASTLE_PROVIDER_NAME) .setWithIntegrityPacket(true); PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(encryptorBuilder); if (encryptionKeyIds.size() == 0) { // Symmetric encryption Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = new JcePBEKeyEncryptionMethodGenerator( encryptionPassphrase.toCharArray()); cPk.addMethod(symmetricEncryptionGenerator); } else { // Asymmetric encryption for (long id : encryptionKeyIds) { PGPPublicKey key = PgpHelper.getEncryptPublicKey(context, id); if (key != null) { JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = new JcePublicKeyKeyEncryptionMethodGenerator( key); cPk.addMethod(pubKeyEncryptionGenerator); } } } encryptOut = cPk.open(out, new byte[1 << 16]); PGPSignatureGenerator signatureGenerator = null; PGPV3SignatureGenerator signatureV3Generator = null; if (signatureKeyId != Id.key.none) { updateProgress(progress, R.string.progress_preparingSignature, 10, 100); // content signer based on signing key algorithm and choosen hash algorithm JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); if (signatureForceV3) { signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); signatureV3Generator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); } else { signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); String userId = PgpHelper.getMainUserId(PgpHelper.getMasterKey(signingKeyRing)); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); } } PGPCompressedDataGenerator compressGen = null; BCPGOutputStream bcpgOut = null; if (compression == Id.choice.compression.none) { bcpgOut = new BCPGOutputStream(encryptOut); } else { compressGen = new PGPCompressedDataGenerator(compression); bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); } if (signatureKeyId != Id.key.none) { if (signatureForceV3) { signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); } else { signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); } } PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); // file name not needed, so empty string OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), new byte[1 << 16]); updateProgress(progress, R.string.progress_encrypting, 20, 100); long done = 0; int n = 0; byte[] buffer = new byte[1 << 16]; InputStream in = data.getInputStream(); while ((n = in.read(buffer)) > 0) { pOut.write(buffer, 0, n); if (signatureKeyId != Id.key.none) { if (signatureForceV3) { signatureV3Generator.update(buffer, 0, n); } else { signatureGenerator.update(buffer, 0, n); } } done += n; if (data.getSize() != 0) { updateProgress(progress, (int) (20 + (95 - 20) * done / data.getSize()), 100); } } literalGen.close(); if (signatureKeyId != Id.key.none) { updateProgress(progress, R.string.progress_generatingSignature, 95, 100); if (signatureForceV3) { signatureV3Generator.generate().encode(pOut); } else { signatureGenerator.generate().encode(pOut); } } if (compressGen != null) { compressGen.close(); } encryptOut.close(); if (useAsciiArmor) { armorOut.close(); } updateProgress(progress, R.string.progress_done, 100, 100); } public static void signText(Context context, ProgressDialogUpdater progress, InputData data, OutputStream outStream, long signatureKeyId, String signaturePassphrase, int signatureHashAlgorithm, boolean forceV3Signature) throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, SignatureException { ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); armorOut.setHeader("Version", getFullVersion(context)); PGPSecretKey signingKey = null; PGPSecretKeyRing signingKeyRing = null; PGPPrivateKey signaturePrivateKey = null; if (signatureKeyId == 0) { armorOut.close(); throw new PgpGeneralException(context.getString(R.string.error_noSignatureKey)); } signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); signingKey = PgpHelper.getSigningKey(context, signatureKeyId); if (signingKey == null) { armorOut.close(); throw new PgpGeneralException(context.getString(R.string.error_signatureFailed)); } if (signaturePassphrase == null) { armorOut.close(); throw new PgpGeneralException(context.getString(R.string.error_noSignaturePassPhrase)); } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { armorOut.close(); throw new PgpGeneralException( context.getString(R.string.error_couldNotExtractPrivateKey)); } updateProgress(progress, R.string.progress_preparingStreams, 0, 100); updateProgress(progress, R.string.progress_preparingSignature, 30, 100); PGPSignatureGenerator signatureGenerator = null; PGPV3SignatureGenerator signatureV3Generator = null; // content signer based on signing key algorithm and choosen hash algorithm JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey .getPublicKey().getAlgorithm(), signatureHashAlgorithm) .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); if (forceV3Signature) { signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); signatureV3Generator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); } else { signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); String userId = PgpHelper.getMainUserId(PgpHelper.getMasterKey(signingKeyRing)); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); } updateProgress(progress, R.string.progress_signing, 40, 100); armorOut.beginClearText(signatureHashAlgorithm); InputStream inStream = data.getInputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); final byte[] newline = "\r\n".getBytes("UTF-8"); if (forceV3Signature) { processLine(reader.readLine(), armorOut, signatureV3Generator); } else { processLine(reader.readLine(), armorOut, signatureGenerator); } while (true) { final String line = reader.readLine(); if (line == null) { armorOut.write(newline); break; } armorOut.write(newline); if (forceV3Signature) { signatureV3Generator.update(newline); processLine(line, armorOut, signatureV3Generator); } else { signatureGenerator.update(newline); processLine(line, armorOut, signatureGenerator); } } armorOut.endClearText(); BCPGOutputStream bOut = new BCPGOutputStream(armorOut); if (forceV3Signature) { signatureV3Generator.generate().encode(bOut); } else { signatureGenerator.generate().encode(bOut); } armorOut.close(); updateProgress(progress, R.string.progress_done, 100, 100); } public static void generateSignature(Context context, ProgressDialogUpdater progress, InputData data, OutputStream outStream, boolean armored, boolean binary, long signatureKeyId, String signaturePassPhrase, int hashAlgorithm, boolean forceV3Signature) throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, SignatureException { OutputStream out = null; // Ascii Armor (Base64) ArmoredOutputStream armorOut = null; if (armored) { armorOut = new ArmoredOutputStream(outStream); armorOut.setHeader("Version", getFullVersion(context)); out = armorOut; } else { out = outStream; } PGPSecretKey signingKey = null; PGPSecretKeyRing signingKeyRing = null; PGPPrivateKey signaturePrivateKey = null; if (signatureKeyId == 0) { throw new PgpGeneralException(context.getString(R.string.error_noSignatureKey)); } signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); signingKey = PgpHelper.getSigningKey(context, signatureKeyId); if (signingKey == null) { throw new PgpGeneralException(context.getString(R.string.error_signatureFailed)); } if (signaturePassPhrase == null) { throw new PgpGeneralException(context.getString(R.string.error_noSignaturePassPhrase)); } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.toCharArray()); signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { throw new PgpGeneralException( context.getString(R.string.error_couldNotExtractPrivateKey)); } updateProgress(progress, R.string.progress_preparingStreams, 0, 100); updateProgress(progress, R.string.progress_preparingSignature, 30, 100); PGPSignatureGenerator signatureGenerator = null; PGPV3SignatureGenerator signatureV3Generator = null; int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; if (binary) { type = PGPSignature.BINARY_DOCUMENT; } // content signer based on signing key algorithm and choosen hash algorithm JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey .getPublicKey().getAlgorithm(), hashAlgorithm) .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); if (forceV3Signature) { signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); signatureV3Generator.init(type, signaturePrivateKey); } else { signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(type, signaturePrivateKey); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); String userId = PgpHelper.getMainUserId(PgpHelper.getMasterKey(signingKeyRing)); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); } updateProgress(progress, R.string.progress_signing, 40, 100); InputStream inStream = data.getInputStream(); if (binary) { byte[] buffer = new byte[1 << 16]; int n = 0; while ((n = inStream.read(buffer)) > 0) { if (forceV3Signature) { signatureV3Generator.update(buffer, 0, n); } else { signatureGenerator.update(buffer, 0, n); } } } else { final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); final byte[] newline = "\r\n".getBytes("UTF-8"); while (true) { final String line = reader.readLine(); if (line == null) { break; } if (forceV3Signature) { processLine(line, null, signatureV3Generator); signatureV3Generator.update(newline); } else { processLine(line, null, signatureGenerator); signatureGenerator.update(newline); } } } BCPGOutputStream bOut = new BCPGOutputStream(out); if (forceV3Signature) { signatureV3Generator.generate().encode(bOut); } else { signatureGenerator.generate().encode(bOut); } out.close(); outStream.close(); if (progress != null) progress.setProgress(R.string.progress_done, 100, 100); } public static PGPPublicKeyRing signKey(Context context, long masterKeyId, long pubKeyId, String passphrase) throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException { if (passphrase == null || passphrase.length() <= 0) { throw new PgpGeneralException("Unable to obtain passphrase"); } else { PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByKeyId(context, pubKeyId); PGPSecretKey signingKey = PgpHelper.getCertificationKey(context, masterKeyId); if (signingKey == null) { throw new PgpGeneralException(context.getString(R.string.error_signatureFailed)); } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { throw new PgpGeneralException( context.getString(R.string.error_couldNotExtractPrivateKey)); } // TODO: SHA256 fixed? JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( contentSignerBuilder); signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketVector packetVector = spGen.generate(); signatureGenerator.setHashedSubpackets(packetVector); PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId), signatureGenerator.generate()); pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); return pubring; } } public static long getDecryptionKeyId(Context context, InputStream inputStream) throws PgpGeneralException, NoAsymmetricEncryptionException, IOException { InputStream in = PGPUtil.getDecoderStream(inputStream); PGPObjectFactory pgpF = new PGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); // the first object might be a PGP marker packet. if (o instanceof PGPEncryptedDataList) { enc = (PGPEncryptedDataList) o; } else { enc = (PGPEncryptedDataList) pgpF.nextObject(); } if (enc == null) { throw new PgpGeneralException(context.getString(R.string.error_invalidData)); } // TODO: currently we always only look at the first known key // find the secret key PGPSecretKey secretKey = null; Iterator it = enc.getEncryptedDataObjects(); boolean gotAsymmetricEncryption = false; while (it.hasNext()) { Object obj = it.next(); if (obj instanceof PGPPublicKeyEncryptedData) { gotAsymmetricEncryption = true; PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, pbe.getKeyID()); if (secretKey != null) { break; } } } if (!gotAsymmetricEncryption) { throw new NoAsymmetricEncryptionException(); } if (secretKey == null) { return Id.key.none; } return secretKey.getKeyID(); } public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) throws PgpGeneralException, IOException { InputStream in = PGPUtil.getDecoderStream(inputStream); PGPObjectFactory pgpF = new PGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); // the first object might be a PGP marker packet. if (o instanceof PGPEncryptedDataList) { enc = (PGPEncryptedDataList) o; } else { enc = (PGPEncryptedDataList) pgpF.nextObject(); } if (enc == null) { throw new PgpGeneralException(context.getString(R.string.error_invalidData)); } Iterator it = enc.getEncryptedDataObjects(); while (it.hasNext()) { Object obj = it.next(); if (obj instanceof PGPPBEEncryptedData) { return true; } } return false; } public static Bundle decryptAndVerify(Context context, ProgressDialogUpdater progress, InputData data, OutputStream outStream, String passphrase, boolean assumeSymmetric) throws IOException, PgpGeneralException, PGPException, SignatureException { if (passphrase == null) { passphrase = ""; } Bundle returnData = new Bundle(); InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); PGPObjectFactory pgpF = new PGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); long signatureKeyId = 0; int currentProgress = 0; if (progress != null) progress.setProgress(R.string.progress_readingData, currentProgress, 100); if (o instanceof PGPEncryptedDataList) { enc = (PGPEncryptedDataList) o; } else { enc = (PGPEncryptedDataList) pgpF.nextObject(); } if (enc == null) { throw new PgpGeneralException(context.getString(R.string.error_invalidData)); } InputStream clear = null; PGPEncryptedData encryptedData = null; currentProgress += 5; // TODO: currently we always only look at the first known key or symmetric encryption, // there might be more... if (assumeSymmetric) { PGPPBEEncryptedData pbe = null; Iterator it = enc.getEncryptedDataObjects(); // find secret key while (it.hasNext()) { Object obj = it.next(); if (obj instanceof PGPPBEEncryptedData) { pbe = (PGPPBEEncryptedData) obj; break; } } if (pbe == null) { throw new PgpGeneralException( context.getString(R.string.error_noSymmetricEncryptionPacket)); } updateProgress(progress, R.string.progress_preparingStreams, currentProgress, 100); PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build(); PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( digestCalcProvider).setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build( passphrase.toCharArray()); clear = pbe.getDataStream(decryptorFactory); encryptedData = pbe; currentProgress += 5; } else { if (progress != null) progress.setProgress(R.string.progress_findingKey, currentProgress, 100); PGPPublicKeyEncryptedData pbe = null; PGPSecretKey secretKey = null; Iterator it = enc.getEncryptedDataObjects(); // find secret key while (it.hasNext()) { Object obj = it.next(); if (obj instanceof PGPPublicKeyEncryptedData) { PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID()); if (secretKey != null) { pbe = encData; break; } } } if (secretKey == null) { throw new PgpGeneralException(context.getString(R.string.error_noSecretKeyFound)); } currentProgress += 5; updateProgress(progress, R.string.progress_extractingKey, currentProgress, 100); PGPPrivateKey privateKey = null; try { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); privateKey = secretKey.extractPrivateKey(keyDecryptor); } catch (PGPException e) { throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); } if (privateKey == null) { throw new PgpGeneralException( context.getString(R.string.error_couldNotExtractPrivateKey)); } currentProgress += 5; updateProgress(progress, R.string.progress_preparingStreams, currentProgress, 100); PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); clear = pbe.getDataStream(decryptorFactory); encryptedData = pbe; currentProgress += 5; } PGPObjectFactory plainFact = new PGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); PGPOnePassSignature signature = null; PGPPublicKey signatureKey = null; int signatureIndex = -1; if (dataChunk instanceof PGPCompressedData) { if (progress != null) progress.setProgress(R.string.progress_decompressingData, currentProgress, 100); PGPObjectFactory fact = new PGPObjectFactory( ((PGPCompressedData) dataChunk).getDataStream()); dataChunk = fact.nextObject(); plainFact = fact; currentProgress += 10; } if (dataChunk instanceof PGPOnePassSignatureList) { if (progress != null) progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID()); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } if (signatureKey == null) { signature = null; } else { signatureIndex = i; signatureKeyId = signature.getKeyID(); String userId = null; PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( context, signatureKeyId); if (signKeyRing != null) { userId = PgpHelper.getMainUserId(PgpHelper.getMasterKey(signKeyRing)); } returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); break; } } returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); if (signature != null) { JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); signature.init(contentVerifierBuilderProvider, signatureKey); } else { returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); } dataChunk = plainFact.nextObject(); currentProgress += 10; } if (dataChunk instanceof PGPSignatureList) { dataChunk = plainFact.nextObject(); } if (dataChunk instanceof PGPLiteralData) { if (progress != null) progress.setProgress(R.string.progress_decrypting, currentProgress, 100); PGPLiteralData literalData = (PGPLiteralData) dataChunk; OutputStream out = outStream; byte[] buffer = new byte[1 << 16]; InputStream dataIn = literalData.getInputStream(); int startProgress = currentProgress; int endProgress = 100; if (signature != null) { endProgress = 90; } else if (encryptedData.isIntegrityProtected()) { endProgress = 95; } int n = 0; int done = 0; long startPos = data.getStreamPosition(); while ((n = dataIn.read(buffer)) > 0) { out.write(buffer, 0, n); done += n; if (signature != null) { try { signature.update(buffer, 0, n); } catch (SignatureException e) { returnData .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); signature = null; } } // unknown size, but try to at least have a moving, slowing down progress bar currentProgress = startProgress + (endProgress - startProgress) * done / (done + 100000); if (data.getSize() - startPos == 0) { currentProgress = endProgress; } else { currentProgress = (int) (startProgress + (endProgress - startProgress) * (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); } updateProgress(progress, currentProgress, 100); } if (signature != null) { if (progress != null) progress.setProgress(R.string.progress_verifyingSignature, 90, 100); PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); PGPSignature messageSignature = signatureList.get(signatureIndex); if (signature.verify(messageSignature)) { returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, true); } else { returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); } } } // TODO: add integrity somewhere if (encryptedData.isIntegrityProtected()) { if (progress != null) progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100); if (encryptedData.verify()) { // passed } else { // failed } } else { // no integrity check } updateProgress(progress, R.string.progress_done, 100, 100); return returnData; } public static Bundle verifyText(Context context, ProgressDialogUpdater progress, InputData data, OutputStream outStream, boolean lookupUnknownKey) throws IOException, PgpGeneralException, PGPException, SignatureException { Bundle returnData = new Bundle(); ByteArrayOutputStream out = new ByteArrayOutputStream(); ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); updateProgress(progress, R.string.progress_done, 0, 100); // mostly taken from ClearSignedFileProcessor ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); int lookAhead = readInputLine(lineOut, aIn); byte[] lineSep = getLineSeparator(); byte[] line = lineOut.toByteArray(); out.write(line, 0, getLengthWithoutSeparator(line)); out.write(lineSep); while (lookAhead != -1 && aIn.isClearText()) { lookAhead = readInputLine(lineOut, lookAhead, aIn); line = lineOut.toByteArray(); out.write(line, 0, getLengthWithoutSeparator(line)); out.write(lineSep); } out.close(); byte[] clearText = out.toByteArray(); outStream.write(clearText); returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); updateProgress(progress, R.string.progress_processingSignature, 60, 100); PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); if (sigList == null) { throw new PgpGeneralException(context.getString(R.string.error_corruptData)); } PGPSignature signature = null; long signatureKeyId = 0; PGPPublicKey signatureKey = null; for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID()); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } // if key is not known and we want to lookup unknown ones... if (signatureKey == null && lookupUnknownKey) { returnData = new Bundle(); returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY, true); // return directly now, decrypt will be done again after importing unknown key return returnData; } if (signatureKey == null) { signature = null; } else { signatureKeyId = signature.getKeyID(); String userId = null; PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, signatureKeyId); if (signKeyRing != null) { userId = PgpHelper.getMainUserId(PgpHelper.getMasterKey(signKeyRing)); } returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); break; } } returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); if (signature == null) { returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); if (progress != null) progress.setProgress(R.string.progress_done, 100, 100); return returnData; } JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() .setProvider(BOUNCY_CASTLE_PROVIDER_NAME); signature.init(contentVerifierBuilderProvider, signatureKey); InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); lookAhead = readInputLine(lineOut, sigIn); processLine(signature, lineOut.toByteArray()); if (lookAhead != -1) { do { lookAhead = readInputLine(lineOut, lookAhead, sigIn); signature.update((byte) '\r'); signature.update((byte) '\n'); processLine(signature, lineOut.toByteArray()); } while (lookAhead != -1); } returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, signature.verify()); updateProgress(progress, R.string.progress_done, 100, 100); return returnData; } public static int getStreamContent(Context context, InputStream inStream) throws IOException { InputStream in = PGPUtil.getDecoderStream(inStream); PGPObjectFactory pgpF = new PGPObjectFactory(in); Object object = pgpF.nextObject(); while (object != null) { if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) { return Id.content.keys; } else if (object instanceof PGPEncryptedDataList) { return Id.content.encrypted_data; } object = pgpF.nextObject(); } return Id.content.unknown; } private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { if (pLine == null) { return; } final char[] chars = pLine.toCharArray(); int len = chars.length; while (len > 0) { if (!Character.isWhitespace(chars[len - 1])) { break; } len--; } final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); if (pArmoredOutput != null) { pArmoredOutput.write(data); } pSignatureGenerator.update(data); } private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, final PGPV3SignatureGenerator pSignatureGenerator) throws IOException, SignatureException { if (pLine == null) { return; } final char[] chars = pLine.toCharArray(); int len = chars.length; while (len > 0) { if (!Character.isWhitespace(chars[len - 1])) { break; } len--; } final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); if (pArmoredOutput != null) { pArmoredOutput.write(data); } pSignatureGenerator.update(data); } // taken from ClearSignedFileProcessor in BC private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, IOException { int length = getLengthWithoutWhiteSpace(line); if (length > 0) { sig.update(line, 0, length); } } private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) throws IOException { bOut.reset(); int lookAhead = -1; int ch; while ((ch = fIn.read()) >= 0) { bOut.write(ch); if (ch == '\r' || ch == '\n') { lookAhead = readPassedEOL(bOut, ch, fIn); break; } } return lookAhead; } private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) throws IOException { bOut.reset(); int ch = lookAhead; do { bOut.write(ch); if (ch == '\r' || ch == '\n') { lookAhead = readPassedEOL(bOut, ch, fIn); break; } } while ((ch = fIn.read()) >= 0); if (ch < 0) { lookAhead = -1; } return lookAhead; } private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException { int lookAhead = fIn.read(); if (lastCh == '\r' && lookAhead == '\n') { bOut.write(lookAhead); lookAhead = fIn.read(); } return lookAhead; } private static int getLengthWithoutSeparator(byte[] line) { int end = line.length - 1; while (end >= 0 && isLineEnding(line[end])) { end--; } return end + 1; } private static boolean isLineEnding(byte b) { return b == '\r' || b == '\n'; } private static int getLengthWithoutWhiteSpace(byte[] line) { int end = line.length - 1; while (end >= 0 && isWhiteSpace(line[end])) { end--; } return end + 1; } private static boolean isWhiteSpace(byte b) { return b == '\r' || b == '\n' || b == '\t' || b == ' '; } private static byte[] getLineSeparator() { String nl = System.getProperty("line.separator"); byte[] nlBytes = new byte[nl.length()]; for (int i = 0; i != nlBytes.length; i++) { nlBytes[i] = (byte) nl.charAt(i); } return nlBytes; } public static boolean isReleaseVersion(Context context) { try { PackageInfo pi = context.getPackageManager().getPackageInfo(Constants.PACKAGE_NAME, 0); if (pi.versionCode % 100 == 99) { return true; } else { return false; } } catch (NameNotFoundException e) { // impossible! return false; } } public static String getVersion(Context context) { String version = null; try { PackageInfo pi = context.getPackageManager().getPackageInfo(Constants.PACKAGE_NAME, 0); version = pi.versionName; return version; } catch (NameNotFoundException e) { Log.e(Constants.TAG, "Version could not be retrieved!", e); return "0.0.0"; } } public static String getFullVersion(Context context) { return "OpenPGP Keychain v" + getVersion(context); } /** * Generate a random filename * * @param length * @return */ public static String generateRandomFilename(int length) { SecureRandom random = new SecureRandom(); byte bytes[] = new byte[length]; random.nextBytes(bytes); String result = ""; for (int i = 0; i < length; ++i) { int v = (bytes[i] + 256) % 64; if (v < 10) { result += (char) ('0' + v); } else if (v < 36) { result += (char) ('A' + v - 10); } else if (v < 62) { result += (char) ('a' + v - 36); } else if (v == 62) { result += '_'; } else if (v == 63) { result += '.'; } } return result; } /** * Go once through stream to get length of stream. The length is later used to display progress * when encrypting/decrypting * * @param in * @return * @throws IOException */ public static long getLengthOfStream(InputStream in) throws IOException { long size = 0; long n = 0; byte dummy[] = new byte[0x10000]; while ((n = in.read(dummy)) > 0) { size += n; } return size; } /** * Deletes file securely by overwriting it with random data before deleting it. * * TODO: Does this really help on flash storage? * * @param context * @param progress * @param file * @throws FileNotFoundException * @throws IOException */ public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file) throws FileNotFoundException, IOException { long length = file.length(); SecureRandom random = new SecureRandom(); RandomAccessFile raf = new RandomAccessFile(file, "rws"); raf.seek(0); raf.getFilePointer(); byte[] data = new byte[1 << 16]; int pos = 0; String msg = context.getString(R.string.progress_deletingSecurely, file.getName()); while (pos < length) { if (progress != null) progress.setProgress(msg, (int) (100 * pos / length), 100); random.nextBytes(data); raf.write(data); pos += data.length; } raf.close(); file.delete(); } }