All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simplify4u.plugins.pgp.PublicKeyUtils Maven / Gradle / Ivy

/*
 * Copyright 2020-2021 Slawomir Jaranowski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.simplify4u.plugins.pgp;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.simplify4u.plugins.utils.HexUtils;

/**
 * Utility for PGPPublicKey
 */
@Slf4j
public final class PublicKeyUtils {

    private PublicKeyUtils() {
        // No need to instantiate utility class.
    }

    /**
     * Generate string version of key fingerprint
     *
     * @param publicKey given key
     *
     * @return fingerprint as string
     */
    static String fingerprint(PGPPublicKey publicKey) {
        return HexUtils.fingerprintToString(publicKey.getFingerprint());
    }

    /**
     * Generate string version of master key fingerprint
     *
     * @param keyInfo given key
     *
     * @return master key fingerprint as string
     */
    public static String fingerprintForMaster(KeyInfo keyInfo) {
        return Optional.ofNullable(keyInfo.getMaster()).orElse(keyInfo.getFingerprint()).toString();
    }

    /**
     * Generate string with key id description.
     *
     * @param keyInfo a key
     *
     * @return string with key id description
     */
    public static String keyIdDescription(KeyInfo keyInfo) {

        if (keyInfo.getMaster() != null) {
            return String.format("SubKeyId: %s of %s", keyInfo.getFingerprint(), keyInfo.getMaster());
        } else {
            return "KeyId: " + keyInfo.getFingerprint();
        }
    }

    /**
     * Return master key for given sub public key.
     *
     * @param publicKey     given key
     * @param publicKeyRing keys ring with master and sub keys
     *
     * @return master key of empty if not found or given key is master key
     */
    static Optional getMasterKey(PGPPublicKey publicKey, PGPPublicKeyRing publicKeyRing) {

        if (publicKey.isMasterKey()) {
            return Optional.empty();
        }

        Iterator signatures = publicKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING);
        if (signatures.hasNext()) {
            PGPSignature sig = (PGPSignature) signatures.next();
            return Optional.ofNullable(publicKeyRing.getPublicKey(sig.getKeyID()));
        }

        return Optional.empty();
    }

    static Collection getUserIDs(PGPPublicKey publicKey, PGPPublicKeyRing publicKeyRing) {
        // use getRawUserIDs and standard java String to transform byte array to utf8
        // because BC generate exception if there is some problem in decoding utf8
        // https://github.com/s4u/pgpverify-maven-plugin/issues/61
        Set ret = new LinkedHashSet<>();
        publicKey.getRawUserIDs().forEachRemaining(ret::add);

        getMasterKey(publicKey, publicKeyRing).ifPresent(masterKey ->
                masterKey.getRawUserIDs().forEachRemaining(ret::add)
        );

        return ret.stream()
                .map(b -> new String(b, StandardCharsets.UTF_8))
                .collect(Collectors.toSet());
    }

    /**
     * Load Public Keys ring from stream for given keyId.
     *
     * @param keyStream input stream with public keys
     * @param keyId     key ID for find proper key ring
     *
     * @return key ring with given key id
     *
     * @throws IOException  if problem with comunication
     * @throws PGPException if problem with PGP data
     */
    public static Optional loadPublicKeyRing(InputStream keyStream, KeyId keyId)
            throws IOException, PGPException {

        InputStream keyIn = PGPUtil.getDecoderStream(keyStream);
        PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(keyIn, new BcKeyFingerprintCalculator());

        Optional publicKeyRing = Optional.ofNullable(keyId.getKeyRingFromRingCollection(pgpRing));
        publicKeyRing.ifPresent(PublicKeyUtils::verifyPublicKeyRing);

        return publicKeyRing;
    }

    /**
     * Validate signatures for subKeys in given key ring.
     *
     * @param publicKeyRing keys to verify
     */
    private static void verifyPublicKeyRing(PGPPublicKeyRing publicKeyRing) {

        StreamSupport.stream(publicKeyRing.spliterator(), false)
                .filter(key -> !key.isMasterKey())
                .forEach(key -> Try.run(() -> verifySigForSubKey(key, publicKeyRing)).get());
    }

    private static void verifySigForSubKey(PGPPublicKey subKey, PGPPublicKeyRing publicKeyRing) throws PGPException {

        int signatureTypeToCheck = subKey.hasRevocation()
                ? PGPSignature.SUBKEY_REVOCATION : PGPSignature.SUBKEY_BINDING;

        AtomicBoolean hasValidSignature = new AtomicBoolean(false);

        Iterator it = subKey.getSignaturesOfType(signatureTypeToCheck);
        it.forEachRemaining(s -> Try.run(() -> {
                    PGPSignature sig = (PGPSignature) s;

                    PGPPublicKey masterKey = publicKeyRing.getPublicKey(sig.getKeyID());
                    if (masterKey != null) {
                        sig.init(new BcPGPContentVerifierBuilderProvider(), masterKey);
                        if (sig.verifyCertification(masterKey, subKey)) {
                            hasValidSignature.set(true);
                        } else {
                            LOGGER.debug("Invalid signature [{}] type: {} for subKey: {}",
                                    sig.getCreationTime(), sig.getSignatureType(), fingerprint(subKey));
                        }
                    } else {
                        throw new PGPException(
                                String.format("Signature type: %d Not found key 0x%016X for subKeyId: %s",
                                        sig.getSignatureType(), sig.getKeyID(), fingerprint(subKey)));
                    }
                }).get()
        );

        if (!hasValidSignature.get()) {
            throw new PGPException(String.format("No valid signature type: %d for subKey: %s",
                    signatureTypeToCheck, fingerprint(subKey)));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy