Merge branch 'development' into detached-sigs-api

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
This commit is contained in:
Dominik Schürmann
2015-01-26 16:30:21 +01:00
652 changed files with 5125 additions and 3400 deletions

View File

@@ -19,7 +19,6 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;

View File

@@ -53,7 +53,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canSign() {
// if key flags subpacket is available, honor it!
if (getKeyUsage() != null) {
if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.SIGN_DATA) != 0;
}
@@ -66,7 +66,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canCertify() {
// if key flags subpacket is available, honor it!
if (getKeyUsage() != null) {
if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0;
}
@@ -79,7 +79,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canEncrypt() {
// if key flags subpacket is available, honor it!
if (getKeyUsage() != null) {
if (getKeyUsage() != 0) {
return (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0;
}
@@ -93,7 +93,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canAuthenticate() {
// if key flags subpacket is available, honor it!
if (getKeyUsage() != null) {
if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0;
}

View File

@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -76,7 +78,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
@SuppressWarnings("unchecked")
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -94,4 +96,13 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
});
}
/** Create a dummy secret ring from this key */
public UncachedKeyRing createDummySecretRing () {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
return new UncachedKeyRing(secRing);
}
}

View File

@@ -182,7 +182,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
* @return
*/
public LinkedList<Integer> getSupportedHashAlgorithms() {
LinkedList<Integer> supported = new LinkedList<Integer>();
LinkedList<Integer> supported = new LinkedList<>();
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
// No support for MD5
@@ -262,11 +262,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch (PgpKeyNotFoundException e) {
} catch (PgpKeyNotFoundException | PGPException e) {
// TODO: simply throw PGPException!
throw new PgpGeneralException("Error initializing signature!", e);
} catch (PGPException e) {
throw new PgpGeneralException("Error initializing signature!", e);
}
}

View File

@@ -18,27 +18,19 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
@@ -94,7 +86,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() {
return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -114,7 +106,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -133,7 +125,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
}
public HashMap<String,String> getLocalNotationData() {
HashMap<String,String> result = new HashMap<String,String>();
HashMap<String,String> result = new HashMap<>();
Iterator<PGPSignature> it = getRing().getPublicKey().getKeySignatures();
while (it.hasNext()) {
WrappedSignature sig = new WrappedSignature(it.next());

View File

@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.Log;
@@ -33,7 +32,7 @@ public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult
private boolean mSignatureOnly = false;
private String mPrimaryUserId;
private ArrayList<String> mUserIds = new ArrayList<String>();
private ArrayList<String> mUserIds = new ArrayList<>();
private long mKeyId;
// builder

View File

@@ -34,6 +34,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.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
@@ -134,7 +135,7 @@ public class PgpKeyOperation {
public PgpKeyOperation(Progressable progress) {
super();
if (progress != null) {
mProgress = new Stack<Progressable>();
mProgress = new Stack<>();
mProgress.push(progress);
}
}
@@ -287,13 +288,11 @@ public class PgpKeyOperation {
// build new key pair
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
} catch(NoSuchProviderException e) {
} catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
} catch(InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(PGPException e) {
Log.e(Constants.TAG, "internal pgp error", e);
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -388,6 +387,9 @@ public class PgpKeyOperation {
* with a passphrase fails, the operation will fail with an unlocking error. More specific
* handling of errors should be done in UI code!
*
* If the passphrase is null, only a restricted subset of operations will be available,
* namely stripping of subkeys and changing the protection mode of dummy keys.
*
*/
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
String passphrase) {
@@ -428,6 +430,11 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// If we have no passphrase, only allow restricted operation
if (passphrase == null) {
return internalRestricted(sKR, saveParcel, log);
}
// read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
@@ -478,7 +485,7 @@ public class PgpKeyOperation {
PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids
subProgressPush(15, 25);
subProgressPush(15, 23);
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size()));
@@ -495,7 +502,7 @@ public class PgpKeyOperation {
@SuppressWarnings("unchecked")
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
if (it != null) {
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
for (PGPSignature cert : new IterableIterator<>(it)) {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
// foreign certificate?! error error error
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
@@ -522,8 +529,37 @@ public class PgpKeyOperation {
}
subProgressPop();
// 2b. Add revocations for revoked user ids
subProgressPush(25, 40);
// 2b. Add certificates for new user ids
subProgressPush(23, 32);
for (int i = 0; i < saveParcel.mAddUserAttribute.size(); i++) {
progress(R.string.progress_modify_adduat, (i - 1) * (100 / saveParcel.mAddUserAttribute.size()));
WrappedUserAttribute attribute = saveParcel.mAddUserAttribute.get(i);
switch (attribute.getType()) {
// the 'none' type must not succeed
case WrappedUserAttribute.UAT_NONE:
log.add(LogType.MSG_MF_UAT_ERROR_EMPTY, indent);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
case WrappedUserAttribute.UAT_IMAGE:
log.add(LogType.MSG_MF_UAT_ADD_IMAGE, indent);
break;
default:
log.add(LogType.MSG_MF_UAT_ADD_UNKNOWN, indent);
break;
}
PGPUserAttributeSubpacketVector vector = attribute.getVector();
// generate and add new certificate
PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
masterPublicKey, vector);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
}
subProgressPop();
// 2c. Add revocations for revoked user ids
subProgressPush(32, 40);
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size()));
@@ -685,6 +721,27 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (change.mDummyStrip || change.mDummyDivert != null) {
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
if (change.mDummyStrip) {
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
} else {
// the serial number must be 16 bytes in length
if (change.mDummyDivert.length != 16) {
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
// This doesn't concern us any further
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
continue;
}
// expiry must not be in the past
if (change.mExpiry != null && change.mExpiry != 0 &&
new Date(change.mExpiry*1000).before(new Date())) {
@@ -775,30 +832,6 @@ public class PgpKeyOperation {
}
subProgressPop();
// 4c. For each subkey to be stripped... do so
subProgressPush(65, 70);
for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) {
progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size()));
long strip = saveParcel.mStripSubKeys.get(i);
log.add(LogType.MSG_MF_SUBKEY_STRIP,
indent, KeyFormattingUtils.convertKeyIdToHex(strip));
PGPSecretKey sKey = sKR.getSecretKey(strip);
if (sKey == null) {
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent+1, KeyFormattingUtils.convertKeyIdToHex(strip));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
subProgressPop();
// 5. Generate and add new subkeys
subProgressPush(70, 90);
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
@@ -907,6 +940,73 @@ public class PgpKeyOperation {
}
/** This method does the actual modifications in a keyring just like internal, except it
* supports only the subset of operations which require no passphrase, and will error
* otherwise.
*/
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
OperationLog log) {
int indent = 1;
progress(R.string.progress_modify, 0);
// Make sure the saveParcel includes only operations available without passphrae!
if (!saveParcel.isRestrictedOnly()) {
log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// Check if we were cancelled
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
}
// The only operation we can do here:
// 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card)
subProgressPush(50, 60);
for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size()));
SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
log.add(LogType.MSG_MF_SUBKEY_CHANGE,
indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
if (sKey == null) {
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (change.mDummyStrip || change.mDummyDivert != null) {
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
if (change.mDummyStrip) {
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
} else {
// the serial number must be 16 bytes in length
if (change.mDummyDivert.length != 16) {
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
}
// And we're done!
progress(R.string.progress_done, 100);
log.add(LogType.MSG_MF_SUCCESS, indent);
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
}
private static PGPSecretKeyRing applyNewUnlock(
PGPSecretKeyRing sKR,
PGPPublicKey masterPublicKey,
@@ -1174,6 +1274,26 @@ public class PgpKeyOperation {
return sGen.generateCertification(userId, pKey);
}
private static PGPSignature generateUserAttributeSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
PGPUserAttributeSubpacketVector vector)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
/* critical subpackets: we consider those important for a modern pgp implementation */
hashedPacketsGen.setSignatureCreationTime(true, new Date());
}
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(vector, pKey);
}
private static PGPSignature generateRevocationSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException {

View File

@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
@@ -30,6 +31,7 @@ import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants;
@@ -443,7 +445,7 @@ public class UncachedKeyRing {
}
}
ArrayList<String> processedUserIds = new ArrayList<String>();
ArrayList<String> processedUserIds = new ArrayList<>();
for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
@@ -468,7 +470,7 @@ public class UncachedKeyRing {
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId);
if (signaturesIt != null) {
for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
@@ -605,6 +607,170 @@ public class UncachedKeyRing {
return null;
}
ArrayList<PGPUserAttributeSubpacketVector> processedUserAttributes = new ArrayList<>();
for (PGPUserAttributeSubpacketVector userAttribute :
new IterableIterator<PGPUserAttributeSubpacketVector>(masterKey.getUserAttributes())) {
if (userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE) != null) {
log.add(LogType.MSG_KC_UAT_JPEG, indent);
} else {
log.add(LogType.MSG_KC_UAT_UNKNOWN, indent);
}
try {
indent += 1;
// check for duplicate user attributes
if (processedUserAttributes.contains(userAttribute)) {
log.add(LogType.MSG_KC_UAT_DUP, indent);
// strip out the first found user id with this name
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
processedUserAttributes.add(userAttribute);
PGPSignature selfCert = null;
revocation = null;
// look through signatures for this specific user id
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute);
if (signaturesIt != null) {
for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
int type = zert.getSignatureType();
if (type != PGPSignature.DEFAULT_CERTIFICATION
&& type != PGPSignature.NO_CERTIFICATION
&& type != PGPSignature.CASUAL_CERTIFICATION
&& type != PGPSignature.POSITIVE_CERTIFICATION
&& type != PGPSignature.CERTIFICATION_REVOCATION) {
log.add(LogType.MSG_KC_UAT_BAD_TYPE,
indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.getCreationTime().after(nowPlusOneDay)) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_TIME, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_LOCAL, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
// If this is a foreign signature, ...
if (certId != masterKeyId) {
// never mind any further for public keys, but remove them from secret ones
if (isSecret()) {
log.add(LogType.MSG_KC_UAT_FOREIGN,
indent, KeyFormattingUtils.convertKeyIdToHex(certId));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
}
continue;
}
// Otherwise, first make sure it checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userAttribute)) {
log.add(LogType.MSG_KC_UAT_BAD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogType.MSG_KC_UAT_BAD_ERR,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
switch (type) {
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
if (selfCert == null) {
selfCert = zert;
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, selfCert);
redundantCerts += 1;
selfCert = zert;
} else {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
// If there is a revocation certificate, and it's older than this, drop it
if (revocation != null
&& revocation.getCreationTime().before(selfCert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
revocation = null;
redundantCerts += 1;
}
break;
case PGPSignature.CERTIFICATION_REVOCATION:
// If this is older than the (latest) self cert, drop it
if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
continue;
}
// first revocation? remember it.
if (revocation == null) {
revocation = zert;
// more revocations? at least one is superfluous, then.
} else if (revocation.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
redundantCerts += 1;
revocation = zert;
} else {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
break;
}
}
}
// If no valid certificate (if only a revocation) remains, drop it
if (selfCert == null && revocation == null) {
log.add(LogType.MSG_KC_UAT_REMOVE,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
} finally {
indent -= 1;
}
}
// Replace modified key in the keyring
ring = replacePublicKey(ring, modified);
indent -= 1;
@@ -612,7 +778,7 @@ public class UncachedKeyRing {
}
// Keep track of ids we encountered so far
Set<Long> knownIds = new HashSet<Long>();
Set<Long> knownIds = new HashSet<>();
// Process all keys
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) {
@@ -852,8 +1018,8 @@ public class UncachedKeyRing {
/** This operation merges information from a different keyring, returning a combined
* UncachedKeyRing.
*
* The combined keyring contains the subkeys and user ids of both input keyrings, but it does
* not necessarily have the canonicalized property.
* The combined keyring contains the subkeys, user ids and user attributes of both input
* keyrings, but it does not necessarily have the canonicalized property.
*
* @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId
* @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as
@@ -876,7 +1042,7 @@ public class UncachedKeyRing {
}
// remember which certs we already added. this is cheaper than semantic deduplication
Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() {
Set<byte[]> certs = new TreeSet<>(new Comparator<byte[]>() {
public int compare(byte[] left, byte[] right) {
// check for length equality
if (left.length != right.length) {
@@ -958,7 +1124,7 @@ public class UncachedKeyRing {
if (signaturesIt == null) {
continue;
}
for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) {
for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
@@ -973,6 +1139,32 @@ public class UncachedKeyRing {
modified = PGPPublicKey.addCertification(modified, rawUserId, cert);
}
}
// Copy over all user attribute certificates
for (PGPUserAttributeSubpacketVector vector :
new IterableIterator<PGPUserAttributeSubpacketVector>(key.getUserAttributes())) {
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = key.getSignaturesForUserAttribute(vector);
// no signatures for this user attribute attribute, skip it
if (signaturesIt == null) {
continue;
}
for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
}
byte[] encoded = cert.getEncoded();
// Known cert, skip it
if (certs.contains(encoded)) {
continue;
}
newCerts += 1;
certs.add(encoded);
modified = PGPPublicKey.addCertification(modified, vector, cert);
}
}
// If anything changed, save the updated (sub)key
if (modified != resultKey) {
result = replacePublicKey(result, modified);

View File

@@ -20,10 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ECPublicBCPGKey;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -135,7 +135,7 @@ public class UncachedPublicKey {
continue;
}
for (PGPSignature sig : new IterableIterator<PGPSignature>(signaturesIt)) {
for (PGPSignature sig : new IterableIterator<>(signaturesIt)) {
try {
// if this is a revocation, this is not the user id
@@ -199,7 +199,7 @@ public class UncachedPublicKey {
}
public ArrayList<String> getUnorderedUserIds() {
ArrayList<String> userIds = new ArrayList<String>();
ArrayList<String> userIds = new ArrayList<>();
for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
// use our decoding method
userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId));
@@ -208,13 +208,22 @@ public class UncachedPublicKey {
}
public ArrayList<byte[]> getUnorderedRawUserIds() {
ArrayList<byte[]> userIds = new ArrayList<byte[]>();
ArrayList<byte[]> userIds = new ArrayList<>();
for (byte[] userId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
userIds.add(userId);
}
return userIds;
}
public ArrayList<WrappedUserAttribute> getUnorderedUserAttributes() {
ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<>();
for (PGPUserAttributeSubpacketVector userAttribute :
new IterableIterator<PGPUserAttributeSubpacketVector>(mPublicKey.getUserAttributes())) {
userAttributes.add(new WrappedUserAttribute(userAttribute));
}
return userAttributes;
}
public boolean isElGamalEncrypt() {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
}
@@ -270,33 +279,83 @@ public class UncachedPublicKey {
}
}
public Iterator<WrappedSignature> getSignaturesForUserAttribute(WrappedUserAttribute attribute) {
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForUserAttribute(attribute.getVector());
if (it != null) {
return new Iterator<WrappedSignature>() {
public void remove() {
it.remove();
}
public WrappedSignature next() {
return new WrappedSignature(it.next());
}
public boolean hasNext() {
return it.hasNext();
}
};
} else {
return null;
}
}
/** Get all key usage flags.
* If at least one key flag subpacket is present return these. If no
* subpacket is present it returns null.
*
* Note that this method has package visiblity because it is used in test
* cases. Certificates of UncachedPublicKey instances can NOT be assumed to
* be verified, so the result of this method should not be used in other
* places!
* be verified or even by the correct key, so the result of this method
* should never be used in other places!
*/
@SuppressWarnings("unchecked")
Integer getKeyUsage() {
if (mCacheUsage == null) {
PGPSignature mostRecentSig = null;
for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignatures())) {
if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) {
continue;
}
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
switch (sig.getSignatureType()) {
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.SUBKEY_BINDING:
break;
// if this is not one of the above types, don't care
default:
continue;
}
// If we have no sig yet, take the first we can get
if (mostRecentSig == null) {
mostRecentSig = sig;
continue;
}
// If the new sig is less recent, skip it
if (mostRecentSig.getCreationTime().after(sig.getCreationTime())) {
continue;
}
// Otherwise, note it down as the new "most recent" one
mostRecentSig = sig;
}
// Initialize to 0 as cached but empty value, if there is no sig (can't happen
// for canonicalized keyring), or there is no KEY_FLAGS packet in the sig
mCacheUsage = 0;
if (mostRecentSig != null) {
// If a mostRecentSig has been found, (cache and) return its flags
PGPSignatureSubpacketVector hashed = mostRecentSig.getHashedSubPackets();
if (hashed != null && hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS) != null) {
// init if at least one key flag subpacket has been found
if (mCacheUsage == null) {
mCacheUsage = 0;
}
mCacheUsage |= hashed.getKeyFlags();
mCacheUsage = hashed.getKeyFlags();
}
}
}
return mCacheUsage;
}
}

View File

@@ -29,13 +29,13 @@ import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -79,7 +79,7 @@ public class WrappedSignature {
}
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>();
ArrayList<WrappedSignature> sigs = new ArrayList<>();
if (!mSig.hasSubpackets()) {
return sigs;
}
@@ -199,12 +199,23 @@ public class WrappedSignature {
}
}
boolean verifySignature(PGPPublicKey key, PGPUserAttributeSubpacketVector attribute) throws PgpGeneralException {
try {
return mSig.verifyCertification(attribute, key);
} catch (PGPException e) {
throw new PgpGeneralException("Error!", e);
}
}
public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), rawUserId);
}
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid);
}
public boolean verifySignature(UncachedPublicKey key, WrappedUserAttribute attribute) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), attribute.getVector());
}
public static WrappedSignature fromBytes(byte[] data) {
PGPObjectFactory factory = new PGPObjectFactory(data);
@@ -243,7 +254,7 @@ public class WrappedSignature {
}
public HashMap<String,String> getNotation() {
HashMap<String,String> result = new HashMap<String,String>();
HashMap<String,String> result = new HashMap<>();
// If there is any notation data
if (mSig.getHashedSubPackets() != null

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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 org.spongycastle.bcpg.BCPGInputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.bcpg.Packet;
import org.spongycastle.bcpg.UserAttributePacket;
import org.spongycastle.bcpg.UserAttributeSubpacket;
import org.spongycastle.bcpg.UserAttributeSubpacketInputStream;
import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
public class WrappedUserAttribute implements Serializable {
public static final int UAT_NONE = 0;
public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE;
private PGPUserAttributeSubpacketVector mVector;
WrappedUserAttribute(PGPUserAttributeSubpacketVector vector) {
mVector = vector;
}
PGPUserAttributeSubpacketVector getVector() {
return mVector;
}
public int getType() {
UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
if (subpackets.length > 0) {
return subpackets[0].getType();
}
return 0;
}
public static WrappedUserAttribute fromSubpacket (int type, byte[] data) {
UserAttributeSubpacket subpacket = new UserAttributeSubpacket(type, data);
PGPUserAttributeSubpacketVector vector = new PGPUserAttributeSubpacketVector(
new UserAttributeSubpacket[] { subpacket });
return new WrappedUserAttribute(vector);
}
public byte[] getEncoded () throws IOException {
UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (UserAttributeSubpacket subpacket : subpackets) {
subpacket.encode(out);
}
return out.toByteArray();
}
public static WrappedUserAttribute fromData (byte[] data) throws IOException {
UserAttributeSubpacketInputStream in =
new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data));
ArrayList<UserAttributeSubpacket> list = new ArrayList<UserAttributeSubpacket>();
while (in.available() > 0) {
list.add(in.readPacket());
}
UserAttributeSubpacket[] result = new UserAttributeSubpacket[list.size()];
list.toArray(result);
return new WrappedUserAttribute(
new PGPUserAttributeSubpacketVector(result));
}
/** Writes this object to an ObjectOutputStream. */
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BCPGOutputStream bcpg = new BCPGOutputStream(baos);
bcpg.writePacket(new UserAttributePacket(mVector.toSubpacketArray()));
out.writeObject(baos.toByteArray());
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
byte[] data = (byte[]) in.readObject();
BCPGInputStream bcpg = new BCPGInputStream(new ByteArrayInputStream(data));
Packet p = bcpg.readPacket();
if ( ! UserAttributePacket.class.isInstance(p)) {
throw new IOException("Could not decode UserAttributePacket!");
}
mVector = new PGPUserAttributeSubpacketVector(((UserAttributePacket) p).getSubpackets());
}
private void readObjectNoData() throws ObjectStreamException {
}
@Override
public boolean equals(Object o) {
if (!WrappedUserAttribute.class.isInstance(o)) {
return false;
}
return mVector.equals(((WrappedUserAttribute) o).mVector);
}
}