diff options
Diffstat (limited to 'src/org/apg/Apg.java')
-rw-r--r-- | src/org/apg/Apg.java | 2283 |
1 files changed, 2283 insertions, 0 deletions
diff --git a/src/org/apg/Apg.java b/src/org/apg/Apg.java new file mode 100644 index 000000000..d0e54f4d8 --- /dev/null +++ b/src/org/apg/Apg.java @@ -0,0 +1,2283 @@ +/* + * Copyright (C) 2010 Thialfihar <thi@thialfihar.org> + * + * 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.apg; + +import org.apg.KeyServer.AddKeyException; +import org.apg.provider.DataProvider; +import org.apg.provider.Database; +import org.apg.provider.KeyRings; +import org.apg.provider.Keys; +import org.apg.provider.UserIds; +import org.apg.ui.BaseActivity; +import org.apg.ui.widget.KeyEditor; +import org.apg.ui.widget.SectionView; +import org.apg.ui.widget.UserIdEditor; +import org.apg.util.IterableIterator; +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.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.apg.R; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Message; +import android.view.ViewGroup; + +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.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; +import java.util.regex.Pattern; + +public class Apg { + private static final String mApgPackageName = "org.thialfihar.android.apg"; + + public static class Intent { + public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT"; + public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT"; + public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE"; + public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE"; + public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN"; + public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN"; + public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS"; + public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY"; + public static final String IMPORT = "org.thialfihar.android.apg.intent.IMPORT"; + public static final String LOOK_UP_KEY_ID = "org.thialfihar.android.apg.intent.LOOK_UP_KEY_ID"; + public static final String LOOK_UP_KEY_ID_AND_RETURN = "org.thialfihar.android.apg.intent.LOOK_UP_KEY_ID_AND_RETURN"; + public static final String GENERATE_SIGNATURE = "org.thialfihar.android.apg.intent.GENERATE_SIGNATURE"; + public static final String EXPORT_KEY_TO_SERVER = "org.thialfihar.android.apg.intent.EXPORT_KEY_TO_SERVER"; + public static final String IMPORT_FROM_QR_CODE = "org.thialfihar.android.apg.intent.IMPORT_FROM_QR_CODE"; + } + + public static final String EXTRA_TEXT = "text"; + public static final String EXTRA_DATA = "data"; + public static final String EXTRA_ERROR = "error"; + public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; + public static final String EXTRA_DECRYPTED_DATA = "decryptedData"; + public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage"; + public static final String EXTRA_ENCRYPTED_DATA = "encryptedData"; + public static final String EXTRA_RESULT_URI = "resultUri"; + public static final String EXTRA_SIGNATURE = "signature"; + public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; + public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; + public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; + public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; + public static final String EXTRA_SIGNATURE_DATA = "signatureData"; + public static final String EXTRA_SIGNATURE_TEXT = "signatureText"; + public static final String EXTRA_USER_ID = "userId"; + public static final String EXTRA_USER_IDS = "userIds"; + public static final String EXTRA_KEY_ID = "keyId"; + public static final String EXTRA_REPLY_TO = "replyTo"; + public static final String EXTRA_SEND_TO = "sendTo"; + public static final String EXTRA_SUBJECT = "subject"; + public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; + public static final String EXTRA_SELECTION = "selection"; + public static final String EXTRA_ASCII_ARMOUR = "asciiArmour"; + public static final String EXTRA_BINARY = "binary"; + public static final String EXTRA_KEY_SERVERS = "keyServers"; + public static final String EXTRA_EXPECTED_FINGERPRINT = "org.thialfihar.android.apg.EXPECTED_FINGERPRINT"; + + public static final String AUTHORITY = DataProvider.AUTHORITY; + + public static final Uri CONTENT_URI_SECRET_KEY_RINGS = Uri.parse("content://" + AUTHORITY + + "/key_rings/secret/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = Uri.parse("content://" + + AUTHORITY + "/key_rings/secret/key_id/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = Uri.parse("content://" + + AUTHORITY + "/key_rings/secret/emails/"); + + public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = Uri.parse("content://" + AUTHORITY + + "/key_rings/public/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = Uri.parse("content://" + + AUTHORITY + "/key_rings/public/key_id/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = Uri.parse("content://" + + AUTHORITY + "/key_rings/public/emails/"); + + private static String VERSION = null; + + 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 HashMap<Long, CachedPassPhrase> mPassPhraseCache = new HashMap<Long, CachedPassPhrase>(); + private static String mEditPassPhrase = null; + + private static Database mDatabase = null; + + public static class GeneralException extends Exception { + static final long serialVersionUID = 0xf812773342L; + + public GeneralException(String message) { + super(message); + } + } + + public static class NoAsymmetricEncryptionException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public NoAsymmetricEncryptionException() { + super(); + } + } + + public static void initialize(Context context) { + if (mDatabase == null) { + mDatabase = new Database(context); + } + } + + public static Database getDatabase() { + return mDatabase; + } + + public static void setEditPassPhrase(String passPhrase) { + mEditPassPhrase = passPhrase; + } + + public static String getEditPassPhrase() { + return mEditPassPhrase; + } + + public static void setCachedPassPhrase(long keyId, String passPhrase) { + mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase)); + } + + public static String getCachedPassPhrase(long keyId) { + long realId = keyId; + if (realId != Id.key.symmetric) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + PGPSecretKey masterKey = getMasterKey(keyRing); + if (masterKey == null) { + return null; + } + realId = masterKey.getKeyID(); + } + CachedPassPhrase cpp = mPassPhraseCache.get(realId); + if (cpp == null) { + return null; + } + // set it again to reset the cache life cycle + setCachedPassPhrase(realId, cpp.passPhrase); + return cpp.passPhrase; + } + + public static int cleanUpCache(int ttl, int initialDelay) { + int delay = initialDelay; + long realTtl = ttl * 1000; + long now = new Date().getTime(); + Vector<Long> oldKeys = new Vector<Long>(); + for (Map.Entry<Long, CachedPassPhrase> pair : mPassPhraseCache.entrySet()) { + long lived = now - pair.getValue().timestamp; + if (lived >= realTtl) { + oldKeys.add(pair.getKey()); + } else { + // see, whether the remaining time for this cache entry improves our + // check delay + long nextCheck = realTtl - lived + 1000; + if (nextCheck < delay) { + delay = (int) nextCheck; + } + } + } + + for (long keyId : oldKeys) { + mPassPhraseCache.remove(keyId); + } + + return delay; + } + + public static PGPSecretKey createKey(Context context, int algorithmChoice, int keySize, + String passPhrase, PGPSecretKey masterKey) throws NoSuchAlgorithmException, + PGPException, NoSuchProviderException, GeneralException, + InvalidAlgorithmParameterException { + + if (keySize < 512) { + throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit)); + } + + Security.addProvider(new BouncyCastleProvider()); + + if (passPhrase == null) { + passPhrase = ""; + } + + int algorithm = 0; + KeyPairGenerator keyGen = null; + + switch (algorithmChoice) { + case Id.choice.algorithm.dsa: { + keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + algorithm = PGPPublicKey.DSA; + break; + } + + case Id.choice.algorithm.elgamal: { + if (masterKey == null) { + throw new GeneralException( + context.getString(R.string.error_masterKeyMustNotBeElGamal)); + } + keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider()); + 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", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + + algorithm = PGPPublicKey.RSA_GENERAL; + break; + } + + default: { + throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); + } + } + + PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); + + PGPSecretKey secretKey = null; + if (masterKey == null) { + // enough for now, as we assemble the key again later anyway + secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "", + PGPEncryptedData.CAST5, passPhrase.toCharArray(), null, null, + new SecureRandom(), new BouncyCastleProvider().getName()); + + } else { + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + PGPKeyRingGenerator ringGen = new PGPKeyRingGenerator( + PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, "", PGPEncryptedData.CAST5, + passPhrase.toCharArray(), null, null, new SecureRandom(), + new BouncyCastleProvider().getName()); + ringGen.addSubKey(keyPair); + PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); + Iterator<PGPSecretKey> it = secKeyRing.getSecretKeys(); + // first one is the master key + it.next(); + secretKey = it.next(); + } + + return secretKey; + } + + private static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { + GregorianCalendar tmp = new GregorianCalendar(); + tmp.setTime(first.getTime()); + long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; + tmp.add(Calendar.DAY_OF_MONTH, (int) numDays); + while (tmp.before(second)) { + tmp.add(Calendar.DAY_OF_MONTH, 1); + ++numDays; + } + return numDays; + } + + public static void buildSecretKey(Activity context, SectionView userIdsView, + SectionView keysView, String oldPassPhrase, String newPassPhrase, + ProgressDialogUpdater progress) throws Apg.GeneralException, NoSuchProviderException, + PGPException, NoSuchAlgorithmException, SignatureException, IOException, + Database.GeneralException { + + if (progress != null) + progress.setProgress(R.string.progress_buildingKey, 0, 100); + + Security.addProvider(new BouncyCastleProvider()); + + if (oldPassPhrase == null || oldPassPhrase.equals("")) { + oldPassPhrase = ""; + } + + if (newPassPhrase == null || newPassPhrase.equals("")) { + newPassPhrase = ""; + } + + Vector<String> userIds = new Vector<String>(); + Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>(); + + ViewGroup userIdEditors = userIdsView.getEditors(); + ViewGroup keyEditors = keysView.getEditors(); + + boolean gotMainUserId = false; + for (int i = 0; i < userIdEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); + String userId = null; + try { + userId = editor.getValue(); + } catch (UserIdEditor.NoNameException e) { + throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName)); + } catch (UserIdEditor.NoEmailException e) { + throw new Apg.GeneralException( + context.getString(R.string.error_userIdNeedsAnEmailAddress)); + } catch (UserIdEditor.InvalidEmailException e) { + throw new Apg.GeneralException("" + e); + } + + if (userId.equals("")) { + continue; + } + + if (editor.isMainUserId()) { + userIds.insertElementAt(userId, 0); + gotMainUserId = true; + } else { + userIds.add(userId); + } + } + + if (userIds.size() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId)); + } + + if (!gotMainUserId) { + throw new Apg.GeneralException( + context.getString(R.string.error_mainUserIdMustNotBeEmpty)); + } + + if (keyEditors.getChildCount() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey)); + } + + for (int i = 0; i < keyEditors.getChildCount(); ++i) { + KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i); + keys.add(editor.getValue()); + } + + if (progress != null) + progress.setProgress(R.string.progress_preparingMasterKey, 10, 100); + KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0); + int usageId = keyEditor.getUsage(); + 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); + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + + if (progress != null) + progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100); + for (int i = 0; i < userIds.size(); ++i) { + String userId = userIds.get(i); + + PGPSignatureGenerator sGen = new PGPSignatureGenerator(masterPublicKey.getAlgorithm(), + HashAlgorithmTags.SHA1, new BouncyCastleProvider()); + + sGen.initSign(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 + + 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 + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = getNumDaysBetween(creationDate, expiryDate); + if (numDays <= 0) { + throw new GeneralException( + context.getString(R.string.error_expiryMustComeAfterCreation)); + } + hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); + } + + if (progress != null) + progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100); + PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, mainUserId, PGPEncryptedData.CAST5, newPassPhrase.toCharArray(), + hashedPacketsGen.generate(), unhashedPacketsGen.generate(), new SecureRandom(), + new BouncyCastleProvider().getName()); + + if (progress != null) + progress.setProgress(R.string.progress_addingSubKeys, 40, 100); + for (int i = 1; i < keys.size(); ++i) { + if (progress != null) + progress.setProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100); + PGPSecretKey subKey = keys.get(i); + keyEditor = (KeyEditor) keyEditors.getChildAt(i); + PGPPublicKey subPublicKey = subKey.getPublicKey(); + PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey.getAlgorithm(), + subPublicKey.getKey(new BouncyCastleProvider()), subPrivateKey.getKey(), + subPublicKey.getCreationTime()); + + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + keyFlags = 0; + usageId = keyEditor.getUsage(); + 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 + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = 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(); + + if (progress != null) + progress.setProgress(R.string.progress_savingKeyRing, 90, 100); + mDatabase.saveKeyRing(secretKeyRing); + mDatabase.saveKeyRing(publicKeyRing); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static PGPKeyRing decodeKeyRing(InputStream is) throws IOException { + InputStream in = PGPUtil.getDecoderStream(is); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); + Object obj = objectFactory.nextObject(); + + if (obj instanceof PGPKeyRing) { + return (PGPKeyRing) obj; + } + + return null; + } + + public static int storeKeyRingInCache(PGPKeyRing keyring) { + int status = Integer.MIN_VALUE; // out of bounds value (Id.retrun_value.*) + try { + if (keyring instanceof PGPSecretKeyRing) { + PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring; + boolean save = true; + try { + PGPPrivateKey testKey = secretKeyRing.getSecretKey().extractPrivateKey( + new char[] {}, new BouncyCastleProvider()); + if (testKey == null) { + // this is bad, something is very wrong... likely a --export-secret-subkeys + // export + save = false; + status = Id.return_value.bad; + } + } catch (PGPException e) { + // all good if this fails, we likely didn't use the right password + } + + if (save) { + status = mDatabase.saveKeyRing(secretKeyRing); + } + } else if (keyring instanceof PGPPublicKeyRing) { + PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; + status = mDatabase.saveKeyRing(publicKeyRing); + } + } catch (IOException e) { + status = Id.return_value.error; + } catch (Database.GeneralException 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(Activity context, int type, InputData data, + ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException, + PGPException, IOException { + Bundle returnData = new Bundle(); + + if (type == Id.type.secret_key) { + if (progress != null) + progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); + } else { + if (progress != null) + progress.setProgress(R.string.progress_importingPublicKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(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 { + PGPKeyRing keyring = decodeKeyRing(bufferedInput); + while (keyring != null) { + int status = Integer.MIN_VALUE; // out of bounds value + + // if this key is what we expect it to be, save it + if ((type == Id.type.secret_key && keyring instanceof PGPSecretKeyRing) + || (type == Id.type.public_key && keyring instanceof PGPPublicKeyRing)) { + status = storeKeyRingInCache(keyring); + } + + if (status == Id.return_value.error) { + throw new GeneralException(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; + } + + if (progress != null) { + progress.setProgress((int) (100 * progressIn.position() / data.getSize()), 100); + } + // TODO: needed? + // obj = objectFactory.nextObject(); + + keyring = decodeKeyRing(bufferedInput); + } + } catch (EOFException e) { + // nothing to do, we are done + } + + returnData.putInt("added", newKeys); + returnData.putInt("updated", oldKeys); + returnData.putInt("bad", badKeys); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Bundle exportKeyRings(Activity context, Vector<Integer> keyRingIds, + OutputStream outStream, ProgressDialogUpdater progress) throws GeneralException, + FileNotFoundException, PGPException, IOException { + Bundle returnData = new Bundle(); + + if (keyRingIds.size() == 1) { + if (progress != null) + progress.setProgress(R.string.progress_exportingKey, 0, 100); + } else { + if (progress != null) + progress.setProgress(R.string.progress_exportingKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + ArmoredOutputStream out = new ArmoredOutputStream(outStream); + + int numKeys = 0; + for (int i = 0; i < keyRingIds.size(); ++i) { + if (progress != null) + progress.setProgress(i * 100 / keyRingIds.size(), 100); + Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); + PGPPublicKeyRing publicKeyRing; + PGPSecretKeyRing secretKeyRing; + + if (obj instanceof PGPSecretKeyRing) { + secretKeyRing = (PGPSecretKeyRing) obj; + secretKeyRing.encode(out); + } else if (obj instanceof PGPPublicKeyRing) { + publicKeyRing = (PGPPublicKeyRing) obj; + publicKeyRing.encode(out); + } else { + continue; + } + ++numKeys; + } + out.close(); + returnData.putInt("exported", numKeys); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Date getCreationDate(PGPPublicKey key) { + return key.getCreationTime(); + } + + public static Date getCreationDate(PGPSecretKey key) { + return key.getPublicKey().getCreationTime(); + } + + @SuppressWarnings("unchecked") + public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static Vector<PGPPublicKey> getEncryptKeys(PGPPublicKeyRing keyRing) { + Vector<PGPPublicKey> encryptKeys = new Vector<PGPPublicKey>(); + + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) { + if (isEncryptionKey(key)) { + encryptKeys.add(key); + } + } + + return encryptKeys; + } + + @SuppressWarnings("unchecked") + public static Vector<PGPSecretKey> getSigningKeys(PGPSecretKeyRing keyRing) { + Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>(); + + for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { + if (isSigningKey(key)) { + signingKeys.add(key); + } + } + + return signingKeys; + } + + public static Vector<PGPPublicKey> getUsableEncryptKeys(PGPPublicKeyRing keyRing) { + Vector<PGPPublicKey> usableKeys = new Vector<PGPPublicKey>(); + Vector<PGPPublicKey> encryptKeys = getEncryptKeys(keyRing); + PGPPublicKey masterKey = null; + for (int i = 0; i < encryptKeys.size(); ++i) { + PGPPublicKey key = encryptKeys.get(i); + if (!isExpired(key)) { + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static boolean isExpired(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + Date expiryDate = getExpiryDate(key); + Date now = new Date(); + if (now.compareTo(creationDate) >= 0 + && (expiryDate == null || now.compareTo(expiryDate) <= 0)) { + return false; + } + return true; + } + + public static boolean isExpired(PGPSecretKey key) { + return isExpired(key.getPublicKey()); + } + + public static Vector<PGPSecretKey> getUsableSigningKeys(PGPSecretKeyRing keyRing) { + Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>(); + Vector<PGPSecretKey> signingKeys = getSigningKeys(keyRing); + PGPSecretKey masterKey = null; + for (int i = 0; i < signingKeys.size(); ++i) { + PGPSecretKey key = signingKeys.get(i); + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static Date getExpiryDate(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + if (key.getValidDays() == 0) { + // no expiry + return null; + } + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.DATE, key.getValidDays()); + Date expiryDate = calendar.getTime(); + + return expiryDate; + } + + public static Date getExpiryDate(PGPSecretKey key) { + return getExpiryDate(key.getPublicKey()); + } + + public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing); + if (encryptKeys.size() == 0) { + return null; + } + return encryptKeys.get(0); + } + + public static PGPSecretKey getSigningKey(long masterKeyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector<PGPSecretKey> signingKeys = getUsableSigningKeys(keyRing); + if (signingKeys.size() == 0) { + return null; + } + return signingKeys.get(0); + } + + @SuppressWarnings("unchecked") + public static String getMainUserId(PGPPublicKey key) { + for (String userId : new IterableIterator<String>(key.getUserIDs())) { + return userId; + } + return null; + } + + @SuppressWarnings("unchecked") + public static String getMainUserId(PGPSecretKey key) { + for (String userId : new IterableIterator<String>(key.getUserIDs())) { + return userId; + } + return null; + } + + public static String getMainUserIdSafe(Context context, PGPPublicKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + public static String getMainUserIdSafe(Context context, PGPSecretKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + @SuppressWarnings("unchecked") + public static boolean isEncryptionKey(PGPPublicKey key) { + if (!key.isEncryptionKey()) { + return false; + } + + if (key.getVersion() <= 3) { + // this must be true now + return key.isEncryptionKey(); + } + + // special cases + if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { + return true; + } + + if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { + return true; + } + + for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null + && (hashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null + && (unhashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + } + return false; + } + + public static boolean isEncryptionKey(PGPSecretKey key) { + return isEncryptionKey(key.getPublicKey()); + } + + @SuppressWarnings("unchecked") + public static boolean isSigningKey(PGPPublicKey key) { + if (key.getVersion() <= 3) { + return true; + } + + // special case + if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) { + return true; + } + + for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + } + + return false; + } + + public static boolean isSigningKey(PGPSecretKey key) { + return isSigningKey(key.getPublicKey()); + } + + public static String getAlgorithmInfo(PGPPublicKey key) { + return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); + } + + public static String getAlgorithmInfo(PGPSecretKey key) { + return getAlgorithmInfo(key.getPublicKey()); + } + + public static String getAlgorithmInfo(int algorithm, int keySize) { + String algorithmStr = null; + + switch (algorithm) { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: { + algorithmStr = "RSA"; + break; + } + + case PGPPublicKey.DSA: { + algorithmStr = "DSA"; + break; + } + + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: { + algorithmStr = "ElGamal"; + break; + } + + default: { + algorithmStr = "???"; + break; + } + } + return algorithmStr + ", " + keySize + "bit"; + } + + public static String convertToHex(byte[] fp) { + String fingerPrint = ""; + for (int i = 0; i < fp.length; ++i) { + if (i != 0 && i % 10 == 0) { + fingerPrint += " "; + } else if (i != 0 && i % 2 == 0) { + fingerPrint += " "; + } + String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(); + while (chunk.length() < 2) { + chunk = "0" + chunk; + } + fingerPrint += chunk; + } + + return fingerPrint; + + } + + public static String getFingerPrint(long keyId) { + PGPPublicKey key = Apg.getPublicKey(keyId); + if (key == null) { + PGPSecretKey secretKey = Apg.getSecretKey(keyId); + if (secretKey == null) { + return ""; + } + key = secretKey.getPublicKey(); + } + + return convertToHex(key.getFingerprint()); + } + + public static String getSmallFingerPrint(long keyId) { + String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(); + while (fingerPrint.length() < 8) { + fingerPrint = "0" + fingerPrint; + } + return fingerPrint; + } + + public static String keyToHex(long keyId) { + return getSmallFingerPrint(keyId >> 32) + getSmallFingerPrint(keyId); + } + + public static long keyFromHex(String data) { + int len = data.length(); + String s2 = data.substring(len - 8); + String s1 = data.substring(0, len - 8); + return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); + } + + public static void deleteKey(int keyRingId) { + mDatabase.deleteKeyRing(keyRingId); + } + + public static PGPKeyRing getKeyRing(int keyRingId) { + return (PGPKeyRing) mDatabase.getKeyRing(keyRingId); + } + + public static PGPSecretKeyRing getSecretKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); + if (data == null) { + return null; + } + try { + return new PGPSecretKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } catch (PGPException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPPublicKeyRing getPublicKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); + if (data == null) { + return null; + } + try { + return new PGPPublicKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPSecretKey getSecretKey(long keyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + return keyRing.getSecretKey(keyId); + } + + public static PGPPublicKey getPublicKey(long keyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); + if (keyRing == null) { + return null; + } + + return keyRing.getPublicKey(keyId); + } + + public static Vector<Integer> getKeyRingIds(int type) { + SQLiteDatabase db = mDatabase.db(); + Vector<Integer> keyIds = new Vector<Integer>(); + Cursor c = db.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, KeyRings.TYPE + + " = ?", new String[] { "" + type }, null, null, null); + if (c != null && c.moveToFirst()) { + do { + keyIds.add(c.getInt(0)); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return keyIds; + } + + public static String getMainUserId(long keyId, int type) { + SQLiteDatabase db = mDatabase.db(); + Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + ") " + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + "masterKey." + + Keys.KEY_RING_ID + " AND " + "masterKey." + Keys.IS_MASTER_KEY + " = '1') " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " = " + "masterKey." + Keys._ID + " AND " + UserIds.TABLE_NAME + + "." + UserIds.RANK + " = '0')", new String[] { UserIds.USER_ID }, Keys.TABLE_NAME + + "." + Keys.KEY_ID + " = ? AND " + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null); + String userId = ""; + if (c != null && c.moveToFirst()) { + do { + userId = c.getString(0); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return userId; + } + + public static void encrypt(Context context, InputData data, OutputStream outStream, + boolean armored, long encryptionKeyIds[], long signatureKeyId, + String signaturePassPhrase, ProgressDialogUpdater progress, int symmetricAlgorithm, + int hashAlgorithm, int compression, boolean forceV3Signature, String passPhrase) + throws IOException, GeneralException, PGPException, NoSuchProviderException, + NoSuchAlgorithmException, SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + if (encryptionKeyIds == null) { + encryptionKeyIds = new long[0]; + } + + ArmoredOutputStream armorOut = null; + OutputStream out = null; + OutputStream encryptOut = 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 (encryptionKeyIds.length == 0 && passPhrase == null) { + throw new GeneralException( + context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); + } + + if (signatureKeyId != 0) { + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + if (progress != null) + progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100); + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException( + context.getString(R.string.error_couldNotExtractPrivateKey)); + } + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 5, 100); + + // encrypt and compress input file content + PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(symmetricAlgorithm, true, + new SecureRandom(), new BouncyCastleProvider()); + + if (encryptionKeyIds.length == 0) { + // symmetric encryption + cPk.addMethod(passPhrase.toCharArray()); + } + for (int i = 0; i < encryptionKeyIds.length; ++i) { + PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]); + if (key != null) { + cPk.addMethod(key); + } + } + encryptOut = cPk.open(out, new byte[1 << 16]); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + if (signatureKeyId != 0) { + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 10, 100); + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + + String userId = getMainUserId(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 != 0) { + if (forceV3Signature) { + 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]); + if (progress != null) + progress.setProgress(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 != 0) { + if (forceV3Signature) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } + } + done += n; + if (data.getSize() != 0) { + if (progress != null) + progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100); + } + } + + literalGen.close(); + + if (signatureKeyId != 0) { + if (progress != null) + progress.setProgress(R.string.progress_generatingSignature, 95, 100); + if (forceV3Signature) { + signatureV3Generator.generate().encode(pOut); + } else { + signatureGenerator.generate().encode(pOut); + } + } + if (compressGen != null) { + compressGen.close(); + } + encryptOut.close(); + if (armored) { + armorOut.close(); + } + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static void signText(Context context, InputData data, OutputStream outStream, + long signatureKeyId, String signaturePassPhrase, int hashAlgorithm, + boolean forceV3Signature, ProgressDialogUpdater progress) throws GeneralException, + PGPException, IOException, NoSuchAlgorithmException, SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (signatureKeyId == 0) { + throw new GeneralException(context.getString(R.string.error_noSignatureKey)); + } + + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 0, 100); + + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 30, 100); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator + .initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator( + signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + if (progress != null) + progress.setProgress(R.string.progress_signing, 40, 100); + + armorOut.beginClearText(hashAlgorithm); + + 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(); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static void generateSignature(Context context, InputData data, OutputStream outStream, + boolean armored, boolean binary, long signatureKeyId, String signaturePassPhrase, + int hashAlgorithm, boolean forceV3Signature, ProgressDialogUpdater progress) + throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, + SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + ArmoredOutputStream armorOut = null; + OutputStream out = 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 GeneralException(context.getString(R.string.error_noSignatureKey)); + } + + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 0, 100); + + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 30, 100); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; + if (binary) { + type = PGPSignature.BINARY_DOCUMENT; + } + + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator.initSign(type, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator( + signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(type, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + if (progress != null) + progress.setProgress(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 long getDecryptionKeyId(Context context, InputData data) throws GeneralException, + NoAsymmetricEncryptionException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + 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 GeneralException(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 = getSecretKey(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, InputData data) + throws GeneralException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + 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 GeneralException(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 decrypt(Context context, InputData data, OutputStream outStream, + String passPhrase, ProgressDialogUpdater progress, boolean assumeSymmetric) + throws IOException, GeneralException, 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 GeneralException(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 GeneralException( + context.getString(R.string.error_noSymmetricEncryptionPacket)); + } + + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider()); + 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 = getSecretKey(encData.getKeyID()); + if (secretKey != null) { + pbe = encData; + break; + } + } + } + + if (secretKey == null) { + throw new GeneralException(context.getString(R.string.error_noSecretKeyFound)); + } + + currentProgress += 5; + if (progress != null) + progress.setProgress(R.string.progress_extractingKey, currentProgress, 100); + PGPPrivateKey privateKey = null; + try { + privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + } catch (PGPException e) { + throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); + } + if (privateKey == null) { + throw new GeneralException( + context.getString(R.string.error_couldNotExtractPrivateKey)); + } + currentProgress += 5; + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(privateKey, new BouncyCastleProvider()); + 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(EXTRA_SIGNATURE, true); + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureIndex = i; + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature != null) { + signature.initVerify(signatureKey, new BouncyCastleProvider()); + } else { + returnData.putBoolean(EXTRA_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(EXTRA_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)); + } + if (progress != null) + progress.setProgress(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(EXTRA_SIGNATURE_SUCCESS, true); + } else { + returnData.putBoolean(EXTRA_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 + } + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public static Bundle verifyText(BaseActivity context, InputData data, OutputStream outStream, + ProgressDialogUpdater progress) throws IOException, GeneralException, PGPException, + SignatureException { + Bundle returnData = new Bundle(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); + + if (progress != null) + progress.setProgress(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(EXTRA_SIGNATURE, true); + + if (progress != null) + progress.setProgress(R.string.progress_processingSignature, 60, 100); + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + + PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); + if (sigList == null) { + throw new GeneralException(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 = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + Bundle pauseData = new Bundle(); + pauseData.putInt(Constants.extras.status, Id.message.unknown_signature_key); + pauseData.putLong(Constants.extras.key_id, signatureKeyId); + Message msg = new Message(); + msg.setData(pauseData); + context.sendMessage(msg); + // pause here + context.getRunningThread().pause(); + // see whether the key was found in the meantime + signatureKey = getPublicKey(signature.getKeyID()); + } + + if (signatureKey == null) { + signature = null; + } else { + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature == null) { + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + signature.initVerify(signatureKey, new BouncyCastleProvider()); + + 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(EXTRA_SIGNATURE_SUCCESS, signature.verify()); + + if (progress != null) + progress.setProgress(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(mApgPackageName, 0); + if (pi.versionCode % 100 == 99) { + return true; + } else { + return false; + } + } catch (NameNotFoundException e) { + // unpossible! + return false; + } + } + + public static String getVersion(Context context) { + if (VERSION != null) { + return VERSION; + } + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0); + VERSION = pi.versionName; + return VERSION; + } catch (NameNotFoundException e) { + // unpossible! + return "0.0.0"; + } + } + + public static String getFullVersion(Context context) { + return "APG v" + getVersion(context); + } + + public static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + /* + * try { random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); } catch + * (NoSuchAlgorithmException e) { // TODO: need to handle this case somehow return null; } + */ + 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; + } + + 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; + } + + public static void deleteFileSecurely(Context context, File file, ProgressDialogUpdater progress) + 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(); + } +} |