1
0
mirror of https://github.com/moparisthebest/open-keychain synced 2025-03-05 03:39:43 -05:00

new savekeyring operation (mostly stub)

This commit is contained in:
Vincent Breitmoser 2014-05-14 15:37:55 +02:00
parent 6415290b2d
commit a53da491c0
16 changed files with 595 additions and 356 deletions

@ -21,8 +21,6 @@ import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
@ -122,26 +120,4 @@ public class PgpConversionHelper {
return new UncachedSecretKey(secKey);
}
/**
* Convert from byte[] to PGPSignature
*
* @param sigBytes
* @return
*/
public static PGPSignature BytesToPGPSignature(byte[] sigBytes) {
PGPObjectFactory factory = new PGPObjectFactory(sigBytes);
PGPSignatureList signatures = null;
try {
if ((signatures = (PGPSignatureList) factory.nextObject()) == null || signatures.isEmpty()) {
Log.e(Constants.TAG, "No signatures given!");
return null;
}
} catch (IOException e) {
Log.e(Constants.TAG, "Error while converting to PGPSignature!", e);
return null;
}
return signatures.get(0);
}
}

@ -257,7 +257,8 @@ public class PgpImportExport {
updateProgress(progress * 100 / masterKeyIdsSize, 100);
try {
PGPSecretKeyRing secretKeyRing = mProviderHelper.getPGPSecretKeyRing(secretKeyMasterId);
WrappedSecretKeyRing secretKeyRing =
mProviderHelper.getWrappedSecretKeyRing(secretKeyMasterId);
secretKeyRing.encode(arOutStream);
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
@ -287,18 +288,13 @@ public class PgpImportExport {
public int storeKeyRingInCache(UncachedKeyRing ring, UncachedKeyRing secretRing) {
int status;
try {
// TODO make sure these are correctly typed!
PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) ring.getRing();
PGPSecretKeyRing secretKeyRing = null;
if(secretRing != null) {
secretKeyRing = (PGPSecretKeyRing) secretRing.getRing();
}
UncachedSecretKeyRing secretKeyRing = null;
// see what type we have. we can either have a secret + public keyring, or just public
if (secretKeyRing != null) {
mProviderHelper.saveKeyRing(ring, secretRing);
status = RETURN_OK;
} else {
mProviderHelper.saveKeyRing(publicKeyRing);
mProviderHelper.saveKeyRing(ring);
status = RETURN_OK;
}
} catch (IOException e) {

@ -35,6 +35,7 @@ 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;
@ -49,6 +50,8 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.service.OldSaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Primes;
import java.io.IOException;
@ -60,9 +63,11 @@ import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
/**
@ -644,7 +649,7 @@ public class PgpKeyOperation {
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(
secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
secretKeyRing.getPublicKey().getSignaturesForId(uid))) {
Log.d(Constants.TAG, "sig: " +
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
@ -655,7 +660,7 @@ public class PgpKeyOperation {
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(
publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
publicKeyRing.getPublicKey().getSignaturesForId(uid))) {
Log.d(Constants.TAG, "sig: " +
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
@ -668,6 +673,335 @@ public class PgpKeyOperation {
}
public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing sKR,
PGPPublicKeyRing pKR,
SaveKeyringParcel saveParcel,
String passphrase)
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
updateProgress(R.string.progress_building_key, 0, 100);
// sort these, so we can use binarySearch later on
Arrays.sort(saveParcel.revokeSubKeys);
Arrays.sort(saveParcel.revokeUserIds);
/*
* What's gonna happen here:
*
* 1. Unlock private key
*
* 2. Create new secret key ring
*
* 3. Copy subkeys
* - Generate revocation if requested
* - Copy old cert, or generate new if change requested
*
* 4. Generate and add new subkeys
*
* 5. Copy user ids
* - Generate revocation if requested
* - Copy old cert, or generate new if primary user id status changed
*
* 6. Add new user ids
*
* 7. Generate PublicKeyRing from SecretKeyRing
*
* 8. Return pair (PublicKeyRing,SecretKeyRing)
*
*/
// 1. Unlock private key
updateProgress(R.string.progress_building_key, 0, 100);
PGPPublicKey masterPublicKey = sKR.getPublicKey();
PGPPrivateKey masterPrivateKey; {
PGPSecretKey masterKey = sKR.getSecretKey();
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
}
// 2. Create new secret key ring
updateProgress(R.string.progress_certifying_master_key, 20, 100);
// Note we do NOT use PGPKeyRingGeneraor, it's just one level too high and does stuff
// we want to do manually. Instead, we simply use a list of secret keys.
ArrayList<PGPSecretKey> secretKeys = new ArrayList<PGPSecretKey>();
ArrayList<PGPPublicKey> publicKeys = new ArrayList<PGPPublicKey>();
// 3. Copy subkeys
// - Generate revocation if requested
// - Copy old cert, or generate new if change requested
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
PGPPublicKey pKey = sKey.getPublicKey();
if (Arrays.binarySearch(saveParcel.revokeSubKeys, sKey.getKeyID()) >= 0) {
// add revocation signature to key, if there is none yet
if (!pKey.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION).hasNext()) {
// generate revocation signature
}
}
if (saveParcel.changeSubKeys.containsKey(sKey.getKeyID())) {
// change subkey flags?
SaveKeyringParcel.SubkeyChange change = saveParcel.changeSubKeys.get(sKey.getKeyID());
// remove old subkey binding signature(s?)
for (PGPSignature sig : new IterableIterator<PGPSignature>(
pKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING))) {
pKey = PGPPublicKey.removeCertification(pKey, sig);
}
// generate and add new signature
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
}
secretKeys.add(PGPSecretKey.replacePublicKey(sKey, pKey));
publicKeys.add(pKey);
}
// 4. Generate and add new subkeys
// TODO
// 5. Copy user ids
for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) {
// - Copy old cert, or generate new if primary user id status changed
boolean certified = false, revoked = false;
for (PGPSignature sig : new IterableIterator<PGPSignature>(
masterPublicKey.getSignaturesForID(userId))) {
// We know there are only revocation and certification types in here.
switch(sig.getSignatureType()) {
case PGPSignature.CERTIFICATION_REVOCATION:
revoked = true;
continue;
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
// Already got one? Remove this one, then.
if (certified) {
masterPublicKey = PGPPublicKey.removeCertification(
masterPublicKey, userId, sig);
continue;
}
boolean primary = userId.equals(saveParcel.changePrimaryUserId);
// Generate a new one under certain circumstances
if (saveParcel.changePrimaryUserId != null &&
sig.getHashedSubPackets().isPrimaryUserID() != primary) {
PGPSignature cert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, primary);
PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
certified = true;
}
}
// - Generate revocation if requested
if (!revoked && Arrays.binarySearch(saveParcel.revokeUserIds, userId) >= 0) {
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
}
// 6. Add new user ids
for(String userId : saveParcel.addUserIds) {
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, userId.equals(saveParcel.changePrimaryUserId));
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
// 7. Generate PublicKeyRing from SecretKeyRing
updateProgress(R.string.progress_building_master_key, 30, 100);
PGPSecretKeyRing ring = new PGPSecretKeyRing(secretKeys);
// Copy all non-self uid certificates
for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) {
// - Copy old cert, or generate new if primary user id status changed
boolean certified = false, revoked = false;
for (PGPSignature sig : new IterableIterator<PGPSignature>(
masterPublicKey.getSignaturesForID(userId))) {
}
}
for (PGPPublicKey newKey : publicKeys) {
PGPPublicKey oldKey = pKR.getPublicKey(newKey.getKeyID());
for (PGPSignature sig : new IterableIterator<PGPSignature>(
oldKey.getSignatures())) {
}
}
// If requested, set new passphrase
if (saveParcel.newPassphrase != null) {
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(HashAlgorithmTags.SHA1);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassphrase.toCharArray());
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
}
// 8. Return pair (PublicKeyRing,SecretKeyRing)
return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(sKR, pKR);
}
private static PGPSignature generateUserIdSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date());
subHashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
subHashedPacketsGen.setPrimaryUserID(false, primary);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
}
private static PGPSignature generateRevocationSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date());
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
}
private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPSecretKey sKey, PGPPublicKey pKey,
int flags, Long expiry, String passphrase)
throws PgpGeneralMsgIdException, IOException, PGPException, SignatureException {
// date for signing
Date todayDate = new Date();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
// If this key can sign, we need a primary key binding signature
if ((flags & KeyFlags.SIGN_DATA) != 0) {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray());
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
// cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification);
}
PGPSignatureSubpacketGenerator hashedPacketsGen;
{
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setSignatureCreationTime(false, todayDate);
hashedPacketsGen.setKeyFlags(false, flags);
}
if (expiry != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(pKey.getCreationTime());
// note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
// here we purposefully ignore partial days in each date - long type has
// no fractional part!
long numDays = (expiry / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
}
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0);
}
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
return sGen.generateCertification(masterPublicKey, pKey);
}
/**
* Certify the given pubkeyid with the given masterkeyid.
*
* @param certificationKey Certifying key
* @param publicKey public key to certify
* @param userIds User IDs to certify, must not be null or empty
* @param passphrase Passphrase of the secret key
* @return A keyring with added certifications
*/
public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey,
List<String> userIds, String passphrase)
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator;
{
if (certificationKey == null) {
throw new PgpGeneralMsgIdException(R.string.error_no_signature_key);
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
}
// TODO: SHA256 fixed?
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
}
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// fetch public key ring, add the certification and return it
for (String userId : new IterableIterator<String>(userIds.iterator())) {
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
}
return publicKey;
}
/**
* Simple static subclass that stores two values.
* <p/>

@ -1,8 +1,9 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
@ -13,6 +14,8 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
@ -26,6 +29,10 @@ public class UncachedKeyRing {
mIsSecret = ring instanceof PGPSecretKeyRing;
}
public long getMasterKeyId() {
return mRing.getPublicKey().getKeyID();
}
/* TODO don't use this */
@Deprecated
public PGPKeyRing getRing() {
@ -36,6 +43,21 @@ public class UncachedKeyRing {
return new UncachedPublicKey(mRing.getPublicKey());
}
public Iterator<UncachedPublicKey> getPublicKeys() {
final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
return new Iterator<UncachedPublicKey>() {
public void remove() {
it.remove();
}
public UncachedPublicKey next() {
return new UncachedPublicKey(it.next());
}
public boolean hasNext() {
return it.hasNext();
}
};
}
public boolean isSecret() {
return mIsSecret;
}
@ -50,6 +72,15 @@ public class UncachedKeyRing {
public static UncachedKeyRing decodePubkeyFromData(byte[] data)
throws PgpGeneralException, IOException {
UncachedKeyRing ring = decodeFromData(data);
if(ring.isSecret()) {
throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!");
}
return ring;
}
public static UncachedKeyRing decodeFromData(byte[] data)
throws PgpGeneralException, IOException {
BufferedInputStream bufferedInput =
new BufferedInputStream(new ByteArrayInputStream(data));
if (bufferedInput.available() > 0) {
@ -58,13 +89,14 @@ public class UncachedKeyRing {
// get first object in block
Object obj;
if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPPublicKeyRing) {
return new UncachedKeyRing((PGPPublicKeyRing) obj);
if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPKeyRing) {
// the constructor will take care of the public/secret part
return new UncachedKeyRing((PGPKeyRing) obj);
} else {
throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!");
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
}
} else {
throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!");
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
}
}
@ -89,4 +121,11 @@ public class UncachedKeyRing {
return result;
}
public void encodeArmored(OutputStream out, String version) throws IOException {
ArmoredOutputStream aos = new ArmoredOutputStream(out);
aos.setHeader("Version", version);
aos.write(mRing.getEncoded());
aos.close();
}
}

@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
public class UncachedPublicKey {
@ -173,8 +174,24 @@ public class UncachedPublicKey {
return mPublicKey.getFingerprint();
}
// Note that this method has package visibility - no access outside the pgp package!
PGPPublicKey getPublicKey() {
// TODO This method should have package visibility - no access outside the pgp package!
// (It's still used in ProviderHelper at this point)
public PGPPublicKey getPublicKey() {
return mPublicKey;
}
public Iterator<WrappedSignature> getSignaturesForId(String userId) {
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);
return new Iterator<WrappedSignature>() {
public void remove() {
it.remove();
}
public WrappedSignature next() {
return new WrappedSignature(it.next());
}
public boolean hasNext() {
return it.hasNext();
}
};
}
}

@ -1,19 +1,33 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.IterableIterator;
public class UncachedSecretKeyRing {
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
final PGPSecretKeyRing mSecretRing;
public class UncachedSecretKeyRing extends UncachedKeyRing {
UncachedSecretKeyRing(PGPSecretKeyRing secretRing) {
mSecretRing = secretRing;
super(secretRing);
}
// Breaking the pattern here, for key import!
// TODO reduce this from public to default visibility!
public PGPSecretKeyRing getSecretKeyRing() {
return mSecretRing;
public ArrayList<Long> getAvailableSubkeys() {
ArrayList<Long> result = new ArrayList<Long>();
// then, mark exactly the keys we have available
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(
((PGPSecretKeyRing) mRing).getSecretKeys())) {
S2K s2k = sub.getS2K();
// Set to 1, except if the encryption type is GNU_DUMMY_S2K
if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
result.add(sub.getKeyID());
}
}
return result;
}
}

@ -5,6 +5,9 @@ import org.spongycastle.openpgp.PGPPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.IterableIterator;
import java.io.IOException;
import java.io.OutputStream;
public abstract class WrappedKeyRing extends KeyRing {
private final boolean mHasAnySecret;
@ -76,6 +79,10 @@ public abstract class WrappedKeyRing extends KeyRing {
}
}
public void encode(OutputStream stream) throws IOException {
getRing().encode(stream);
}
abstract PGPKeyRing getRing();
}

@ -120,4 +120,8 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
});
}
public UncachedSecretKeyRing getUncached() {
return new UncachedSecretKeyRing(mRing);
}
}

@ -5,6 +5,7 @@ import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.RevocationReason;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
@ -14,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.security.SignatureException;
import java.util.Date;
public class WrappedSignature {
@ -33,16 +35,57 @@ public class WrappedSignature {
return mSig.getKeyID();
}
public int getSignatureType() {
return mSig.getSignatureType();
}
public int getKeyAlgorithm() {
return mSig.getKeyAlgorithm();
}
public Date getCreationTime() {
return mSig.getCreationTime();
}
public byte[] getEncoded() throws IOException {
return mSig.getEncoded();
}
public boolean isRevocation() {
return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON);
}
public boolean isPrimaryUserId() {
return mSig.getHashedSubPackets().isPrimaryUserID();
}
public String getRevocationReason() throws PgpGeneralException {
if(!isRevocation()) {
throw new PgpGeneralException("Not a revocation signature.");
}
SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(
SignatureSubpacketTags.REVOCATION_REASON);
// For some reason, this is missing in SignatureSubpacketInputStream:146
if (!(p instanceof RevocationReason)) {
p = new RevocationReason(false, p.getData());
}
return ((RevocationReason) p).getRevocationDescription();
}
public void init(WrappedPublicKey key) throws PgpGeneralException {
init(key.getPublicKey());
}
public void init(UncachedPublicKey key) throws PgpGeneralException {
init(key.getPublicKey());
}
protected void init(PGPPublicKey key) throws PgpGeneralException {
try {
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
mSig.init(contentVerifierBuilderProvider, key.getPublicKey());
mSig.init(contentVerifierBuilderProvider, key);
} catch(PGPException e) {
throw new PgpGeneralException(e);
}
@ -74,30 +117,9 @@ public class WrappedSignature {
}
}
public boolean isRevocation() {
return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON);
}
public String getRevocationReason() throws PgpGeneralException {
if(!isRevocation()) {
throw new PgpGeneralException("Not a revocation signature.");
}
SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(
SignatureSubpacketTags.REVOCATION_REASON);
// For some reason, this is missing in SignatureSubpacketInputStream:146
if (!(p instanceof RevocationReason)) {
p = new RevocationReason(false, p.getData());
}
return ((RevocationReason) p).getRevocationDescription();
}
/** Verify a signature for this pubkey, after it has been initialized by the signer using
* initSignature(). This method should probably move into a wrapped PGPSignature class
* at some point.
*/
public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException {
protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException {
try {
return mSig.verifyCertification(uid, key.getPublicKey());
return mSig.verifyCertification(uid, key);
} catch (SignatureException e) {
throw new PgpGeneralException("Error!", e);
} catch (PGPException e) {
@ -105,6 +127,13 @@ public class WrappedSignature {
}
}
public boolean verifySignature(UncachedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid);
}
public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid);
}
public static WrappedSignature fromBytes(byte[] data) {
PGPObjectFactory factory = new PGPObjectFactory(data);
PGPSignatureList signatures = null;

@ -23,11 +23,9 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
@ -256,6 +254,8 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}.getReadableDatabase();
Cursor cursor = null;
ProviderHelper providerHelper = new ProviderHelper(context);
try {
// we insert in two steps: first, all public keys that have secret keys
cursor = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS ("
@ -266,14 +266,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
byte[] data = cursor.getBlob(0);
PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data);
ProviderHelper providerHelper = new ProviderHelper(context);
if (ring instanceof PGPPublicKeyRing)
providerHelper.saveKeyRing((PGPPublicKeyRing) ring);
else if (ring instanceof PGPSecretKeyRing)
providerHelper.saveKeyRing((PGPSecretKeyRing) ring);
else {
Log.e(Constants.TAG, "Unknown blob data type!");
try {
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
providerHelper.saveKeyRing(ring);
} catch(PgpGeneralException e) {
Log.e(Constants.TAG, "Error decoding keyring blob!");
}
}
}
@ -293,14 +290,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
byte[] data = cursor.getBlob(0);
PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data);
ProviderHelper providerHelper = new ProviderHelper(context);
if (ring instanceof PGPPublicKeyRing) {
providerHelper.saveKeyRing((PGPPublicKeyRing) ring);
} else if (ring instanceof PGPSecretKeyRing) {
providerHelper.saveKeyRing((PGPSecretKeyRing) ring);
} else {
Log.e(Constants.TAG, "Unknown blob data type!");
try {
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
providerHelper.saveKeyRing(ring);
} catch(PgpGeneralException e) {
Log.e(Constants.TAG, "Error decoding keyring blob!");
}
}
}

@ -23,30 +23,21 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.S2K;
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.PGPSignature;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
@ -60,7 +51,6 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -147,19 +137,26 @@ public class ProviderHelper {
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
}
@Deprecated
public LongSparseArray<PGPKeyRing> getPGPKeyRings(Uri queryUri) {
private LongSparseArray<UncachedPublicKey> getUncachedMasterKeys(Uri queryUri) {
Cursor cursor = mContentResolver.query(queryUri,
new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA},
null, null, null);
LongSparseArray<PGPKeyRing> result = new LongSparseArray<PGPKeyRing>(cursor.getCount());
LongSparseArray<UncachedPublicKey> result =
new LongSparseArray<UncachedPublicKey>(cursor.getCount());
try {
if (cursor != null && cursor.moveToFirst()) do {
long masterKeyId = cursor.getLong(0);
byte[] data = cursor.getBlob(1);
if (data != null) {
result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data));
try {
result.put(masterKeyId,
UncachedKeyRing.decodePubkeyFromData(data).getPublicKey());
} catch(PgpGeneralException e) {
Log.e(Constants.TAG, "Error parsing keyring, skipping.");
} catch(IOException e) {
Log.e(Constants.TAG, "IO error, skipping keyring");
}
}
} while (cursor.moveToNext());
} finally {
@ -194,12 +191,13 @@ public class ProviderHelper {
private KeyRing getWrappedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
Cursor cursor = mContentResolver.query(queryUri,
new String[] {
// we pick from cache only information that is not easily available from keyrings
KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED,
// and of course, ring data
secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA
}, null, null, null);
new String[]{
// we pick from cache only information that is not easily available from keyrings
KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED,
// and of course, ring data
secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA
}, null, null, null
);
try {
if (cursor != null && cursor.moveToFirst()) {
@ -219,37 +217,18 @@ public class ProviderHelper {
}
}
@Deprecated
public PGPKeyRing getPGPKeyRing(Uri queryUri) throws NotFoundException {
LongSparseArray<PGPKeyRing> result = getPGPKeyRings(queryUri);
if (result.size() == 0) {
throw new NotFoundException("PGPKeyRing object not found!");
} else {
return result.valueAt(0);
}
}
/**
* Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
*/
@Deprecated
public PGPSecretKeyRing getPGPSecretKeyRing(long masterKeyId) throws NotFoundException {
Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
return (PGPSecretKeyRing) getPGPKeyRing(queryUri);
}
/**
* Saves PGPPublicKeyRing with its keys and userIds in DB
*/
@SuppressWarnings("unchecked")
public void saveKeyRing(PGPPublicKeyRing keyRing) throws IOException {
PGPPublicKey masterKey = keyRing.getPublicKey();
long masterKeyId = masterKey.getKeyID();
public void saveKeyRing(UncachedKeyRing keyRing) throws IOException {
UncachedPublicKey masterKey = keyRing.getPublicKey();
long masterKeyId = masterKey.getKeyId();
// IF there is a secret key, preserve it!
PGPSecretKeyRing secretRing = null;
UncachedSecretKeyRing secretRing = null;
try {
secretRing = getPGPSecretKeyRing(masterKeyId);
secretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
} catch (NotFoundException e) {
Log.e(Constants.TAG, "key not found!");
}
@ -272,36 +251,38 @@ public class ProviderHelper {
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
int rank = 0;
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
operations.add(buildPublicKeyOperations(masterKeyId, key, rank));
++rank;
}
// get a list of owned secret keys, for verification filtering
LongSparseArray<PGPKeyRing> allKeyRings = getPGPKeyRings(KeyRingData.buildSecretKeyRingUri());
LongSparseArray<UncachedPublicKey> allKeyRings =
getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri());
// special case: available secret keys verify themselves!
if (secretRing != null)
allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing);
if (secretRing != null) {
allKeyRings.put(secretRing.getMasterKeyId(), secretRing.getPublicKey());
}
// classify and order user ids. primary are moved to the front, revoked to the back,
// otherwise the order in the keyfile is preserved.
List<UserIdItem> uids = new ArrayList<UserIdItem>();
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
for (String userId : new IterableIterator<String>(
masterKey.getUnorderedUserIds().iterator())) {
UserIdItem item = new UserIdItem();
uids.add(item);
item.userId = userId;
// look through signatures for this specific key
for (PGPSignature cert : new IterableIterator<PGPSignature>(
masterKey.getSignaturesForID(userId))) {
long certId = cert.getKeyID();
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
masterKey.getSignaturesForId(userId))) {
long certId = cert.getKeyId();
try {
// self signature
if (certId == masterKeyId) {
cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), masterKey);
if (!cert.verifyCertification(userId, masterKey)) {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userId)) {
// not verified?! dang! TODO notify user? this is kinda serious...
Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!");
continue;
@ -310,31 +291,22 @@ public class ProviderHelper {
if (item.selfCert == null ||
item.selfCert.getCreationTime().before(cert.getCreationTime())) {
item.selfCert = cert;
item.isPrimary = cert.getHashedSubPackets().isPrimaryUserID();
item.isRevoked =
cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;
item.isPrimary = cert.isPrimaryUserId();
item.isRevoked = cert.isRevocation();
}
}
// verify signatures from known private keys
if (allKeyRings.indexOfKey(certId) >= 0) {
// mark them as verified
cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME),
allKeyRings.get(certId).getPublicKey());
if (cert.verifyCertification(userId, masterKey)) {
cert.init(allKeyRings.get(certId));
if (cert.verifySignature(masterKey, userId)) {
item.trustedCerts.add(cert);
}
}
} catch (SignatureException e) {
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, "Signature verification failed! "
+ PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyId())
+ " from "
+ PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
} catch (PGPException e) {
Log.e(Constants.TAG, "Signature verification failed! "
+ PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())
+ " from "
+ PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e);
+ PgpKeyHelper.convertKeyIdToHex(cert.getKeyId()), e);
}
}
}
@ -380,8 +352,8 @@ public class ProviderHelper {
String userId;
boolean isPrimary = false;
boolean isRevoked = false;
PGPSignature selfCert;
List<PGPSignature> trustedCerts = new ArrayList<PGPSignature>();
WrappedSignature selfCert;
List<WrappedSignature> trustedCerts = new ArrayList<WrappedSignature>();
@Override
public int compareTo(UserIdItem o) {
@ -401,18 +373,8 @@ public class ProviderHelper {
* Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring
* is already in the database!
*/
public void saveKeyRing(UncachedSecretKeyRing wrappedRing) throws IOException {
// TODO split up getters
PGPSecretKeyRing keyRing = wrappedRing.getSecretKeyRing();
saveKeyRing(keyRing);
}
/**
* Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring
* is already in the database!
*/
public void saveKeyRing(PGPSecretKeyRing keyRing) throws IOException {
long masterKeyId = keyRing.getSecretKey().getKeyID();
public void saveKeyRing(UncachedSecretKeyRing keyRing) throws IOException {
long masterKeyId = keyRing.getMasterKeyId();
{
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
@ -424,14 +386,10 @@ public class ProviderHelper {
values.put(Keys.HAS_SECRET, 1);
// then, mark exactly the keys we have available
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
S2K s2k = sub.getS2K();
// Set to 1, except if the encryption type is GNU_DUMMY_S2K
if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[]{
Long.toString(sub.getKeyID())
});
}
for (Long sub : new IterableIterator<Long>(keyRing.getAvailableSubkeys().iterator())) {
mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[] {
Long.toString(sub)
});
}
// this implicitly leaves all keys which were not in the secret key ring
// with has_secret = 0
@ -449,11 +407,6 @@ public class ProviderHelper {
}
public void saveKeyRing(UncachedKeyRing ring) throws IOException {
PGPPublicKeyRing pubRing = (PGPPublicKeyRing) ring.getRing();
saveKeyRing(pubRing);
}
/**
* Saves (or updates) a pair of public and secret KeyRings in the database
*/
@ -464,32 +417,32 @@ public class ProviderHelper {
mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null);
// save public keyring
saveKeyRing((PGPPublicKeyRing) pubRing.getRing());
saveKeyRing((PGPSecretKeyRing) secRing.getRing());
saveKeyRing(pubRing);
saveKeyRing(secRing);
}
/**
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/
private ContentProviderOperation
buildPublicKeyOperations(long masterKeyId, PGPPublicKey key, int rank) throws IOException {
buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException {
ContentValues values = new ContentValues();
values.put(Keys.MASTER_KEY_ID, masterKeyId);
values.put(Keys.RANK, rank);
values.put(Keys.KEY_ID, key.getKeyID());
values.put(Keys.KEY_ID, key.getKeyId());
values.put(Keys.KEY_SIZE, key.getBitStrength());
values.put(Keys.ALGORITHM, key.getAlgorithm());
values.put(Keys.FINGERPRINT, key.getFingerprint());
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key)));
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key)));
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
values.put(Keys.IS_REVOKED, key.isRevoked());
values.put(Keys.CAN_CERTIFY, key.canCertify());
values.put(Keys.CAN_SIGN, key.canSign());
values.put(Keys.CAN_ENCRYPT, key.canEncrypt());
values.put(Keys.IS_REVOKED, key.maybeRevoked());
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
Date expiryDate = PgpKeyHelper.getExpiryDate(key);
values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000);
Date expiryDate = key.getExpiryTime();
if (expiryDate != null) {
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
}
@ -503,11 +456,12 @@ public class ProviderHelper {
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/
private ContentProviderOperation
buildCertOperations(long masterKeyId, int rank, PGPSignature cert, int verified) throws IOException {
buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, int verified)
throws IOException {
ContentValues values = new ContentValues();
values.put(Certs.MASTER_KEY_ID, masterKeyId);
values.put(Certs.RANK, rank);
values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID());
values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId());
values.put(Certs.TYPE, cert.getSignatureType());
values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
values.put(Certs.VERIFIED, verified);
@ -535,23 +489,11 @@ public class ProviderHelper {
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
private String getKeyRingAsArmoredString(byte[] data) throws IOException {
Object keyRing = null;
if (data != null) {
keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
}
private String getKeyRingAsArmoredString(byte[] data) throws IOException, PgpGeneralException {
UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
aos.setHeader("Version", PgpHelper.getFullVersion(mContext));
if (keyRing instanceof PGPSecretKeyRing) {
aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
} else if (keyRing instanceof PGPPublicKeyRing) {
aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
}
aos.close();
keyRing.encodeArmored(bos, PgpHelper.getFullVersion(mContext));
String armoredKey = bos.toString("UTF-8");
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
@ -560,77 +502,12 @@ public class ProviderHelper {
}
public String getKeyRingAsArmoredString(Uri uri)
throws NotFoundException, IOException {
throws NotFoundException, IOException, PgpGeneralException {
byte[] data = (byte[]) getGenericData(
uri, KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB);
return getKeyRingAsArmoredString(data);
}
/**
* TODO: currently not used, but will be needed to upload many keys at once!
*
* @param masterKeyIds
* @return
* @throws IOException
*/
public ArrayList<String> getKeyRingsAsArmoredString(long[] masterKeyIds)
throws IOException {
ArrayList<String> output = new ArrayList<String>();
if (masterKeyIds == null || masterKeyIds.length == 0) {
Log.e(Constants.TAG, "No master keys given!");
return output;
}
// Build a cursor for the selected masterKeyIds
Cursor cursor;
{
String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN (";
for (int i = 0; i < masterKeyIds.length; ++i) {
if (i != 0) {
inMasterKeyList += ", ";
}
inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]);
}
inMasterKeyList += ")";
cursor = mContentResolver.query(KeyRingData.buildPublicKeyRingUri(), new String[]{
KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA
}, inMasterKeyList, null, null);
}
try {
if (cursor != null) {
int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID);
int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
if (cursor.moveToFirst()) {
do {
Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
byte[] data = cursor.getBlob(dataCol);
// get actual keyring data blob and write it to ByteArrayOutputStream
try {
output.add(getKeyRingAsArmoredString(data));
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
}
} while (cursor.moveToNext());
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
if (output.size() > 0) {
return output;
} else {
return null;
}
}
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);

@ -26,16 +26,13 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
@ -594,21 +591,21 @@ public class KeychainIntentService extends IntentService
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, true);
os.write(buf);
keyUsageList.add(KeyFlags.CERTIFY_OTHER);
keyUsageList.add(UncachedSecretKey.CERTIFY_OTHER);
keysCreated++;
setProgress(keysCreated, keysTotal);
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, false);
os.write(buf);
keyUsageList.add(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
keyUsageList.add(UncachedSecretKey.ENCRYPT_COMMS | UncachedSecretKey.ENCRYPT_STORAGE);
keysCreated++;
setProgress(keysCreated, keysTotal);
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, false);
os.write(buf);
keyUsageList.add(KeyFlags.SIGN_DATA);
keyUsageList.add(UncachedSecretKey.SIGN_DATA);
keysCreated++;
setProgress(keysCreated, keysTotal);
@ -749,23 +746,15 @@ public class KeychainIntentService extends IntentService
byte[] downloadedKeyBytes = server.get(keybaseId).getBytes();
// create PGPKeyRing object based on downloaded armored key
PGPKeyRing downloadedKey = null;
UncachedKeyRing downloadedKey = null;
BufferedInputStream bufferedInput =
new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
if (bufferedInput.available() > 0) {
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
// get first object in block
Object obj;
if ((obj = objectFactory.nextObject()) != null) {
if (obj instanceof PGPKeyRing) {
downloadedKey = (PGPKeyRing) obj;
} else {
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
}
List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput);
if(rings.isEmpty()) {
throw new PgpGeneralException("No keys in result!");
}
downloadedKey = rings.get(0);
}
// save key bytes in entry object for doing the

@ -34,12 +34,6 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
@ -48,7 +42,6 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
import java.util.Iterator;
/**
* This service runs in its own process, but is available to all other processes as the main
@ -191,7 +184,8 @@ public class PassphraseCacheService extends Service {
// get cached passphrase
String cachedPassphrase = mPassphraseCache.get(keyId);
if (cachedPassphrase == null) {
// this is an error
Log.d(TAG, "Passphrase not (yet) cached, returning null");
// not really an error, just means the passphrase is not cached but not empty either
return null;
}
@ -206,44 +200,6 @@ public class PassphraseCacheService extends Service {
}
}
@Deprecated
public static boolean hasPassphrase(PGPSecretKeyRing secretKeyRing) {
PGPSecretKey secretKey = null;
boolean foundValidKey = false;
for (Iterator keys = secretKeyRing.getSecretKeys(); keys.hasNext(); ) {
secretKey = (PGPSecretKey) keys.next();
if (!secretKey.isPrivateKeyEmpty()) {
foundValidKey = true;
break;
}
}
if(!foundValidKey) {
return false;
}
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider("SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
return testKey == null;
} catch(PGPException e) {
// this means the crc check failed -> passphrase required
return true;
}
}
/**
* Checks if key has a passphrase.
*
* @param secretKeyId
* @return true if it has a passphrase
*/
@Deprecated
public static boolean hasPassphrase(Context context, long secretKeyId)
throws ProviderHelper.NotFoundException {
return new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase();
}
/**
* Register BroadcastReceiver that is unregistered when service is destroyed. This
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout

@ -13,7 +13,8 @@ import java.util.HashMap;
*
* All changes are done in a differential manner. Besides the two key
* identification attributes, all attributes may be null, which indicates no
* change to the keyring.
* change to the keyring. This is also the reason why boxed values are used
* instead of primitives in the subclasses.
*
* Application of operations in the backend should be fail-fast, which means an
* error in any included operation (for example revocation of a non-existent
@ -45,10 +46,12 @@ public class SaveKeyringParcel implements Parcelable {
// performance gain for using Parcelable here would probably be negligible,
// use Serializable instead.
public static class SubkeyAdd implements Serializable {
public final int mAlgorithm;
public final int mKeysize;
public final int mFlags;
public final Long mExpiry;
public SubkeyAdd(int keysize, int flags, long expiry) {
public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) {
mAlgorithm = algorithm;
mKeysize = keysize;
mFlags = flags;
mExpiry = expiry;
@ -59,7 +62,7 @@ public class SaveKeyringParcel implements Parcelable {
public final long mKeyId;
public final Integer mFlags;
public final Long mExpiry;
public SubkeyChange(long keyId, int flags, long expiry) {
public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId;
mFlags = flags;
mExpiry = expiry;

@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
@ -190,6 +191,9 @@ public class ViewKeyShareFragment extends LoaderFragment implements
}
startActivity(Intent.createChooser(sendIntent, title));
}
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
} catch (IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();

@ -102,7 +102,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
// check if secret key has a passphrase
if (!(secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none)) {
try {
if (new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) {
if (!new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) {
throw new PgpGeneralException("No passphrase! No passphrase dialog needed!");
}
} catch(ProviderHelper.NotFoundException e) {