Example usage for org.bouncycastle.openpgp PGPSignature getHashedSubPackets

List of usage examples for org.bouncycastle.openpgp PGPSignature getHashedSubPackets

Introduction

In this page you can find the example usage for org.bouncycastle.openpgp PGPSignature getHashedSubPackets.

Prototype

public PGPSignatureSubpacketVector getHashedSubPackets() 

Source Link

Usage

From source file:com.google.e2e.bcdriver.KeyChecker.java

License:Apache License

private static final boolean isGoodBackSignature(PGPSignature sig, PGPPublicKey signer, PGPPublicKey target,
        StringBuilder errors) throws PGPException, SignatureException, IOException {

    SignatureSubpacket esigpack = null;//from   w  w w.j  ava 2 s  .c  om

    // Prefer to get it from the hashed subpacket.
    PGPSignatureSubpacketVector svec = sig.getHashedSubPackets();
    if (svec != null) {
        esigpack = svec.getSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE);
    }

    if (esigpack == null) {
        svec = sig.getUnhashedSubPackets();
        if (svec != null) {
            esigpack = svec.getSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE);
        }
    }

    if (esigpack == null) {
        errors.append("Rejecting " + niceSig(sig) + " for subkey " + nicePk(target)
                + " because it doesn't have a cross-certification.\n"
                + "See https://www.gnupg.org/faq/subkey-cross-certify.html\n");
        return false;
    }

    // Unfortunately, since PGPSignature(byte[]) is not public, we
    // have to go through this ugly contortion to get a signature.

    ByteArrayOutputStream baout = new ByteArrayOutputStream();
    // dump out an old-style header.
    int hdr = 0x80 | (PacketTags.SIGNATURE << 2);
    int len = esigpack.getData().length;
    if (len <= 0xff) {
        baout.write(hdr);
        baout.write(len);
    } else if (len <= 0xffff) {
        baout.write(hdr | 0x01);
        baout.write((len >> 8) & 0xff);
        baout.write(len & 0xff);
    } else {
        baout.write(hdr | 0x02);
        baout.write((len >> 24) & 0xff);
        baout.write((len >> 16) & 0xff);
        baout.write((len >> 8) & 0xff);
        baout.write(len & 0xff);
    }

    baout.write(esigpack.getData());
    baout.close();

    PGPObjectFactory fact = new PGPObjectFactory(new ByteArrayInputStream(baout.toByteArray()),
            new BcKeyFingerprintCalculator());

    Object obj = fact.nextObject();

    if (!(obj instanceof PGPSignatureList)) {
        errors.append("Rejecting " + niceSig(sig) + " for subkey " + nicePk(target)
                + " because no usable embedded signature is available.\n");
        return false;
    }
    PGPSignatureList esiglist = (PGPSignatureList) obj;
    if (esiglist.size() != 1) {
        errors.append("Rejecting " + niceSig(sig) + " for subkey " + nicePk(target)
                + " because no usable embedded signature is available.\n");
        return false;
    }

    PGPSignature esig = esiglist.get(0);
    if (esig.getSignatureType() != PGPSignature.PRIMARYKEY_BINDING) {
        errors.append("Rejecting " + niceSig(sig) + " for subkey " + nicePk(target) + " because the embedded "
                + niceSig(esig) + " is not a proper backsignature.\n");
        return false;
    }

    esig.init(new BcPGPContentVerifierBuilderProvider(), target);

    return esig.verifyCertification(signer, target) && isSignatureCurrent(esig, errors);
}

From source file:com.google.e2e.bcdriver.KeyChecker.java

License:Apache License

private static final long getSignatureTimestamp(PGPSignature sig, StringBuilder errors) {
    long ts = sig.getCreationTime().getTime();
    // Work-around bouncycastle not indicating lack of timestamp as a
    // hashed subpacket.
    if (sig.getVersion() == 4) {
        // Make sure we actually have a timestamp packet in
        // the hashed section.
        PGPSignatureSubpacketVector svec = sig.getHashedSubPackets();
        if (svec == null) {
            errors.append(niceSig(sig) + " is missing a creation timestamp.\n");
            return -1L;
        }//from ww w .  j  a  va  2 s  . c  om
        SignatureCreationTime tspack = (SignatureCreationTime) svec
                .getSubpacket(SignatureSubpacketTags.CREATION_TIME);
        if (tspack == null) {
            errors.append(niceSig(sig) + " is missing a creation timestamp.\n");
            return -1L;
        }
        ts = tspack.getTime().getTime();
    }
    return ts;
}

From source file:com.google.e2e.bcdriver.KeyChecker.java

License:Apache License

private static final boolean isSignatureCurrent(PGPSignature sig, StringBuilder errors) {

    long ts = getSignatureTimestamp(sig, errors);
    if (ts < 0) {
        return false;
    }//from ww  w .jav  a  2s .  c  om
    // Timestamp should not be in the future.
    if (ts > (System.currentTimeMillis() + ACCEPTABLE_DELTA_MSEC)) {
        errors.append(niceSig(sig) + " in the future? (" + new Date(ts) + ")\n");
        return false;
    }
    if (ts < 0) {
        errors.append(niceSig(sig) + " is negative? (" + ts + ")\n");
        return false;
    }

    // Check if the signature or key has expired.
    PGPSignatureSubpacketVector svec = sig.getHashedSubPackets();
    if (svec != null) {
        SignatureExpirationTime tspack = (SignatureExpirationTime) svec
                .getSubpacket(SignatureSubpacketTags.EXPIRE_TIME);
        if (tspack != null) {
            long expDelta = tspack.getTime() * 1000L;
            if (!acceptableInterval(sig, ts, expDelta, errors)) {
                return false;
            }
        }
        // If there's a key-expiration subpacket, also check that.
        KeyExpirationTime ket = (KeyExpirationTime) svec.getSubpacket(SignatureSubpacketTags.KEY_EXPIRE_TIME);
        if (ket != null) {
            long expDelta = ket.getTime() * 1000L;
            if (!acceptableInterval(sig, ts, expDelta, errors)) {
                return false;
            }
        }
    }

    return true;
}

From source file:com.google.e2e.bcdriver.Util.java

License:Apache License

static final boolean hasKeyFlag(PGPSignature sig, int flag) {
    PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
    if (hashed == null) {
        return false;
    }/*from ww  w.  ja v a2  s  .  co  m*/

    KeyFlags flags = (KeyFlags) hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS);
    if (flags == null) {
        return false;
    }
    return ((flags.getFlags() & flag) != 0);
}

From source file:com.google.gerrit.gpg.PublicKeyChecker.java

License:Apache License

private CheckResult checkTrustSubpacket(PGPSignature sig, int depth) {
    SignatureSubpacket trustSub = sig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.TRUST_SIG);
    if (trustSub == null || trustSub.getData().length != 2) {
        return new CheckResult("Certification is missing trust information");
    }/* w w  w.  java 2s .  com*/
    byte amount = trustSub.getData()[1];
    if (amount < COMPLETE_TRUST) {
        return new CheckResult("Certification does not fully trust key");
    }
    byte level = trustSub.getData()[0];
    int required = depth + 1;
    if (level < required) {
        return new CheckResult(
                "Certification trusts to depth " + level + ", but depth " + required + " is required");
    }
    return CheckResult.OK;
}

From source file:com.google.gerrit.gpg.PublicKeyCheckerTest.java

License:Apache License

private PGPPublicKeyRing removeRevokers(PGPPublicKeyRing kr) {
    PGPPublicKey k = kr.getPublicKey();//from   www  .j a v a  2s.  c o m
    @SuppressWarnings("unchecked")
    Iterator<PGPSignature> sigs = k.getSignaturesOfType(DIRECT_KEY);
    while (sigs.hasNext()) {
        PGPSignature sig = sigs.next();
        if (sig.getHashedSubPackets().hasSubpacket(REVOCATION_KEY)) {
            k = PGPPublicKey.removeCertification(k, sig);
        }
    }
    return PGPPublicKeyRing.insertPublicKey(kr, k);
}

From source file:org.sufficientlysecure.keychain.pgp.PgpKeyOperation.java

License:Open Source License

private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags,
        long masterKeyExpiry, CryptoInputParcel cryptoInput, SaveKeyringParcel saveParcel, OperationLog log,
        int indent) {

    SecurityTokenSignOperationsBuilder nfcSignOps = new SecurityTokenSignOperationsBuilder(
            cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(), masterSecretKey.getKeyID());
    SecurityTokenKeyToCardOperationsBuilder nfcKeyToCardOps = new SecurityTokenKeyToCardOperationsBuilder(
            masterSecretKey.getKeyID());

    progress(R.string.progress_modify, 0);

    PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();

    PGPPrivateKey masterPrivateKey;/*  w  ww. jav  a 2  s. c om*/

    if (isDivertToCard(masterSecretKey)) {
        masterPrivateKey = null;
        log.add(LogType.MSG_MF_DIVERT, indent);
    } else {

        // 1. Unlock private key
        progress(R.string.progress_modify_unlock, 10);
        log.add(LogType.MSG_MF_UNLOCK, indent);
        {
            try {
                PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
                        .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
                        .build(cryptoInput.getPassphrase().getCharArray());
                masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
            } catch (PGPException e) {
                log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }
        }
    }

    try {

        // Check if we were cancelled
        if (checkCancelled()) {
            log.add(LogType.MSG_OPERATION_CANCELLED, indent);
            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
        }

        { // work on master secret key

            PGPPublicKey modifiedPublicKey = masterPublicKey;

            // 2a. Add certificates for new user ids
            subProgressPush(15, 23);
            for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {

                progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size()));
                String userId = saveParcel.mAddUserIds.get(i);
                log.add(LogType.MSG_MF_UID_ADD, indent, userId);

                if ("".equals(userId)) {
                    log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }

                // this operation supersedes all previous binding and revocation certificates,
                // so remove those to retain assertions from canonicalization for later operations
                @SuppressWarnings("unchecked")
                Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
                if (it != null) {
                    for (PGPSignature cert : new IterableIterator<>(it)) {
                        if (cert.getKeyID() != masterPublicKey.getKeyID()) {
                            // foreign certificate?! error error error
                            log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
                            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                        }
                        if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
                                || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
                            modifiedPublicKey = PGPPublicKey.removeCertification(modifiedPublicKey, userId,
                                    cert);
                        }
                    }
                }

                // if it's supposed to be primary, we can do that here as well
                boolean isPrimary = saveParcel.mChangePrimaryUserId != null
                        && userId.equals(saveParcel.mChangePrimaryUserId);
                // generate and add new certificate
                try {
                    PGPSignature cert = generateUserIdSignature(
                            getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                            masterPrivateKey, masterPublicKey, userId, isPrimary, masterKeyFlags,
                            masterKeyExpiry);
                    modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
                } catch (NfcInteractionNeeded e) {
                    nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
                }
            }
            subProgressPop();

            // 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
                try {
                    PGPSignature cert = generateUserAttributeSignature(
                            getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                            masterPrivateKey, masterPublicKey, vector, masterKeyFlags, masterKeyExpiry);
                    modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
                } catch (NfcInteractionNeeded e) {
                    nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
                }
            }
            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()));
                String userId = saveParcel.mRevokeUserIds.get(i);
                log.add(LogType.MSG_MF_UID_REVOKE, indent, userId);

                // Make sure the user id exists (yes these are 10 LoC in Java!)
                boolean exists = false;
                //noinspection unchecked
                for (String uid : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
                    if (userId.equals(uid)) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) {
                    log.add(LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent);
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }

                // a duplicate revocation will be removed during canonicalization, so no need to
                // take care of that here.
                try {
                    PGPSignature cert = generateRevocationSignature(
                            getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                            masterPrivateKey, masterPublicKey, userId);
                    modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
                } catch (NfcInteractionNeeded e) {
                    nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
                }
            }
            subProgressPop();

            // 3. If primary user id changed, generate new certificates for both old and new
            if (saveParcel.mChangePrimaryUserId != null) {
                progress(R.string.progress_modify_primaryuid, 40);

                // keep track if we actually changed one
                boolean ok = false;
                log.add(LogType.MSG_MF_UID_PRIMARY, indent, saveParcel.mChangePrimaryUserId);
                indent += 1;

                // we work on the modifiedPublicKey here, to respect new or newly revoked uids
                // noinspection unchecked
                for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
                    boolean isRevoked = false;
                    PGPSignature currentCert = null;
                    // noinspection unchecked
                    for (PGPSignature cert : new IterableIterator<PGPSignature>(
                            modifiedPublicKey.getSignaturesForID(userId))) {
                        if (cert.getKeyID() != masterPublicKey.getKeyID()) {
                            // foreign certificate?! error error error
                            log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
                            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                        }
                        // we know from canonicalization that if there is any revocation here, it
                        // is valid and not superseded by a newer certification.
                        if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
                            isRevoked = true;
                            continue;
                        }
                        // we know from canonicalization that there is only one binding
                        // certification here, so we can just work with the first one.
                        if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION
                                || cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
                            currentCert = cert;
                        }
                    }

                    if (currentCert == null) {
                        // no certificate found?! error error error
                        log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
                        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                    }

                    // we definitely should not update certifications of revoked keys, so just leave it.
                    if (isRevoked) {
                        // revoked user ids cannot be primary!
                        if (userId.equals(saveParcel.mChangePrimaryUserId)) {
                            log.add(LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
                            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                        }
                        continue;
                    }

                    // if this is~ the/a primary user id
                    if (currentCert.getHashedSubPackets() != null
                            && currentCert.getHashedSubPackets().isPrimaryUserID()) {
                        // if it's the one we want, just leave it as is
                        if (userId.equals(saveParcel.mChangePrimaryUserId)) {
                            ok = true;
                            continue;
                        }
                        // otherwise, generate new non-primary certification
                        log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
                        modifiedPublicKey = PGPPublicKey.removeCertification(modifiedPublicKey, userId,
                                currentCert);
                        try {
                            PGPSignature newCert = generateUserIdSignature(
                                    getSignatureGenerator(masterSecretKey, cryptoInput),
                                    cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId,
                                    false, masterKeyFlags, masterKeyExpiry);
                            modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId,
                                    newCert);
                        } catch (NfcInteractionNeeded e) {
                            nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
                        }

                        continue;
                    }

                    // if we are here, this is not currently a primary user id

                    // if it should be
                    if (userId.equals(saveParcel.mChangePrimaryUserId)) {
                        // add shiny new primary user id certificate
                        log.add(LogType.MSG_MF_PRIMARY_NEW, indent);
                        modifiedPublicKey = PGPPublicKey.removeCertification(modifiedPublicKey, userId,
                                currentCert);
                        try {
                            PGPSignature newCert = generateUserIdSignature(
                                    getSignatureGenerator(masterSecretKey, cryptoInput),
                                    cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId,
                                    true, masterKeyFlags, masterKeyExpiry);
                            modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId,
                                    newCert);
                        } catch (NfcInteractionNeeded e) {
                            nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
                        }
                        ok = true;
                    }

                    // user id is not primary and is not supposed to be - nothing to do here.

                }

                indent -= 1;

                if (!ok) {
                    log.add(LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }
            }

            // Update the secret key ring
            if (modifiedPublicKey != masterPublicKey) {
                masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, modifiedPublicKey);
                masterPublicKey = modifiedPublicKey;
                sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
            }

        }

        // Check if we were cancelled - again
        if (checkCancelled()) {
            log.add(LogType.MSG_OPERATION_CANCELLED, indent);
            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
        }

        // 4a. For each subkey change, generate new subkey binding certificate
        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) {
                // 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);
            } else if (change.mMoveKeyToSecurityToken) {
                if (checkSecurityTokenCompatibility(sKey, log, indent + 1)) {
                    log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1,
                            KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
                    nfcKeyToCardOps.addSubkey(change.mKeyId);
                } else {
                    // Appropriate log message already set by checkSecurityTokenCompatibility
                    return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
                }
            } else if (change.mSecurityTokenSerialNo != null) {
                // NOTE: Does this code get executed? Or always handled in internalRestricted?
                if (change.mSecurityTokenSerialNo.length != 16) {
                    log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, indent + 1,
                            KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }
                log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
                        KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
                        Hex.toHexString(change.mSecurityTokenSerialNo, 8, 6));
                sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mSecurityTokenSerialNo);
                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())) {
                log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent + 1,
                        KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            // if this is the master key, update uid certificates instead
            if (change.mKeyId == masterPublicKey.getKeyID()) {
                int flags = change.mFlags == null ? masterKeyFlags : change.mFlags;
                long expiry = change.mExpiry == null ? masterKeyExpiry : change.mExpiry;

                if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
                    log.add(LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1);
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }

                PGPPublicKey pKey = updateMasterCertificates(masterSecretKey, masterPrivateKey, masterPublicKey,
                        flags, expiry, cryptoInput, nfcSignOps, indent, log);
                if (pKey == null) {
                    // error log entry has already been added by updateMasterCertificates itself
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }
                masterSecretKey = PGPSecretKey.replacePublicKey(sKey, pKey);
                masterPublicKey = pKey;
                sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
                continue;
            }

            // otherwise, continue working on the public key
            PGPPublicKey pKey = sKey.getPublicKey();

            // keep old flags, or replace with new ones
            int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
            long expiry;
            if (change.mExpiry == null) {
                long valid = pKey.getValidSeconds();
                expiry = valid == 0 ? 0 : pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds();
            } else {
                expiry = change.mExpiry;
            }

            // drop all old signatures, they will be superseded by the new one
            //noinspection unchecked
            for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) {
                // special case: if there is a revocation, don't use expiry from before
                if ((change.mExpiry == null || change.mExpiry == 0L)
                        && sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
                    expiry = 0;
                }
                pKey = PGPPublicKey.removeCertification(pKey, sig);
            }

            PGPPrivateKey subPrivateKey;
            if (!isDivertToCard(sKey)) {
                PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
                        .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
                        .build(cryptoInput.getPassphrase().getCharArray());
                subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
                // super special case: subkey is allowed to sign, but isn't available
                if (subPrivateKey == null) {
                    log.add(LogType.MSG_MF_ERROR_SUB_STRIPPED, indent + 1,
                            KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
                }
            } else {
                subPrivateKey = null;
            }
            try {
                PGPSignature sig = generateSubkeyBindingSignature(
                        getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                        masterPublicKey, masterPrivateKey, getSignatureGenerator(sKey, cryptoInput),
                        subPrivateKey, pKey, flags, expiry);

                // generate and add new signature
                pKey = PGPPublicKey.addCertification(pKey, sig);
                sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
            } catch (NfcInteractionNeeded e) {
                nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
            }

        }
        subProgressPop();

        // 4b. For each subkey revocation, generate new subkey revocation certificate
        subProgressPush(60, 65);
        for (int i = 0; i < saveParcel.mRevokeSubKeys.size(); i++) {

            progress(R.string.progress_modify_subkeyrevoke, (i - 1) * (100 / saveParcel.mRevokeSubKeys.size()));
            long revocation = saveParcel.mRevokeSubKeys.get(i);
            log.add(LogType.MSG_MF_SUBKEY_REVOKE, indent, KeyFormattingUtils.convertKeyIdToHex(revocation));

            PGPSecretKey sKey = sKR.getSecretKey(revocation);
            if (sKey == null) {
                log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent + 1,
                        KeyFormattingUtils.convertKeyIdToHex(revocation));
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }
            PGPPublicKey pKey = sKey.getPublicKey();

            // generate and add new signature
            try {
                PGPSignature sig = generateRevocationSignature(
                        getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                        masterPublicKey, masterPrivateKey, pKey);

                pKey = PGPPublicKey.addCertification(pKey, sig);
                sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
            } catch (NfcInteractionNeeded e) {
                nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
            }
        }
        subProgressPop();

        // 5. Generate and add new subkeys
        subProgressPush(70, 90);
        for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {

            // Check if we were cancelled - again. This operation is expensive so we do it each loop.
            if (checkCancelled()) {
                log.add(LogType.MSG_OPERATION_CANCELLED, indent);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
            }

            progress(R.string.progress_modify_subkeyadd, (i - 1) * (100 / saveParcel.mAddSubKeys.size()));
            SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(i);
            log.add(LogType.MSG_MF_SUBKEY_NEW, indent,
                    KeyFormattingUtils.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve));

            if (isDivertToCard(masterSecretKey)) {
                log.add(LogType.MSG_MF_ERROR_DIVERT_NEWSUB, indent + 1);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            if (add.mExpiry == null) {
                log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent + 1);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            if (add.mExpiry > 0L && new Date(add.mExpiry * 1000).before(new Date())) {
                log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent + 1);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            // generate a new secret key (privkey only for now)
            subProgressPush((i - 1) * (100 / saveParcel.mAddSubKeys.size()),
                    i * (100 / saveParcel.mAddSubKeys.size()));
            PGPKeyPair keyPair = createKey(add, cryptoInput.getSignatureTime(), log, indent);
            subProgressPop();
            if (keyPair == null) {
                log.add(LogType.MSG_MF_ERROR_PGP, indent + 1);
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            // add subkey binding signature (making this a sub rather than master key)
            PGPPublicKey pKey = keyPair.getPublicKey();
            try {
                PGPSignature cert = generateSubkeyBindingSignature(
                        getSignatureGenerator(masterSecretKey, cryptoInput), cryptoInput.getSignatureTime(),
                        masterPublicKey, masterPrivateKey, getSignatureGenerator(pKey, cryptoInput, false),
                        keyPair.getPrivateKey(), pKey, add.mFlags, add.mExpiry);
                pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
            } catch (NfcInteractionNeeded e) {
                nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
            }

            PGPSecretKey sKey;
            {
                // Build key encrypter and decrypter based on passphrase
                PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
                        .get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
                PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
                        PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
                        PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
                                .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
                                .build(cryptoInput.getPassphrase().getCharArray());

                PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
                        .get(PgpSecurityConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
                sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor);
            }

            log.add(LogType.MSG_MF_SUBKEY_NEW_ID, indent + 1,
                    KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));

            sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);

        }
        subProgressPop();

        // Check if we were cancelled - again. This operation is expensive so we do it each loop.
        if (checkCancelled()) {
            log.add(LogType.MSG_OPERATION_CANCELLED, indent);
            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
        }

        // 6. If requested, change passphrase
        if (saveParcel.getChangeUnlockParcel() != null) {
            progress(R.string.progress_modify_passphrase, 90);
            log.add(LogType.MSG_MF_PASSPHRASE, indent);
            indent += 1;

            sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(),
                    saveParcel.getChangeUnlockParcel().mNewPassphrase, log, indent);
            if (sKR == null) {
                // The error has been logged above, just return a bad state
                return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
            }

            indent -= 1;
        }

        // 7. if requested, change PIN and/or Admin PIN on security token
        if (saveParcel.mSecurityTokenPin != null) {
            progress(R.string.progress_modify_pin, 90);
            log.add(LogType.MSG_MF_PIN, indent);
            indent += 1;

            nfcKeyToCardOps.setPin(saveParcel.mSecurityTokenPin);

            indent -= 1;
        }
        if (saveParcel.mSecurityTokenAdminPin != null) {
            progress(R.string.progress_modify_admin_pin, 90);
            log.add(LogType.MSG_MF_ADMIN_PIN, indent);
            indent += 1;

            nfcKeyToCardOps.setAdminPin(saveParcel.mSecurityTokenAdminPin);

            indent -= 1;
        }

    } catch (IOException e) {
        Log.e(Constants.TAG, "encountered IOException while modifying key", e);
        log.add(LogType.MSG_MF_ERROR_ENCODE, indent + 1);
        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
    } catch (PGPException e) {
        Log.e(Constants.TAG, "encountered pgp error while modifying key", e);
        log.add(LogType.MSG_MF_ERROR_PGP, indent + 1);
        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
    } catch (SignatureException e) {
        Log.e(Constants.TAG, "encountered SignatureException while modifying key", e);
        log.add(LogType.MSG_MF_ERROR_SIG, indent + 1);
        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
    }

    progress(R.string.progress_done, 100);

    if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) {
        log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent + 1);
        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
    }

    if (!nfcSignOps.isEmpty()) {
        log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
        return new PgpEditKeyResult(log, nfcSignOps.build(), cryptoInput);
    }

    if (!nfcKeyToCardOps.isEmpty()) {
        log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
        return new PgpEditKeyResult(log, nfcKeyToCardOps.build(), cryptoInput);
    }

    log.add(LogType.MSG_MF_SUCCESS, indent);
    return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));

}

From source file:org.sufficientlysecure.keychain.pgp.PgpKeyOperation.java

License:Open Source License

/** Update all (non-revoked) uid signatures with new flags and expiry time. */
private PGPPublicKey updateMasterCertificates(PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey,
        PGPPublicKey masterPublicKey, int flags, long expiry, CryptoInputParcel cryptoInput,
        SecurityTokenSignOperationsBuilder nfcSignOps, int indent, OperationLog log)
        throws PGPException, IOException, SignatureException {

    // keep track if we actually changed one
    boolean ok = false;
    log.add(LogType.MSG_MF_MASTER, indent);
    indent += 1;/*from w  w  w  .  j av a  2s.  c  o m*/

    PGPPublicKey modifiedPublicKey = masterPublicKey;

    // we work on the modifiedPublicKey here, to respect new or newly revoked uids
    // noinspection unchecked
    for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) {
        boolean isRevoked = false;
        PGPSignature currentCert = null;
        // noinspection unchecked
        for (PGPSignature cert : new IterableIterator<PGPSignature>(
                modifiedPublicKey.getSignaturesForID(userId))) {
            if (cert.getKeyID() != masterPublicKey.getKeyID()) {
                // foreign certificate?! error error error
                log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
                return null;
            }
            // we know from canonicalization that if there is any revocation here, it
            // is valid and not superseded by a newer certification.
            if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
                isRevoked = true;
                continue;
            }
            // we know from canonicalization that there is only one binding
            // certification here, so we can just work with the first one.
            if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
                    || cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION
                    || cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION
                    || cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) {
                currentCert = cert;
            }
        }

        if (currentCert == null) {
            // no certificate found?! error error error
            log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
            return null;
        }

        // we definitely should not update certifications of revoked keys, so just leave it.
        if (isRevoked) {
            continue;
        }

        // add shiny new user id certificate
        boolean isPrimary = currentCert.getHashedSubPackets() != null
                && currentCert.getHashedSubPackets().isPrimaryUserID();
        modifiedPublicKey = PGPPublicKey.removeCertification(modifiedPublicKey, userId, currentCert);
        try {
            PGPSignature newCert = generateUserIdSignature(getSignatureGenerator(masterSecretKey, cryptoInput),
                    cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId, isPrimary, flags,
                    expiry);
            modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, newCert);
        } catch (NfcInteractionNeeded e) {
            nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
        }
        ok = true;

    }

    if (!ok) {
        // might happen, theoretically, if there is a key with no uid..
        log.add(LogType.MSG_MF_ERROR_MASTER_NONE, indent);
        return null;
    }

    return modifiedPublicKey;

}

From source file:org.sufficientlysecure.keychain.pgp.PgpKeyOperation.java

License:Open Source License

/** Returns all flags valid for this key.
 *
 * This method does not do any validity checks on the signature, so it should not be used on
 * a non-canonicalized key!/*from ww  w .  j a  v  a  2 s  . com*/
 *
 */
private static int readKeyFlags(PGPPublicKey key) {
    int flags = 0;
    //noinspection unchecked
    for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
        if (sig.getHashedSubPackets() == null) {
            continue;
        }
        flags |= sig.getHashedSubPackets().getKeyFlags();
    }
    return flags;
}

From source file:org.sufficientlysecure.keychain.pgp.UncachedKeyRing.java

License:Open Source License

/** "Canonicalizes" a public key, removing inconsistencies in the process.
 *
 * More specifically://  w ww. j  ava  2  s  .  c  o  m
 *  - Remove all non-verifying self-certificates
 *  - Remove all "future" self-certificates
 *  - Remove all certificates flagged as "local"
 *  - For UID certificates, remove all certificates which are
 *      superseded by a newer one on the same target, including
 *      revocations with later re-certifications.
 *  - For subkey certifications, remove all certificates which
 *      are superseded by a newer one on the same target, unless
 *      it encounters a revocation certificate. The revocation
 *      certificate is considered to permanently revoke the key,
 *      even if contains later re-certifications.
 *  This is the "behavior in practice" used by (e.g.) GnuPG, and
 *  the rationale for both can be found as comments in the GnuPG
 *  source.
 *  UID signatures:
 *  https://github.com/mtigas/gnupg/blob/50c98c7ed6b542857ee2f902eca36cda37407737/g10/getkey.c#L1668-L1674
 *  Subkey signatures:
 *  https://github.com/mtigas/gnupg/blob/50c98c7ed6b542857ee2f902eca36cda37407737/g10/getkey.c#L1990-L1997
 *  - Remove all certificates in other positions if not of known type:
 *   - key revocation signatures on the master key
 *   - subkey binding signatures for subkeys
 *   - certifications and certification revocations for user ids
 *  - If a subkey retains no valid subkey binding certificate, remove it
 *  - If a user id retains no valid self certificate, remove it
 *  - If the key is a secret key, remove all certificates by foreign keys
 *  - If no valid user id remains, log an error and return null
 *
 * This operation writes an OperationLog which can be used as part of an OperationResultParcel.
 *
 * @param forExport if this is true, non-exportable signatures will be removed
 * @return A canonicalized key, or null on fatal error (log will include a message in this case)
 *
 */
@SuppressWarnings("ConstantConditions")
public CanonicalizedKeyRing canonicalize(OperationLog log, int indent, boolean forExport) {

    log.add(isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, indent,
            KeyFormattingUtils.convertKeyIdToHex(getMasterKeyId()));
    indent += 1;

    // do not accept v3 keys
    if (getVersion() <= 3) {
        log.add(LogType.MSG_KC_ERROR_V3, indent);
        return null;
    }

    Calendar nowCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    // allow for diverging clocks up to one day when checking creation time
    nowCal.add(Calendar.DAY_OF_YEAR, 1);
    final Date nowPlusOneDay = nowCal.getTime();

    int redundantCerts = 0, badCerts = 0;

    PGPKeyRing ring = mRing;
    PGPPublicKey masterKey = mRing.getPublicKey();
    final long masterKeyId = masterKey.getKeyID();

    if (Arrays.binarySearch(KNOWN_ALGORITHMS, masterKey.getAlgorithm()) < 0) {
        log.add(LogType.MSG_KC_ERROR_MASTER_ALGO, indent, Integer.toString(masterKey.getAlgorithm()));
        return null;
    }

    {
        log.add(LogType.MSG_KC_MASTER, indent, KeyFormattingUtils.convertKeyIdToHex(masterKey.getKeyID()));
        indent += 1;

        PGPPublicKey modified = masterKey;
        PGPSignature revocation = null;
        PGPSignature notation = null;
        for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) {
            int type = zert.getSignatureType();

            // These should most definitely not be here...
            if (type == PGPSignature.NO_CERTIFICATION || type == PGPSignature.DEFAULT_CERTIFICATION
                    || type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION
                    || type == PGPSignature.CERTIFICATION_REVOCATION) {
                log.add(LogType.MSG_KC_MASTER_BAD_TYPE_UID, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                badCerts += 1;
                continue;
            }
            WrappedSignature cert = new WrappedSignature(zert);

            if (type != PGPSignature.KEY_REVOCATION && type != PGPSignature.DIRECT_KEY) {
                // Unknown type, just remove
                log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent, "0x" + Integer.toString(type, 16));
                modified = PGPPublicKey.removeCertification(modified, zert);
                badCerts += 1;
                continue;
            }

            if (cert.getCreationTime().after(nowPlusOneDay)) {
                // Creation date in the future? No way!
                log.add(LogType.MSG_KC_MASTER_BAD_TIME, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                badCerts += 1;
                continue;
            }

            try {
                cert.init(masterKey);
                if (!cert.verifySignature(masterKey)) {
                    log.add(LogType.MSG_KC_MASTER_BAD, indent);
                    modified = PGPPublicKey.removeCertification(modified, zert);
                    badCerts += 1;
                    continue;
                }
            } catch (PgpGeneralException e) {
                log.add(LogType.MSG_KC_MASTER_BAD_ERR, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                badCerts += 1;
                continue;
            }

            // if this is for export, we always remove any non-exportable certs
            if (forExport && cert.isLocal()) {
                // Remove revocation certs with "local" flag
                log.add(LogType.MSG_KC_MASTER_LOCAL, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                continue;
            }

            // special case: non-exportable, direct key signatures for notations!
            if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) {
                // must be local, otherwise strip!
                if (!cert.isLocal()) {
                    log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent);
                    modified = PGPPublicKey.removeCertification(modified, zert);
                    badCerts += 1;
                    continue;
                }

                // first notation? fine then.
                if (notation == null) {
                    notation = zert;
                    // more notations? at least one is superfluous, then.
                } else if (notation.getCreationTime().before(zert.getCreationTime())) {
                    log.add(LogType.MSG_KC_NOTATION_DUP, indent);
                    modified = PGPPublicKey.removeCertification(modified, notation);
                    redundantCerts += 1;
                    notation = zert;
                } else {
                    log.add(LogType.MSG_KC_NOTATION_DUP, indent);
                    modified = PGPPublicKey.removeCertification(modified, zert);
                    redundantCerts += 1;
                }
                continue;
            } else if (cert.isLocal()) {
                // Remove revocation certs with "local" flag
                log.add(LogType.MSG_KC_MASTER_BAD_LOCAL, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                badCerts += 1;
                continue;
            }

            // first revocation? fine then.
            if (revocation == null) {
                revocation = zert;
                // more revocations? at least one is superfluous, then.
            } else if (revocation.getCreationTime().before(zert.getCreationTime())) {
                log.add(LogType.MSG_KC_REVOKE_DUP, indent);
                modified = PGPPublicKey.removeCertification(modified, revocation);
                redundantCerts += 1;
                revocation = zert;
            } else {
                log.add(LogType.MSG_KC_REVOKE_DUP, indent);
                modified = PGPPublicKey.removeCertification(modified, zert);
                redundantCerts += 1;
            }
        }

        // If we have a notation packet, check if there is even any data in it?
        if (notation != null) {
            // If there isn't, might as well strip it
            if (new WrappedSignature(notation).getNotation().isEmpty()) {
                log.add(LogType.MSG_KC_NOTATION_EMPTY, indent);
                modified = PGPPublicKey.removeCertification(modified, notation);
                redundantCerts += 1;
            }
        }

        ArrayList<String> processedUserIds = new ArrayList<>();
        for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
            String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);

            // warn if user id was made with bad encoding
            if (!Utf8Util.isValidUTF8(rawUserId)) {
                log.add(LogType.MSG_KC_UID_WARN_ENCODING, indent);
            }

            // check for duplicate user ids
            if (processedUserIds.contains(userId)) {
                log.add(LogType.MSG_KC_UID_DUP, indent, userId);
                // strip out the first found user id with this name
                modified = PGPPublicKey.removeCertification(modified, rawUserId);
            }
            if (processedUserIds.size() > CANONICALIZE_MAX_USER_IDS) {
                log.add(LogType.MSG_KC_UID_TOO_MANY, indent, userId);
                // strip out the user id
                modified = PGPPublicKey.removeCertification(modified, rawUserId);
            }
            processedUserIds.add(userId);

            PGPSignature selfCert = null;
            revocation = null;

            // look through signatures for this specific user id
            @SuppressWarnings("unchecked")
            Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId);
            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_UID_BAD_TYPE, indent,
                                "0x" + Integer.toString(zert.getSignatureType(), 16));
                        modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
                        badCerts += 1;
                        continue;
                    }

                    if (cert.getCreationTime().after(nowPlusOneDay)) {
                        // Creation date in the future? No way!
                        log.add(LogType.MSG_KC_UID_BAD_TIME, indent);
                        modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
                        badCerts += 1;
                        continue;
                    }

                    if (cert.isLocal()) {
                        // Creation date in the future? No way!
                        log.add(LogType.MSG_KC_UID_BAD_LOCAL, indent);
                        modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_FOREIGN, indent,
                                    KeyFormattingUtils.convertKeyIdToHex(certId));
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
                            badCerts += 1;
                        }
                        continue;
                    }

                    // Otherwise, first make sure it checks out
                    try {
                        cert.init(masterKey);
                        if (!cert.verifySignature(masterKey, rawUserId)) {
                            log.add(LogType.MSG_KC_UID_BAD, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
                            badCerts += 1;
                            continue;
                        }
                    } catch (PgpGeneralException e) {
                        log.add(LogType.MSG_KC_UID_BAD_ERR, indent, userId);
                        modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_CERT_DUP, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, selfCert);
                            redundantCerts += 1;
                            selfCert = zert;
                        } else {
                            log.add(LogType.MSG_KC_UID_CERT_DUP, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_REVOKE_OLD, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_REVOKE_OLD, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_REVOKE_DUP, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, revocation);
                            redundantCerts += 1;
                            revocation = zert;
                        } else {
                            log.add(LogType.MSG_KC_UID_REVOKE_DUP, indent, userId);
                            modified = PGPPublicKey.removeCertification(modified, rawUserId, 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_UID_REMOVE, indent, userId);
                modified = PGPPublicKey.removeCertification(modified, rawUserId);
            }
        }

        // If NO user ids remain, error out!
        if (modified == null || !modified.getUserIDs().hasNext()) {
            log.add(LogType.MSG_KC_ERROR_NO_UID, indent);
            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;

    }

    // Keep track of ids we encountered so far
    Set<Long> knownIds = new HashSet<>();

    // Process all keys
    for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) {
        // Make sure this is not a duplicate, avoid undefined behavior!
        if (knownIds.contains(key.getKeyID())) {
            log.add(LogType.MSG_KC_ERROR_DUP_KEY, indent, KeyFormattingUtils.convertKeyIdToHex(key.getKeyID()));
            return null;
        }
        // Add the key id to known
        knownIds.add(key.getKeyID());

        // Don't care about the master key any further, that one gets special treatment above
        if (key.isMasterKey()) {
            continue;
        }

        log.add(LogType.MSG_KC_SUB, indent, KeyFormattingUtils.convertKeyIdToHex(key.getKeyID()));
        indent += 1;

        if (Arrays.binarySearch(KNOWN_ALGORITHMS, key.getAlgorithm()) < 0) {
            ring = removeSubKey(ring, key);

            log.add(LogType.MSG_KC_SUB_UNKNOWN_ALGO, indent, Integer.toString(key.getAlgorithm()));
            indent -= 1;
            continue;
        }

        Date keyCreationTime = key.getCreationTime(), keyCreationTimeLenient;
        {
            Calendar keyCreationCal = Calendar.getInstance();
            keyCreationCal.setTime(keyCreationTime);
            // allow for diverging clocks up to one day when checking creation time
            keyCreationCal.add(Calendar.MINUTE, -5);
            keyCreationTimeLenient = keyCreationCal.getTime();
        }

        // A subkey needs exactly one subkey binding certificate, and optionally one revocation
        // certificate.
        PGPPublicKey modified = key;
        PGPSignature selfCert = null, revocation = null;
        uids: for (PGPSignature zert : new IterableIterator<PGPSignature>(key.getSignatures())) {
            // remove from keyring (for now)
            modified = PGPPublicKey.removeCertification(modified, zert);

            WrappedSignature cert = new WrappedSignature(zert);
            int type = cert.getSignatureType();

            // filter out bad key types...
            if (cert.getKeyId() != masterKey.getKeyID()) {
                log.add(LogType.MSG_KC_SUB_BAD_KEYID, indent);
                badCerts += 1;
                continue;
            }

            if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) {
                log.add(LogType.MSG_KC_SUB_BAD_TYPE, indent, "0x" + Integer.toString(type, 16));
                badCerts += 1;
                continue;
            }

            if (cert.getCreationTime().after(nowPlusOneDay)) {
                // Creation date in the future? No way!
                log.add(LogType.MSG_KC_SUB_BAD_TIME, indent);
                badCerts += 1;
                continue;
            }

            if (cert.getCreationTime().before(keyCreationTime)) {
                // Signature is earlier than key creation time
                log.add(LogType.MSG_KC_SUB_BAD_TIME_EARLY, indent);
                // due to an earlier accident, we generated keys which had creation timestamps
                // a few seconds after their signature timestamp. for compatibility, we only
                // error out with some margin of error
                if (cert.getCreationTime().before(keyCreationTimeLenient)) {
                    badCerts += 1;
                    continue;
                }
            }

            if (cert.isLocal()) {
                // Creation date in the future? No way!
                log.add(LogType.MSG_KC_SUB_BAD_LOCAL, indent);
                badCerts += 1;
                continue;
            }

            if (type == PGPSignature.SUBKEY_BINDING) {

                // make sure the certificate checks out
                try {
                    cert.init(masterKey);
                    if (!cert.verifySignature(masterKey, key)) {
                        log.add(LogType.MSG_KC_SUB_BAD, indent);
                        badCerts += 1;
                        continue;
                    }
                } catch (PgpGeneralException e) {
                    log.add(LogType.MSG_KC_SUB_BAD_ERR, indent);
                    badCerts += 1;
                    continue;
                }

                boolean needsPrimaryBinding = false;

                // If the algorithm is even suitable for signing
                if (isSigningAlgo(key.getAlgorithm())) {

                    // If this certificate says it allows signing for the key
                    if (zert.getHashedSubPackets() != null
                            && zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
                        int flags = ((KeyFlags) zert.getHashedSubPackets()
                                .getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
                        if ((flags & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA) {
                            needsPrimaryBinding = true;
                        }
                    } else {
                        // If there are no key flags, we STILL require this because the key can sign!
                        needsPrimaryBinding = true;
                    }

                }

                // If this key can sign, it MUST have a primary key binding certificate
                if (needsPrimaryBinding) {
                    boolean ok = false;
                    if (zert.getUnhashedSubPackets() != null)
                        try {
                            // Check all embedded signatures, if any of them fits
                            PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures();
                            for (int i = 0; i < list.size(); i++) {
                                WrappedSignature subsig = new WrappedSignature(list.get(i));
                                if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
                                    subsig.init(key);
                                    if (subsig.verifySignature(masterKey, key)) {
                                        ok = true;
                                    } else {
                                        log.add(LogType.MSG_KC_SUB_PRIMARY_BAD, indent);
                                        badCerts += 1;
                                        continue uids;
                                    }
                                }
                            }
                        } catch (Exception e) {
                            log.add(LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, indent);
                            badCerts += 1;
                            continue;
                        }
                    // if it doesn't, get rid of this!
                    if (!ok) {
                        log.add(LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
                        badCerts += 1;
                        continue;
                    }
                }

                // if we already have a cert, and this one is older: skip it
                if (selfCert != null && cert.getCreationTime().before(selfCert.getCreationTime())) {
                    log.add(LogType.MSG_KC_SUB_DUP, indent);
                    redundantCerts += 1;
                    continue;
                }

                selfCert = zert;

                // it must be a revocation, then (we made sure above)
            } else {

                // make sure the certificate checks out
                try {
                    cert.init(masterKey);
                    if (!cert.verifySignature(masterKey, key)) {
                        log.add(LogType.MSG_KC_SUB_REVOKE_BAD, indent);
                        badCerts += 1;
                        continue;
                    }
                } catch (PgpGeneralException e) {
                    log.add(LogType.MSG_KC_SUB_REVOKE_BAD_ERR, indent);
                    badCerts += 1;
                    continue;
                }

                // If we already have a newer revocation cert, skip this one.
                if (revocation != null && revocation.getCreationTime().after(cert.getCreationTime())) {
                    log.add(LogType.MSG_KC_SUB_REVOKE_DUP, indent);
                    redundantCerts += 1;
                    continue;
                }

                revocation = zert;
            }
        }

        // it is not properly bound? error!
        if (selfCert == null) {
            ring = removeSubKey(ring, key);

            log.add(LogType.MSG_KC_SUB_NO_CERT, indent, KeyFormattingUtils.convertKeyIdToHex(key.getKeyID()));
            indent -= 1;
            continue;
        }

        // If we have flags, check if the algorithm supports all of them
        if (selfCert.getHashedSubPackets() != null
                && selfCert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
            int flags = ((KeyFlags) selfCert.getHashedSubPackets()
                    .getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
            int algo = key.getAlgorithm();
            // If this is a signing key, but not a signing algorithm, warn the user
            if (!isSigningAlgo(algo) && (flags & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA) {
                log.add(LogType.MSG_KC_SUB_ALGO_BAD_SIGN, indent);
            }
            // If this is an encryption key, but not an encryption algorithm, warn the user
            if (!isEncryptionAlgo(algo) && ((flags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE
                    || (flags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS)) {
                log.add(LogType.MSG_KC_SUB_ALGO_BAD_ENCRYPT, indent);
            }
        }

        // re-add certification
        modified = PGPPublicKey.addCertification(modified, selfCert);
        // add revocation, if any
        if (revocation != null) {
            modified = PGPPublicKey.addCertification(modified, revocation);
        }
        // replace pubkey in keyring
        ring = replacePublicKey(ring, modified);
        indent -= 1;
    }

    if (badCerts > 0 && redundantCerts > 0) {
        // multi plural would make this complex, just leaving this as is...
        log.add(LogType.MSG_KC_SUCCESS_BAD_AND_RED, indent, Integer.toString(badCerts),
                Integer.toString(redundantCerts));
    } else if (badCerts > 0) {
        log.add(LogType.MSG_KC_SUCCESS_BAD, indent, badCerts);
    } else if (redundantCerts > 0) {
        log.add(LogType.MSG_KC_SUCCESS_REDUNDANT, indent, redundantCerts);
    } else {
        log.add(LogType.MSG_KC_SUCCESS, indent);
    }

    return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, 1)
            : new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, 0);
}