open-keychain/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java

295 lines
11 KiB
Java

/*
* 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 android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.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.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.HkpKeyServer;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class PgpImportExport {
private Context mContext;
private ProgressDialogUpdater mProgress;
private KeychainServiceListener mKeychainServiceListener;
public PgpImportExport(Context context, ProgressDialogUpdater progress) {
super();
this.mContext = context;
this.mProgress = progress;
}
public PgpImportExport(Context context,
ProgressDialogUpdater progress, KeychainServiceListener keychainListener) {
super();
this.mContext = context;
this.mProgress = progress;
this.mKeychainServiceListener = keychainListener;
}
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 = null;
try {
aos = new ArmoredOutputStream(bos);
aos.write(keyring.getEncoded());
aos.close();
String armoredKey = bos.toString("UTF-8");
server.add(armoredKey);
return true;
} catch (IOException e) {
return false;
} catch (AddKeyException e) {
// TODO: tell the user?
return false;
} finally {
try {
if (aos != null) { aos.close(); }
if (bos != null) { bos.close(); }
} catch (IOException e) {
}
}
}
/**
* Imports keys from given data. If keyIds is given only those are imported
*/
public Bundle importKeyRings(List<ImportKeysListEntry> entries)
throws PgpGeneralException, PGPException, IOException {
Bundle returnData = new Bundle();
updateProgress(R.string.progress_importing, 0, 100);
int newKeys = 0;
int oldKeys = 0;
int badKeys = 0;
int position = 0;
try {
for (ImportKeysListEntry entry : entries) {
Object obj = PgpConversionHelper.BytesToPGPKeyRing(entry.getBytes());
if (obj instanceof PGPKeyRing) {
PGPKeyRing keyring = (PGPKeyRing) obj;
int 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;
}
} else {
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
}
position++;
updateProgress(position / entries.size() * 100, 100);
}
} 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);
return returnData;
}
public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds,
ArrayList<Long> secretKeyRingMasterIds,
OutputStream outStream) throws PgpGeneralException,
PGPException, IOException {
Bundle returnData = new Bundle();
int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size();
int progress = 0;
updateProgress(
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
masterKeyIdsSize), 0, 100);
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
throw new PgpGeneralException(
mContext.getString(R.string.error_external_storage_not_ready));
}
// For each public masterKey id
for (long pubKeyMasterId : publicKeyRingMasterIds) {
progress++;
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
updateProgress(progress * 100 / masterKeyIdsSize, 100);
PGPPublicKeyRing publicKeyRing =
ProviderHelper.getPGPPublicKeyRing(mContext, pubKeyMasterId);
if (publicKeyRing != null) {
publicKeyRing.encode(arOutStream);
}
if (mKeychainServiceListener.hasServiceStopped()) {
arOutStream.close();
return null;
}
arOutStream.close();
}
// For each secret masterKey id
for (long secretKeyMasterId : secretKeyRingMasterIds) {
progress++;
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
updateProgress(progress * 100 / masterKeyIdsSize, 100);
PGPSecretKeyRing secretKeyRing =
ProviderHelper.getPGPSecretKeyRing(mContext, secretKeyMasterId);
if (secretKeyRing != null) {
secretKeyRing.encode(arOutStream);
}
if (mKeychainServiceListener.hasServiceStopped()) {
arOutStream.close();
return null;
}
arOutStream.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);
updateProgress(R.string.progress_done, 100, 100);
return returnData;
}
/**
* TODO: implement Id.return_value.updated as status when key already existed
*/
@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 (testSecretKey.isPrivateKeyEmpty()) {
// this is bad, something is very wrong...
save = false;
status = Id.return_value.bad;
}
}
}
if (save) {
// 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);
}
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
// 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;
}
}