com.google.gerrit.server.git.gpg.PushCertificateChecker.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.server.git.gpg.PushCertificateChecker.java

Source

// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.server.git.gpg;

import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyToString;

import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate;
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/** Checker for push certificates. */
public abstract class PushCertificateChecker {
    private final PublicKeyChecker publicKeyChecker;

    protected PushCertificateChecker(PublicKeyChecker publicKeyChecker) {
        this.publicKeyChecker = publicKeyChecker;
    }

    /**
     * Check a push certificate.
     *
     * @return result of the check.
     * @throws PGPException if an error occurred during GPG checks.
     * @throws IOException if an error occurred reading from the repository.
     */
    public final CheckResult check(PushCertificate cert) throws PGPException, IOException {
        if (cert.getNonceStatus() != NonceStatus.OK) {
            return new CheckResult("Invalid nonce");
        }
        PGPSignature sig = readSignature(cert);
        if (sig == null) {
            return new CheckResult("Invalid signature format");
        }
        Repository repo = getRepository();
        List<String> problems = new ArrayList<>();
        try (PublicKeyStore store = new PublicKeyStore(repo)) {
            checkSignature(sig, cert, store.get(sig.getKeyID()), problems);
            checkCustom(repo, problems);
            return new CheckResult(problems);
        } finally {
            if (shouldClose(repo)) {
                repo.close();
            }
        }
    }

    /**
     * Get the repository that this checker should operate on.
     * <p>
     * This method is called once per call to {@link #check(PushCertificate)}.
     *
     * @return the repository.
     * @throws IOException if an error occurred reading the repository.
     */
    protected abstract Repository getRepository() throws IOException;

    /**
     * @param repo a repository previously returned by {@link #getRepository()}.
     * @return whether this repository should be closed before returning from
     *     {@link #check(PushCertificate)}.
     */
    protected abstract boolean shouldClose(Repository repo);

    /**
     * Perform custom checks.
     * <p>
     * Default implementation does nothing, but may be overridden by subclasses.
     *
     * @param repo a repository previously returned by {@link #getRepository()}.
     * @param problems list to which any problems should be added.
     */
    protected void checkCustom(Repository repo, List<String> problems) {
        // Default implementation does nothing.
    }

    private PGPSignature readSignature(PushCertificate cert) throws IOException {
        ArmoredInputStream in = new ArmoredInputStream(
                new ByteArrayInputStream(Constants.encode(cert.getSignature())));
        PGPObjectFactory factory = new BcPGPObjectFactory(in);
        Object obj;
        while ((obj = factory.nextObject()) != null) {
            if (obj instanceof PGPSignatureList) {
                PGPSignatureList sigs = (PGPSignatureList) obj;
                if (!sigs.isEmpty()) {
                    return sigs.get(0);
                }
            }
        }
        return null;
    }

    private void checkSignature(PGPSignature sig, PushCertificate cert, PGPPublicKeyRingCollection keys,
            List<String> problems) {
        List<String> deferredProblems = new ArrayList<>();
        boolean anyKeys = false;
        for (PGPPublicKeyRing kr : keys) {
            PGPPublicKey k = kr.getPublicKey();
            anyKeys = true;
            try {
                sig.init(new BcPGPContentVerifierBuilderProvider(), k);
                sig.update(Constants.encode(cert.toText()));
                if (!sig.verify()) {
                    // TODO(dborowitz): Privacy issues with exposing fingerprint/user ID
                    // of keys having the same ID as the pusher's key?
                    deferredProblems.add("Signature not valid with public key: " + keyToString(k));
                    continue;
                }
                CheckResult result = publicKeyChecker.check(k, sig.getKeyID());
                if (result.isOk()) {
                    return;
                }
                StringBuilder err = new StringBuilder("Invalid public key (").append(keyToString(k)).append("):");
                for (int i = 0; i < result.getProblems().size(); i++) {
                    err.append('\n').append("  ").append(result.getProblems().get(i));
                }
                problems.add(err.toString());
                return;
            } catch (PGPException e) {
                deferredProblems
                        .add("Error checking signature with public key (" + keyToString(k) + ": " + e.getMessage());
            }
        }
        if (!anyKeys) {
            problems.add("No public keys found for Key ID " + keyIdToString(sig.getKeyID()));
        } else {
            problems.addAll(deferredProblems);
        }
    }
}