New Gradle project structure
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
public class PgpConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from byte[] to PGPKeyRing
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static PGPKeyRing BytesToPGPKeyRing(byte[] keysBytes) {
|
||||
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
|
||||
}
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from byte[] to ArrayList<PGPSecretKey>
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
|
||||
PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes);
|
||||
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
|
||||
while (itr.hasNext()) {
|
||||
keys.add(itr.next());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from byte[] to PGPSecretKey
|
||||
*
|
||||
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
|
||||
PGPSecretKey key = BytesToPGPSecretKeyList(keyBytes).get(0);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from ArrayList<PGPSecretKey> to byte[]
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyArrayListToBytes(ArrayList<PGPSecretKey> keys) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (PGPSecretKey key : keys) {
|
||||
try {
|
||||
key.encode(os);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting ArrayList<PGPSecretKey> to byte[]!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKey to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
|
||||
try {
|
||||
return key.getEncoded();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Encoding failed", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKeyRing to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {
|
||||
try {
|
||||
return keyRing.getEncoded();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Encoding failed", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
public class PgpHelper {
|
||||
|
||||
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);
|
||||
|
||||
public static String getVersion(Context context) {
|
||||
String version = null;
|
||||
try {
|
||||
PackageInfo pi = context.getPackageManager().getPackageInfo(Constants.PACKAGE_NAME, 0);
|
||||
version = pi.versionName;
|
||||
return version;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Version could not be retrieved!", e);
|
||||
return "0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFullVersion(Context context) {
|
||||
return "OpenPGP Keychain v" + getVersion(context);
|
||||
}
|
||||
|
||||
public static long getDecryptionKeyId(Context context, InputStream inputStream)
|
||||
throws PgpGeneralException, NoAsymmetricEncryptionException, IOException {
|
||||
InputStream in = PGPUtil.getDecoderStream(inputStream);
|
||||
PGPObjectFactory pgpF = new PGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
// the first object might be a PGP marker packet.
|
||||
if (o instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) o;
|
||||
} else {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
|
||||
if (enc == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
|
||||
}
|
||||
|
||||
// TODO: currently we always only look at the first known key
|
||||
// find the secret key
|
||||
PGPSecretKey secretKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
boolean gotAsymmetricEncryption = false;
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||
gotAsymmetricEncryption = true;
|
||||
PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj;
|
||||
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, pbe.getKeyID());
|
||||
if (secretKey != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotAsymmetricEncryption) {
|
||||
throw new NoAsymmetricEncryptionException();
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
return Id.key.none;
|
||||
}
|
||||
|
||||
return secretKey.getKeyID();
|
||||
}
|
||||
|
||||
public static 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random filename
|
||||
*
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
public static String generateRandomFilename(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
byte bytes[] = new byte[length];
|
||||
random.nextBytes(bytes);
|
||||
String result = "";
|
||||
for (int i = 0; i < length; ++i) {
|
||||
int v = (bytes[i] + 256) % 64;
|
||||
if (v < 10) {
|
||||
result += (char) ('0' + v);
|
||||
} else if (v < 36) {
|
||||
result += (char) ('A' + v - 10);
|
||||
} else if (v < 62) {
|
||||
result += (char) ('a' + v - 36);
|
||||
} else if (v == 62) {
|
||||
result += '_';
|
||||
} else if (v == 63) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go once through stream to get length of stream. The length is later used to display progress
|
||||
* when encrypting/decrypting
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static long getLengthOfStream(InputStream in) throws IOException {
|
||||
long size = 0;
|
||||
long n = 0;
|
||||
byte dummy[] = new byte[0x10000];
|
||||
while ((n = in.read(dummy)) > 0) {
|
||||
size += n;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes file securely by overwriting it with random data before deleting it.
|
||||
*
|
||||
* TODO: Does this really help on flash storage?
|
||||
*
|
||||
* @param context
|
||||
* @param progress
|
||||
* @param file
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
|
||||
throws FileNotFoundException, IOException {
|
||||
long length = file.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "rws");
|
||||
raf.seek(0);
|
||||
raf.getFilePointer();
|
||||
byte[] data = new byte[1 << 16];
|
||||
int pos = 0;
|
||||
String msg = context.getString(R.string.progress_deleting_securely, 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
|
||||
public class PgpImportExport {
|
||||
private Context mContext;
|
||||
private ProgressDialogUpdater mProgress;
|
||||
|
||||
public PgpImportExport(Context context, ProgressDialogUpdater progress) {
|
||||
super();
|
||||
this.mContext = context;
|
||||
this.mProgress = progress;
|
||||
}
|
||||
|
||||
public void updateProgress(int message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(String message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports keys from given data. If keyIds is given only those are imported
|
||||
*
|
||||
* @param data
|
||||
* @param keyIds
|
||||
* @return
|
||||
* @throws PgpGeneralException
|
||||
* @throws FileNotFoundException
|
||||
* @throws PGPException
|
||||
* @throws IOException
|
||||
*/
|
||||
public Bundle importKeyRings(InputData data, ArrayList<Long> keyIds)
|
||||
throws PgpGeneralException, FileNotFoundException, PGPException, IOException {
|
||||
Bundle returnData = new Bundle();
|
||||
|
||||
updateProgress(R.string.progress_importing_secret_keys, 0, 100);
|
||||
|
||||
PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream());
|
||||
|
||||
// need to have access to the bufferedInput, so we can reuse it for the possible
|
||||
// PGPObject chunks after the first one, e.g. files with several consecutive ASCII
|
||||
// armour blocks
|
||||
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
|
||||
int newKeys = 0;
|
||||
int oldKeys = 0;
|
||||
int badKeys = 0;
|
||||
try {
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
PGPKeyRing keyring = (PGPKeyRing) obj;
|
||||
|
||||
int status = Integer.MIN_VALUE; // out of bounds value
|
||||
|
||||
if (keyIds != null) {
|
||||
if (keyIds.contains(keyring.getPublicKey().getKeyID())) {
|
||||
status = storeKeyRingInCache(keyring);
|
||||
} else {
|
||||
Log.d(Constants.TAG, "not selected! key id: "
|
||||
+ keyring.getPublicKey().getKeyID());
|
||||
}
|
||||
} else {
|
||||
status = storeKeyRingInCache(keyring);
|
||||
}
|
||||
|
||||
if (status == Id.return_value.error) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_saving_keys));
|
||||
}
|
||||
|
||||
// update the counts to display to the user at the end
|
||||
if (status == Id.return_value.updated) {
|
||||
++oldKeys;
|
||||
} else if (status == Id.return_value.ok) {
|
||||
++newKeys;
|
||||
} else if (status == Id.return_value.bad) {
|
||||
++badKeys;
|
||||
}
|
||||
|
||||
updateProgress((int) (100 * progressIn.position() / data.getSize()), 100);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception on parsing key file!", e);
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys);
|
||||
returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys);
|
||||
returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType,
|
||||
OutputStream outStream) throws PgpGeneralException, FileNotFoundException,
|
||||
PGPException, IOException {
|
||||
Bundle returnData = new Bundle();
|
||||
|
||||
updateProgress(
|
||||
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
|
||||
keyRingMasterKeyIds.size()), 0, 100);
|
||||
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
|
||||
// export public keyrings...
|
||||
ArmoredOutputStream outPub = new ArmoredOutputStream(outStream);
|
||||
outPub.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||
|
||||
int numKeys = 0;
|
||||
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
|
||||
// double the needed time if exporting both public and secret parts
|
||||
if (keyType == Id.type.secret_key) {
|
||||
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
|
||||
} else {
|
||||
updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100);
|
||||
}
|
||||
|
||||
PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(
|
||||
mContext, keyRingMasterKeyIds.get(i));
|
||||
|
||||
if (publicKeyRing != null) {
|
||||
publicKeyRing.encode(outPub);
|
||||
}
|
||||
++numKeys;
|
||||
}
|
||||
outPub.close();
|
||||
|
||||
// if we export secret keyrings, append all secret parts after the public parts
|
||||
if (keyType == Id.type.secret_key) {
|
||||
ArmoredOutputStream outSec = new ArmoredOutputStream(outStream);
|
||||
outSec.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||
|
||||
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
|
||||
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
|
||||
|
||||
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
||||
mContext, keyRingMasterKeyIds.get(i));
|
||||
|
||||
if (secretKeyRing != null) {
|
||||
secretKeyRing.encode(outSec);
|
||||
}
|
||||
}
|
||||
outSec.close();
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_EXPORT, numKeys);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: implement Id.return_value.updated as status when key already existed
|
||||
*
|
||||
* @param context
|
||||
* @param keyring
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public int storeKeyRingInCache(PGPKeyRing keyring) {
|
||||
int status = Integer.MIN_VALUE; // out of bounds value (Id.return_value.*)
|
||||
try {
|
||||
if (keyring instanceof PGPSecretKeyRing) {
|
||||
PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring;
|
||||
boolean save = true;
|
||||
|
||||
for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>(
|
||||
secretKeyRing.getSecretKeys())) {
|
||||
if (!testSecretKey.isMasterKey()) {
|
||||
if (PgpKeyHelper.isSecretKeyPrivateEmpty(testSecretKey)) {
|
||||
// this is bad, something is very wrong...
|
||||
save = false;
|
||||
status = Id.return_value.bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (save) {
|
||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||
// TODO: preserve certifications
|
||||
// (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?)
|
||||
PGPPublicKeyRing newPubRing = null;
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(
|
||||
secretKeyRing.getPublicKeys())) {
|
||||
if (newPubRing == null) {
|
||||
newPubRing = new PGPPublicKeyRing(key.getEncoded(),
|
||||
new JcaKeyFingerprintCalculator());
|
||||
}
|
||||
newPubRing = PGPPublicKeyRing.insertPublicKey(newPubRing, key);
|
||||
}
|
||||
if (newPubRing != null)
|
||||
ProviderHelper.saveKeyRing(mContext, newPubRing);
|
||||
// TODO: remove status returns, use exceptions!
|
||||
status = Id.return_value.ok;
|
||||
}
|
||||
} else if (keyring instanceof PGPPublicKeyRing) {
|
||||
PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
|
||||
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
|
||||
// TODO: remove status returns, use exceptions!
|
||||
status = Id.return_value.ok;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
status = Id.return_value.error;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class PgpKeyHelper {
|
||||
|
||||
/**
|
||||
* Returns the last 9 chars of a fingerprint
|
||||
*
|
||||
* @param fingerprint
|
||||
* String containing short or long fingerprint
|
||||
* @return
|
||||
*/
|
||||
public static String shortifyFingerprint(String fingerprint) {
|
||||
return fingerprint.substring(41);
|
||||
}
|
||||
|
||||
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 PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) {
|
||||
long cnt = 0;
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
if (cnt == num) {
|
||||
return key;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Vector<PGPSecretKey> getCertificationKeys(PGPSecretKeyRing keyRing) {
|
||||
Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>();
|
||||
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
if (isCertificationKey(key)) {
|
||||
signingKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return signingKeys;
|
||||
}
|
||||
|
||||
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) && !key.isRevoked()) {
|
||||
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> getUsableCertificationKeys(PGPSecretKeyRing keyRing) {
|
||||
Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>();
|
||||
Vector<PGPSecretKey> signingKeys = getCertificationKeys(keyRing);
|
||||
PGPSecretKey masterKey = null;
|
||||
for (int i = 0; i < signingKeys.size(); ++i) {
|
||||
PGPSecretKey key = signingKeys.get(i);
|
||||
if (key.isMasterKey()) {
|
||||
masterKey = key;
|
||||
} else {
|
||||
usableKeys.add(key);
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
usableKeys.add(masterKey);
|
||||
}
|
||||
return usableKeys;
|
||||
}
|
||||
|
||||
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(Context context, long masterKeyId) {
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(context,
|
||||
masterKeyId);
|
||||
if (keyRing == null) {
|
||||
Log.e(Constants.TAG, "keyRing is null!");
|
||||
return null;
|
||||
}
|
||||
Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing);
|
||||
if (encryptKeys.size() == 0) {
|
||||
Log.e(Constants.TAG, "encryptKeys is null!");
|
||||
return null;
|
||||
}
|
||||
return encryptKeys.get(0);
|
||||
}
|
||||
|
||||
public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
||||
masterKeyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
Vector<PGPSecretKey> signingKeys = getUsableCertificationKeys(keyRing);
|
||||
if (signingKeys.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return signingKeys.get(0);
|
||||
}
|
||||
|
||||
public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
||||
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.equals("")) {
|
||||
userId = context.getString(R.string.unknown_user_id);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static String getMainUserIdSafe(Context context, PGPSecretKey key) {
|
||||
String userId = getMainUserId(key);
|
||||
if (userId == null || userId.equals("")) {
|
||||
userId = context.getString(R.string.unknown_user_id);
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isCertificationKey(PGPPublicKey key) {
|
||||
if (key.getVersion() <= 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
|
||||
if (hashed != null && (hashed.getKeyFlags() & KeyFlags.CERTIFY_OTHER) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
|
||||
|
||||
if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.CERTIFY_OTHER) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isCertificationKey(PGPSecretKey key) {
|
||||
return isCertificationKey(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 = "Unknown";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return algorithmStr + ", " + keySize + " bit";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fingerprint to hex with whitespaces after 4 characters
|
||||
*
|
||||
* @param fp
|
||||
* @return
|
||||
*/
|
||||
public static String convertFingerprintToHex(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(Locale.US);
|
||||
while (chunk.length() < 2) {
|
||||
chunk = "0" + chunk;
|
||||
}
|
||||
fingerPrint += chunk;
|
||||
}
|
||||
|
||||
return fingerPrint;
|
||||
|
||||
}
|
||||
|
||||
public static String getFingerPrint(Context context, long keyId) {
|
||||
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
|
||||
// if it is no public key get it from your own keys...
|
||||
if (key == null) {
|
||||
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
|
||||
if (secretKey == null) {
|
||||
Log.e(Constants.TAG, "Key could not be found!");
|
||||
return null;
|
||||
}
|
||||
key = secretKey.getPublicKey();
|
||||
}
|
||||
|
||||
return convertFingerprintToHex(key.getFingerprint());
|
||||
}
|
||||
|
||||
public static boolean isSecretKeyPrivateEmpty(PGPSecretKey secretKey) {
|
||||
return secretKey.isPrivateKeyEmpty();
|
||||
}
|
||||
|
||||
public static boolean isSecretKeyPrivateEmpty(Context context, long keyId) {
|
||||
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
|
||||
if (secretKey == null) {
|
||||
Log.e(Constants.TAG, "Key could not be found!");
|
||||
return false; // could be a public key, assume it is not empty
|
||||
}
|
||||
return isSecretKeyPrivateEmpty(secretKey);
|
||||
}
|
||||
|
||||
public static String convertKeyIdToHex(long keyId) {
|
||||
String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(Locale.US);
|
||||
while (fingerPrint.length() < 8) {
|
||||
fingerPrint = "0" + fingerPrint;
|
||||
}
|
||||
return fingerPrint;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: documentation
|
||||
*
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static String convertKeyToHex(long keyId) {
|
||||
return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId);
|
||||
}
|
||||
|
||||
public static long convertHexToKeyId(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits userId string into naming part, email part, and comment part
|
||||
*
|
||||
* @param userId
|
||||
* @return array with naming (0), email (1), comment (2)
|
||||
*/
|
||||
public static String[] splitUserId(String userId) {
|
||||
String[] result = new String[] { "", "", "" };
|
||||
|
||||
Pattern withComment = Pattern.compile("^(.*) \\((.*)\\) <(.*)>$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
result[0] = matcher.group(1);
|
||||
result[1] = matcher.group(3);
|
||||
result[2] = matcher.group(2);
|
||||
return result;
|
||||
}
|
||||
|
||||
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
|
||||
matcher = withoutComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
result[0] = matcher.group(1);
|
||||
result[1] = matcher.group(2);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.IOException;
|
||||
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.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
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.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyPair;
|
||||
import org.spongycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Primes;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class PgpKeyOperation {
|
||||
private Context mContext;
|
||||
private ProgressDialogUpdater mProgress;
|
||||
|
||||
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 PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
|
||||
super();
|
||||
this.mContext = context;
|
||||
this.mProgress = progress;
|
||||
}
|
||||
|
||||
public void updateProgress(int message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new secret key. The returned PGPSecretKeyRing contains only one newly generated key
|
||||
* when this key is the new masterkey. If a masterkey is supplied in the parameters
|
||||
* PGPSecretKeyRing contains the masterkey and the new key as a subkey (certified by the
|
||||
* masterkey).
|
||||
*
|
||||
* @param algorithmChoice
|
||||
* @param keySize
|
||||
* @param passPhrase
|
||||
* @param masterSecretKey
|
||||
* @return
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws PGPException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws PgpGeneralException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
*/
|
||||
public PGPSecretKeyRing createKey(int algorithmChoice, int keySize, String passPhrase,
|
||||
PGPSecretKey masterSecretKey) throws NoSuchAlgorithmException, PGPException,
|
||||
NoSuchProviderException, PgpGeneralException, InvalidAlgorithmParameterException {
|
||||
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
|
||||
}
|
||||
|
||||
if (passPhrase == null) {
|
||||
passPhrase = "";
|
||||
}
|
||||
|
||||
int algorithm = 0;
|
||||
KeyPairGenerator keyGen = null;
|
||||
|
||||
switch (algorithmChoice) {
|
||||
case Id.choice.algorithm.dsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
algorithm = PGPPublicKey.DSA;
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.algorithm.elgamal: {
|
||||
if (masterSecretKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
||||
}
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
BigInteger g = new BigInteger("2");
|
||||
|
||||
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
|
||||
|
||||
keyGen.initialize(elParams);
|
||||
algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.algorithm.rsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
|
||||
algorithm = PGPPublicKey.RSA_GENERAL;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
||||
}
|
||||
}
|
||||
|
||||
// build new key pair
|
||||
PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
|
||||
HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray());
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray());
|
||||
|
||||
PGPKeyRingGenerator ringGen = null;
|
||||
PGPContentSignerBuilder certificationSignerBuilder = null;
|
||||
if (masterSecretKey == null) {
|
||||
certificationSignerBuilder = new JcaPGPContentSignerBuilder(keyPair.getPublicKey()
|
||||
.getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// build keyRing with only this one master key in it!
|
||||
ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, keyPair, "",
|
||||
sha1Calc, null, null, certificationSignerBuilder, keyEncryptor);
|
||||
} else {
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
PGPPrivateKey masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||
|
||||
certificationSignerBuilder = new JcaPGPContentSignerBuilder(masterKeyPair
|
||||
.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// build keyRing with master key and new key as subkey (certified by masterkey)
|
||||
ringGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair,
|
||||
"", sha1Calc, null, null, certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
ringGen.addSubKey(keyPair);
|
||||
}
|
||||
|
||||
PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing();
|
||||
|
||||
return secKeyRing;
|
||||
}
|
||||
|
||||
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
||||
String newPassPhrase) throws IOException, PGPException, PGPException,
|
||||
NoSuchProviderException {
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
if (oldPassPhrase == null) {
|
||||
oldPassPhrase = "";
|
||||
}
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = "";
|
||||
}
|
||||
|
||||
PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
|
||||
keyRing,
|
||||
new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()),
|
||||
new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
|
||||
.getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray()));
|
||||
|
||||
updateProgress(R.string.progress_saving_key_ring, 50, 100);
|
||||
|
||||
ProviderHelper.saveKeyRing(mContext, newKeyRing);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
}
|
||||
|
||||
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||
ArrayList<Integer> keysUsages, long masterKeyId, String oldPassPhrase,
|
||||
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
|
||||
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
|
||||
|
||||
Log.d(Constants.TAG, "userIds: " + userIds.toString());
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
|
||||
if (oldPassPhrase == null) {
|
||||
oldPassPhrase = "";
|
||||
}
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = "";
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_preparing_master_key, 10, 100);
|
||||
|
||||
int usageId = keysUsages.get(0);
|
||||
boolean canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
|
||||
String mainUserId = userIds.get(0);
|
||||
|
||||
PGPSecretKey masterKey = keys.get(0);
|
||||
|
||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||
PGPPublicKey tmpKey = masterKey.getPublicKey();
|
||||
PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
|
||||
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
|
||||
|
||||
// already done by code above:
|
||||
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||
// // Somehow, the PGPPublicKey already has an empty certification attached to it when the
|
||||
// // keyRing is generated the first time, we remove that when it exists, before adding the
|
||||
// new
|
||||
// // ones
|
||||
// PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey,
|
||||
// "");
|
||||
// if (masterPublicKeyRmCert != null) {
|
||||
// masterPublicKey = masterPublicKeyRmCert;
|
||||
// }
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
|
||||
// TODO: if we are editing a key, keep old certs, don't remake certs we don't have to.
|
||||
|
||||
for (String userId : userIds) {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
}
|
||||
|
||||
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
|
||||
if (canEncrypt) {
|
||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
hashedPacketsGen.setKeyFlags(true, keyFlags);
|
||||
|
||||
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
|
||||
// TODO: this doesn't work quite right yet (APG 1)
|
||||
// if (keyEditor.getExpiryDate() != null) {
|
||||
// GregorianCalendar creationDate = new GregorianCalendar();
|
||||
// creationDate.setTime(getCreationDate(masterKey));
|
||||
// GregorianCalendar expiryDate = keyEditor.getExpiryDate();
|
||||
// long numDays = Utils.getNumDaysBetween(creationDate, expiryDate);
|
||||
// if (numDays <= 0) {
|
||||
// throw new GeneralException(
|
||||
// context.getString(R.string.error_expiryMustComeAfterCreation));
|
||||
// }
|
||||
// hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
|
||||
// }
|
||||
|
||||
updateProgress(R.string.progress_building_master_key, 30, 100);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
|
||||
HashAlgorithmTags.SHA1);
|
||||
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
newPassPhrase.toCharArray());
|
||||
|
||||
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
|
||||
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
|
||||
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
|
||||
|
||||
for (int i = 1; i < keys.size(); ++i) {
|
||||
updateProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100);
|
||||
|
||||
PGPSecretKey subKey = keys.get(i);
|
||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
oldPassPhrase.toCharArray());
|
||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
||||
|
||||
// TODO: now used without algorithm and creation time?! (APG 1)
|
||||
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
||||
|
||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
keyFlags = 0;
|
||||
|
||||
usageId = keysUsages.get(i);
|
||||
canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
if (canSign) { // TODO: ensure signing times are the same, like gpg
|
||||
keyFlags |= KeyFlags.SIGN_DATA;
|
||||
// cross-certify signing keys
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
subPublicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
PGPSignature certification = sGen.generateCertification(masterPublicKey,
|
||||
subPublicKey);
|
||||
unhashedPacketsGen.setEmbeddedSignature(false, certification);
|
||||
}
|
||||
if (canEncrypt) {
|
||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
hashedPacketsGen.setKeyFlags(false, keyFlags);
|
||||
|
||||
// TODO: this doesn't work quite right yet (APG 1)
|
||||
// if (keyEditor.getExpiryDate() != null) {
|
||||
// GregorianCalendar creationDate = new GregorianCalendar();
|
||||
// creationDate.setTime(getCreationDate(masterKey));
|
||||
// GregorianCalendar expiryDate = keyEditor.getExpiryDate();
|
||||
// long numDays = Utils.getNumDaysBetween(creationDate, expiryDate);
|
||||
// if (numDays <= 0) {
|
||||
// throw new GeneralException(
|
||||
// context.getString(R.string.error_expiryMustComeAfterCreation));
|
||||
// }
|
||||
// hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
|
||||
// }
|
||||
|
||||
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
|
||||
}
|
||||
|
||||
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
||||
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
|
||||
|
||||
updateProgress(R.string.progress_saving_key_ring, 90, 100);
|
||||
|
||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing signKey(long masterKeyId, long pubKeyId, String passphrase)
|
||||
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
PGPException, SignatureException {
|
||||
if (passphrase == null) {
|
||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||
} else {
|
||||
PGPPublicKeyRing pubring = ProviderHelper
|
||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||
|
||||
PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (signingKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
|
||||
contentSignerBuilder);
|
||||
|
||||
signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
|
||||
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
|
||||
signatureGenerator.generate());
|
||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||
|
||||
return pubring;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,307 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
|
||||
import org.spongycastle.asn1.DERObjectIdentifier;
|
||||
import org.spongycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.spongycastle.asn1.x509.BasicConstraints;
|
||||
import org.spongycastle.asn1.x509.GeneralName;
|
||||
import org.spongycastle.asn1.x509.GeneralNames;
|
||||
import org.spongycastle.asn1.x509.SubjectKeyIdentifier;
|
||||
import org.spongycastle.asn1.x509.X509Extensions;
|
||||
import org.spongycastle.asn1.x509.X509Name;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.x509.X509V3CertificateGenerator;
|
||||
import org.spongycastle.x509.extension.AuthorityKeyIdentifierStructure;
|
||||
import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class PgpToX509 {
|
||||
public final static String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
|
||||
public final static String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
|
||||
|
||||
/**
|
||||
* Creates a self-signed certificate from a public and private key. The (critical) key-usage
|
||||
* extension is set up with: digital signature, non-repudiation, key-encipherment, key-agreement
|
||||
* and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and
|
||||
* S/MIME. A URI subjectAltName may also be set up.
|
||||
*
|
||||
* @param pubKey
|
||||
* public key
|
||||
* @param privKey
|
||||
* private key
|
||||
* @param subject
|
||||
* subject (and issuer) DN for this certificate, RFC 2253 format preferred.
|
||||
* @param startDate
|
||||
* date from which the certificate will be valid (defaults to current date and time
|
||||
* if null)
|
||||
* @param endDate
|
||||
* date until which the certificate will be valid (defaults to current date and time
|
||||
* if null) *
|
||||
* @param subjAltNameURI
|
||||
* URI to be placed in subjectAltName
|
||||
* @return self-signed certificate
|
||||
* @throws InvalidKeyException
|
||||
* @throws SignatureException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws IllegalStateException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws CertificateException
|
||||
* @throws Exception
|
||||
*
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
|
||||
X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
|
||||
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException, NoSuchProviderException {
|
||||
|
||||
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
||||
|
||||
certGenerator.reset();
|
||||
/*
|
||||
* Sets up the subject distinguished name. Since it's a self-signed certificate, issuer and
|
||||
* subject are the same.
|
||||
*/
|
||||
certGenerator.setIssuerDN(subject);
|
||||
certGenerator.setSubjectDN(subject);
|
||||
|
||||
/*
|
||||
* Sets up the validity dates.
|
||||
*/
|
||||
if (startDate == null) {
|
||||
startDate = new Date(System.currentTimeMillis());
|
||||
}
|
||||
certGenerator.setNotBefore(startDate);
|
||||
if (endDate == null) {
|
||||
endDate = new Date(startDate.getTime() + (365L * 24L * 60L * 60L * 1000L));
|
||||
Log.d(Constants.TAG, "end date is=" + DateFormat.getDateInstance().format(endDate));
|
||||
}
|
||||
|
||||
certGenerator.setNotAfter(endDate);
|
||||
|
||||
/*
|
||||
* The serial-number of this certificate is 1. It makes sense because it's self-signed.
|
||||
*/
|
||||
certGenerator.setSerialNumber(BigInteger.ONE);
|
||||
/*
|
||||
* Sets the public-key to embed in this certificate.
|
||||
*/
|
||||
certGenerator.setPublicKey(pubKey);
|
||||
/*
|
||||
* Sets the signature algorithm.
|
||||
*/
|
||||
String pubKeyAlgorithm = pubKey.getAlgorithm();
|
||||
if (pubKeyAlgorithm.equals("DSA")) {
|
||||
certGenerator.setSignatureAlgorithm("SHA1WithDSA");
|
||||
} else if (pubKeyAlgorithm.equals("RSA")) {
|
||||
certGenerator.setSignatureAlgorithm("SHA1WithRSAEncryption");
|
||||
} else {
|
||||
RuntimeException re = new RuntimeException("Algorithm not recognised: "
|
||||
+ pubKeyAlgorithm);
|
||||
Log.e(Constants.TAG, re.getMessage(), re);
|
||||
throw re;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds the Basic Constraint (CA: true) extension.
|
||||
*/
|
||||
certGenerator.addExtension(X509Extensions.BasicConstraints, true,
|
||||
new BasicConstraints(true));
|
||||
|
||||
/*
|
||||
* Adds the subject key identifier extension.
|
||||
*/
|
||||
SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pubKey);
|
||||
certGenerator
|
||||
.addExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
|
||||
|
||||
/*
|
||||
* Adds the authority key identifier extension.
|
||||
*/
|
||||
AuthorityKeyIdentifier authorityKeyIdentifier = new AuthorityKeyIdentifierStructure(pubKey);
|
||||
certGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
|
||||
authorityKeyIdentifier);
|
||||
|
||||
/*
|
||||
* Adds the subject alternative-name extension.
|
||||
*/
|
||||
if (subjAltNameURI != null) {
|
||||
GeneralNames subjectAltNames = new GeneralNames(new GeneralName(
|
||||
GeneralName.uniformResourceIdentifier, subjAltNameURI));
|
||||
certGenerator.addExtension(X509Extensions.SubjectAlternativeName, false,
|
||||
subjectAltNames);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates and sign this certificate with the private key corresponding to the public key of
|
||||
* the certificate (hence the name "self-signed certificate").
|
||||
*/
|
||||
X509Certificate cert = certGenerator.generate(privKey);
|
||||
|
||||
/*
|
||||
* Checks that this certificate has indeed been correctly signed.
|
||||
*/
|
||||
cert.verify(pubKey);
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a self-signed certificate from a PGP Secret Key.
|
||||
*
|
||||
* @param pgpSecKey
|
||||
* PGP Secret Key (from which one can extract the public and private keys and other
|
||||
* attributes).
|
||||
* @param pgpPrivKey
|
||||
* PGP Private Key corresponding to the Secret Key (password callbacks should be done
|
||||
* before calling this method)
|
||||
* @param subjAltNameURI
|
||||
* optional URI to embed in the subject alternative-name
|
||||
* @return self-signed certificate
|
||||
* @throws PGPException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws InvalidKeyException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws SignatureException
|
||||
* @throws CertificateException
|
||||
*
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
|
||||
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
|
||||
NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException {
|
||||
// get public key from secret key
|
||||
PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();
|
||||
|
||||
// LOGGER.info("Key ID: " + Long.toHexString(pgpPubKey.getKeyID() & 0xffffffffL));
|
||||
|
||||
/*
|
||||
* The X.509 Name to be the subject DN is prepared. The CN is extracted from the Secret Key
|
||||
* user ID.
|
||||
*/
|
||||
Vector<DERObjectIdentifier> x509NameOids = new Vector<DERObjectIdentifier>();
|
||||
Vector<String> x509NameValues = new Vector<String>();
|
||||
|
||||
x509NameOids.add(X509Name.O);
|
||||
x509NameValues.add(DN_COMMON_PART_O);
|
||||
|
||||
x509NameOids.add(X509Name.OU);
|
||||
x509NameValues.add(DN_COMMON_PART_OU);
|
||||
|
||||
for (@SuppressWarnings("unchecked")
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext();) {
|
||||
Object attrib = it.next();
|
||||
x509NameOids.add(X509Name.CN);
|
||||
x509NameValues.add("CryptoCall");
|
||||
// x509NameValues.add(attrib.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* Currently unused.
|
||||
*/
|
||||
Log.d(Constants.TAG, "User attributes: ");
|
||||
for (@SuppressWarnings("unchecked")
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext();) {
|
||||
Object attrib = it.next();
|
||||
Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass());
|
||||
}
|
||||
|
||||
X509Name x509name = new X509Name(x509NameOids, x509NameValues);
|
||||
|
||||
Log.d(Constants.TAG, "Subject DN: " + x509name);
|
||||
|
||||
/*
|
||||
* To check the signature from the certificate on the recipient side, the creation time
|
||||
* needs to be embedded in the certificate. It seems natural to make this creation time be
|
||||
* the "not-before" date of the X.509 certificate. Unlimited PGP keys have a validity of 0
|
||||
* second. In this case, the "not-after" date will be the same as the not-before date. This
|
||||
* is something that needs to be checked by the service receiving this certificate.
|
||||
*/
|
||||
Date creationTime = pgpPubKey.getCreationTime();
|
||||
Log.d(Constants.TAG,
|
||||
"pgp pub key creation time=" + DateFormat.getDateInstance().format(creationTime));
|
||||
Log.d(Constants.TAG, "pgp valid seconds=" + pgpPubKey.getValidSeconds());
|
||||
Date validTo = null;
|
||||
if (pgpPubKey.getValidSeconds() > 0) {
|
||||
validTo = new Date(creationTime.getTime() + 1000L * pgpPubKey.getValidSeconds());
|
||||
}
|
||||
|
||||
X509Certificate selfSignedCert = createSelfSignedCert(
|
||||
pgpPubKey.getKey(Constants.BOUNCY_CASTLE_PROVIDER_NAME), pgpPrivKey.getKey(),
|
||||
x509name, creationTime, validTo, subjAltNameURI);
|
||||
|
||||
return selfSignedCert;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a password callback handler that will fill in a password automatically. Useful to
|
||||
* configure passwords in advance, but should be used with caution depending on how much you
|
||||
* allow passwords to be stored within your application.
|
||||
*
|
||||
* @author Bruno Harbulot.
|
||||
*
|
||||
*/
|
||||
public final static class PredefinedPasswordCallbackHandler implements CallbackHandler {
|
||||
|
||||
private char[] password;
|
||||
private String prompt;
|
||||
|
||||
public PredefinedPasswordCallbackHandler(String password) {
|
||||
this(password == null ? null : password.toCharArray(), null);
|
||||
}
|
||||
|
||||
public PredefinedPasswordCallbackHandler(char[] password) {
|
||||
this(password, null);
|
||||
}
|
||||
|
||||
public PredefinedPasswordCallbackHandler(String password, String prompt) {
|
||||
this(password == null ? null : password.toCharArray(), prompt);
|
||||
}
|
||||
|
||||
public PredefinedPasswordCallbackHandler(char[] password, String prompt) {
|
||||
this.password = password;
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||
for (Callback callback : callbacks) {
|
||||
if (callback instanceof PasswordCallback) {
|
||||
PasswordCallback pwCallback = (PasswordCallback) callback;
|
||||
if ((this.prompt == null) || (this.prompt.equals(pwCallback.getPrompt()))) {
|
||||
pwCallback.setPassword(this.password);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedCallbackException(callback, "Unrecognised callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Object clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.sufficientlysecure.keychain.pgp.exception;
|
||||
|
||||
public class NoAsymmetricEncryptionException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
public NoAsymmetricEncryptionException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.sufficientlysecure.keychain.pgp.exception;
|
||||
|
||||
public class PgpGeneralException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773342L;
|
||||
|
||||
public PgpGeneralException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user