diff options
Diffstat (limited to 'OpenKeychain/src/main')
5 files changed, 366 insertions, 1 deletions
| 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<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<PGPSignature>(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<String> mFlags; +    final HashMap<String,String> mParams; + +    protected LinkedIdentity(byte[] data, String nonce, Set<String> flags, +                             HashMap<String, String> 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<String> flags, +                   HashMap<String, String> 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<Entry<String, String>> it = mParams.entrySet().iterator(); +            while (it.hasNext()) { +                if (!first) { +                    b.append(";"); +                } +                first = false; +                Entry<String, String> 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<String> flags = new HashSet<String>(); +        HashMap<String,String> params = new HashMap<String,String>(); +        { +            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"; +    } + +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 301de0be1..4b5c46a5f 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -760,8 +760,23 @@      <string name="msg_kc_uid_revoke_old">"Removing outdated revocation certificate for user ID '%s'"</string>      <string name="msg_kc_uid_no_cert">"No valid self-certificate found for user ID '%s', removing from ring"</string>      <string name="msg_kc_uid_remove">"Removing invalid user ID '%s'"</string> -    <string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The secret key contained two of them. This may result in missing certificates!"</string> +    <string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The keyring contained two of them. This may result in missing certificates!"</string>      <string name="msg_kc_uid_warn_encoding">"User id does not verify as UTF-8!"</string> +    <string name="msg_kc_uat_jpeg">"Processing user attribute of type JPEG"</string> +    <string name="msg_kc_uat_unknown">"Processing user attribute of unknown type"</string> +    <string name="msg_kc_uat_bad_err">"Removing bad self certificate for user attribute"</string> +    <string name="msg_kc_uat_bad_local">"Removing user attribute certificate with 'local' flag"</string> +    <string name="msg_kc_uat_bad_time">"Removing user attribute with future timestamp"</string> +    <string name="msg_kc_uat_bad_type">"Removing user attribute certificate of unknown type (%s)"</string> +    <string name="msg_kc_uat_bad">"Removing bad self certificate for user attribute"</string> +    <string name="msg_kc_uat_cert_dup">"Removing outdated self certificate for user attribute"</string> +    <string name="msg_kc_uat_dup">"Removing duplicate user attribute. The keyring contained two of them. This may result in missing certificates!"</string> +    <string name="msg_kc_uat_foreign">"Removing foreign user attribute certificate by"</string> +    <string name="msg_kc_uat_revoke_dup">"Removing redundant revocation certificate for user attribute"</string> +    <string name="msg_kc_uat_revoke_old">"Removing outdated revocation certificate for user attribute"</string> +    <string name="msg_kc_uat_no_cert">"No valid self-certificate found for user attribute, removing from ring"</string> +    <string name="msg_kc_uat_remove">"Removing invalid user attribute"</string> +    <string name="msg_kc_uat_warn_encoding">"User id does not verify as UTF-8!"</string>      <!-- Keyring merging log entries -->      <string name="msg_mg_error_secret_dummy">"New public subkey found, but secret subkey dummy generation is not supported!"</string> | 
