open-keychain/src/org/thialfihar/android/apg/Apg.java
2012-03-09 12:13:28 +01:00

2352 lines
89 KiB
Java

/*
* 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.thialfihar.android.apg;
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.thialfihar.android.apg.KeyServer.AddKeyException;
import org.thialfihar.android.apg.provider.DataProvider;
import org.thialfihar.android.apg.provider.Database;
import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import org.thialfihar.android.apg.ui.widget.KeyEditor;
import org.thialfihar.android.apg.ui.widget.SectionView;
import org.thialfihar.android.apg.ui.widget.UserIdEditor;
import org.thialfihar.android.apg.utils.IterableIterator;
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;
}
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();
}
}