001package co.codewizards.cloudstore.updater;
002
003import static co.codewizards.cloudstore.core.io.StreamUtil.*;
004import static java.util.Objects.*;
005
006import java.io.BufferedInputStream;
007import java.io.InputStream;
008
009import org.bouncycastle.openpgp.PGPObjectFactory;
010import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
011import org.bouncycastle.openpgp.PGPSignature;
012import org.bouncycastle.openpgp.PGPSignatureList;
013import org.bouncycastle.openpgp.PGPUtil;
014import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
015import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
016
017import co.codewizards.cloudstore.core.oio.File;
018
019public class PGPVerifier {
020        private PGPPublicKeyRingCollection publicKeyRingWithTrustedKeys;
021
022        /**
023         * Verify the specified {@code file}.
024         * @param file the file to be verified. Must not be <code>null</code>. There must be a second file
025         * with the same name and the additional suffix ".sig" next to this file (in the same directory).
026         * This secondary file is a so-called detached signature.
027         * @throws PGPVerifyException if the given {@code file} could not be verified successfully. Either
028         * there is no detached-signature-file, or its signature is broken or its signature does not match
029         * any of the {@linkplain #getPublicKeyRingWithTrustedKeys() trusted keys}.
030         */
031        public void verify(final File file, final File signatureFile) throws PGPVerifyException {
032                requireNonNull(file, "file");
033                requireNonNull(signatureFile, "signatureFile");
034
035                final PGPSignatureList sl = readSignatureFile(signatureFile);
036                final PGPPublicKeyRingCollection publicKeyRing = getPublicKeyRingWithTrustedKeys();
037
038                for (int index = 0; index < sl.size(); ++index) {
039                        try {
040                                final PGPSignature signature = sl.get(index);
041                                signature.init(new BcPGPContentVerifierBuilderProvider(), publicKeyRing.getPublicKey(signature.getKeyID()));
042
043                                final InputStream contentIn = castStream(file.createInputStream());
044                                try {
045                                        final byte[] buf = new byte[16 * 1024];
046                                        int len;
047                                        while (0 <= (len = contentIn.read(buf))) {
048                                                if (len > 0)
049                                                        signature.update(buf, 0, len);
050                                        }
051                                } finally {
052                                        contentIn.close();
053                                }
054
055                                if (signature.verify())
056                                        return;
057
058                        } catch (final Exception e) {
059                                throw new PGPVerifyException(file.getAbsolutePath() + ": " + e, e);
060                        }
061                }
062                throw new PGPVerifyException(file.getAbsolutePath());
063        }
064
065        private PGPPublicKeyRingCollection getPublicKeyRingWithTrustedKeys() {
066                try {
067                        PGPPublicKeyRingCollection ring = publicKeyRingWithTrustedKeys;
068                        if (ring == null) {
069                                // Currently only one single trusted key ;-)
070                                final InputStream publicKeyIn = new BufferedInputStream(PGPVerifier.class.getResourceAsStream("/0x4AB0FBC1.asc"));
071                                try {
072                                        ring = new PGPPublicKeyRingCollection(
073                                                        PGPUtil.getDecoderStream(publicKeyIn), new BcKeyFingerprintCalculator());
074                                } finally {
075                                        publicKeyIn.close();
076                                }
077                                publicKeyRingWithTrustedKeys = ring;
078                        }
079                        return ring;
080                } catch (final RuntimeException x) {
081                        throw x;
082                } catch (final Exception x) {
083                        throw new RuntimeException(x);
084                }
085        }
086
087        private PGPSignatureList readSignatureFile(final File signatureFile) throws PGPVerifyException {
088                requireNonNull(signatureFile, "signatureFile");
089                if (!signatureFile.isFile() || !signatureFile.canRead())
090                        throw new PGPVerifyException("The signature-file does not exist or is not readable: " + signatureFile.getAbsolutePath());
091
092                try {
093                        final InputStream in = new BufferedInputStream(castStream(signatureFile.createInputStream()));
094                        try {
095                                final PGPObjectFactory objectFactory = new PGPObjectFactory(
096                                                PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator());
097                                final PGPSignatureList sl = (PGPSignatureList) objectFactory.nextObject();
098                                return sl;
099                        } finally {
100                                in.close();
101                        }
102                } catch (final Exception e) {
103                        throw new PGPVerifyException(signatureFile.getAbsolutePath() + ": " + e, e);
104                }
105        }
106}