Java tutorial
// 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); } } }