From fc3397de5dabf23fac0de3fd297bcc4125864861 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 12 Jan 2015 20:03:03 +0100 Subject: add support for user attributes (during canonicalization) --- .../operations/results/OperationResult.java | 15 ++ .../keychain/pgp/UncachedKeyRing.java | 166 +++++++++++++++++++++ .../keychain/pgp/WrappedSignature.java | 9 ++ .../keychain/pgp/affirmation/LinkedIdentity.java | 160 ++++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 1388c0eac..5884dd2d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -416,6 +416,21 @@ public abstract class OperationResult implements Parcelable { MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old), MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove), MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding), + MSG_KC_UAT_JPEG (LogLevel.DEBUG, R.string.msg_kc_uat_jpeg), + MSG_KC_UAT_UNKNOWN (LogLevel.DEBUG, R.string.msg_kc_uat_unknown), + MSG_KC_UAT_BAD_ERR (LogLevel.WARN, R.string.msg_kc_uat_bad_err), + MSG_KC_UAT_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_uat_bad_local), + MSG_KC_UAT_BAD_TIME (LogLevel.WARN, R.string.msg_kc_uat_bad_time), + MSG_KC_UAT_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_uat_bad_type), + MSG_KC_UAT_BAD (LogLevel.WARN, R.string.msg_kc_uat_bad), + MSG_KC_UAT_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_cert_dup), + MSG_KC_UAT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_dup), + MSG_KC_UAT_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uat_foreign), + MSG_KC_UAT_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uat_no_cert), + MSG_KC_UAT_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_dup), + MSG_KC_UAT_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_old), + MSG_KC_UAT_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uat_remove), + MSG_KC_UAT_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uat_warn_encoding), // keyring consolidation diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index a445e161f..404228a3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -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; @@ -605,6 +607,170 @@ public class UncachedKeyRing { return null; } + ArrayList processedUserAttributes = new ArrayList<>(); + for (PGPUserAttributeSubpacketVector userAttribute : + new IterableIterator(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 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index c395ca52d..3dc02d3ed 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -29,6 +29,7 @@ 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; @@ -199,6 +200,14 @@ 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); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java new file mode 100644 index 000000000..dcbaa1c1c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java @@ -0,0 +1,160 @@ +package org.sufficientlysecure.keychain.pgp.affirmation; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.util.Strings; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; + +public class LinkedIdentity { + + protected byte[] mData; + public final String mNonce; + public final URI mSubUri; + final Set mFlags; + final HashMap mParams; + + protected LinkedIdentity(byte[] data, String nonce, Set flags, + HashMap params, URI subUri) { + if ( ! nonce.matches("[0-9a-zA-Z]+")) { + throw new AssertionError("bug: nonce must be hexstring!"); + } + + mData = data; + mNonce = nonce; + mFlags = flags; + mParams = params; + mSubUri = subUri; + } + + LinkedIdentity(String nonce, Set flags, + HashMap params, URI subUri) { + this(null, nonce, flags, params, subUri); + } + + public byte[] encode() { + if (mData != null) { + return mData; + } + + StringBuilder b = new StringBuilder(); + b.append("pgpid:"); + + // add flags + if (mFlags != null) { + boolean first = true; + for (String flag : mFlags) { + if (!first) { + b.append(";"); + } + first = false; + b.append(flag); + } + } + + // add parameters + if (mParams != null) { + boolean first = true; + Iterator> it = mParams.entrySet().iterator(); + while (it.hasNext()) { + if (!first) { + b.append(";"); + } + first = false; + Entry entry = it.next(); + b.append(entry.getKey()).append("=").append(entry.getValue()); + } + } + + b.append("@"); + b.append(mSubUri); + + byte[] nonceBytes = Hex.decode(mNonce); + byte[] data = Strings.toUTF8ByteArray(b.toString()); + + byte[] result = new byte[data.length+12]; + System.arraycopy(nonceBytes, 0, result, 0, 12); + System.arraycopy(data, 0, result, 12, result.length); + + return result; + } + + /** This method parses an affirmation from a UserAttributeSubpacket, or returns null if the + * subpacket can not be parsed as a valid affirmation. + */ + public static LinkedIdentity parseAffirmation(UserAttributeSubpacket subpacket) { + if (subpacket.getType() != 100) { + return null; + } + + byte[] data = subpacket.getData(); + String nonce = Hex.toHexString(data, 0, 12); + + try { + return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 12, data.length))); + + } catch (IllegalArgumentException e) { + Log.e(Constants.TAG, "error parsing uri in (suspected) affirmation packet"); + return null; + } + } + + protected static LinkedIdentity parseUri (String nonce, String uriString) { + URI uri = URI.create(uriString); + + if ("pgpid".equals(uri.getScheme())) { + Log.e(Constants.TAG, "unknown uri scheme in (suspected) affirmation packet"); + return null; + } + + if (!uri.isOpaque()) { + Log.e(Constants.TAG, "non-opaque uri in (suspected) affirmation packet"); + return null; + } + + String specific = uri.getSchemeSpecificPart(); + if (!specific.contains("@")) { + Log.e(Constants.TAG, "unknown uri scheme in affirmation packet"); + return null; + } + + String[] pieces = specific.split("@", 2); + URI subUri = URI.create(pieces[1]); + + Set flags = new HashSet(); + HashMap params = new HashMap(); + { + String[] rawParams = pieces[0].split(";"); + for (String param : rawParams) { + String[] p = param.split("=", 2); + if (p.length == 1) { + flags.add(param); + } else { + params.put(p[0], p[1]); + } + } + } + + return new LinkedIdentity(nonce, flags, params, subUri); + + } + + public static String generateNonce() { + // TODO make this actually random + // byte[] data = new byte[96]; + // new SecureRandom().nextBytes(data); + // return Hex.toHexString(data); + + // debug for now + return "0123456789ABCDEF01234567"; + } + +} -- cgit v1.2.3