PublicKeyChecker: Fix revocation check (mostly)
Bouncy Castle's isRevoked() method is so dumb as to be harmful: it only checks whether there is a revocation packet in the key, without doing any sort of validity checks on the packet. Improve the check by verifying the signature against the key that provided it, and for non-self-signatures, verifying that the signer is an authorized revocation key. This requires always passing in a store to any PublicKeyChecker. However, we still have the same initialization order problem as before, where a store is not always available at PublicKeyChecker creation, in particular in the PushCertificateChecker codepath. Argument precondition checks are the best we can do. Also add a convenience method to GerritPublicKeyChecker.Factory for the very common case (particularly in tests) where we do have both an expected user and a store. Change-Id: Id15714f0395a200fcb33fb199c57355b860187a3
This commit is contained in:
@@ -36,6 +36,10 @@ public class Fingerprint {
|
||||
NB.decodeUInt16(fp, 16), NB.decodeUInt16(fp, 18));
|
||||
}
|
||||
|
||||
public static long getId(byte[] fp) {
|
||||
return NB.decodeInt64(fp, 12);
|
||||
}
|
||||
|
||||
public static Map<Long, Fingerprint> byId(Iterable<Fingerprint> fps) {
|
||||
Map<Long, Fingerprint> result = new HashMap<>();
|
||||
for (Fingerprint fp : fps) {
|
||||
@@ -65,6 +69,23 @@ public class Fingerprint {
|
||||
this.fp = checkLength(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a portion of a fingerprint byte array.
|
||||
* <p>
|
||||
* Unlike {@link #Fingerprint(byte[])}, creates a new copy of the byte array.
|
||||
*
|
||||
* @param buf byte array to wrap; must have at least {@code off + 20} bytes.
|
||||
* @param off offset in buf.
|
||||
*/
|
||||
public Fingerprint(byte[] buf, int off) {
|
||||
int expected = 20 + off;
|
||||
checkArgument(buf.length >= expected,
|
||||
"fingerprint buffer must have at least %s bytes, got %s",
|
||||
expected, buf.length);
|
||||
this.fp = new byte[20];
|
||||
System.arraycopy(buf, off, fp, 0, 20);
|
||||
}
|
||||
|
||||
public byte[] get() {
|
||||
return fp;
|
||||
}
|
||||
@@ -90,6 +111,6 @@ public class Fingerprint {
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return NB.decodeInt64(fp, 12);
|
||||
return getId(fp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,14 @@ public class GerritPublicKeyChecker extends PublicKeyChecker {
|
||||
public GerritPublicKeyChecker create() {
|
||||
return new GerritPublicKeyChecker(this);
|
||||
}
|
||||
|
||||
public GerritPublicKeyChecker create(IdentifiedUser expectedUser,
|
||||
PublicKeyStore store) {
|
||||
GerritPublicKeyChecker checker = new GerritPublicKeyChecker(this);
|
||||
checker.setExpectedUser(expectedUser);
|
||||
checker.setStore(store);
|
||||
return checker;
|
||||
}
|
||||
}
|
||||
|
||||
private final Provider<ReviewDb> db;
|
||||
|
||||
@@ -19,19 +19,34 @@ import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.OK;
|
||||
import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.TRUSTED;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
||||
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
|
||||
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_REASON;
|
||||
import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_COMPROMISED;
|
||||
import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_RETIRED;
|
||||
import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_SUPERSEDED;
|
||||
import static org.bouncycastle.bcpg.sig.RevocationReasonTags.NO_REASON;
|
||||
import static org.bouncycastle.openpgp.PGPSignature.DIRECT_KEY;
|
||||
import static org.bouncycastle.openpgp.PGPSignature.KEY_REVOCATION;
|
||||
|
||||
import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
|
||||
|
||||
import org.bouncycastle.bcpg.SignatureSubpacket;
|
||||
import org.bouncycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.bouncycastle.bcpg.sig.RevocationKey;
|
||||
import org.bouncycastle.bcpg.sig.RevocationReason;
|
||||
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.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -40,6 +55,9 @@ import java.util.Set;
|
||||
|
||||
/** Checker for GPG public keys for use in a push certificate. */
|
||||
public class PublicKeyChecker {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(PublicKeyChecker.class);
|
||||
|
||||
// https://tools.ietf.org/html/rfc4880#section-5.2.3.13
|
||||
private static final int COMPLETE_TRUST = 120;
|
||||
|
||||
@@ -78,16 +96,11 @@ public class PublicKeyChecker {
|
||||
|
||||
/** Disable web-of-trust checks. */
|
||||
public PublicKeyChecker disableTrust() {
|
||||
store = null;
|
||||
trusted = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the public key store for web-of-trust checks.
|
||||
* <p>
|
||||
* If set, {@link #enableTrust(int, Map)} must also be called.
|
||||
*/
|
||||
/** Set the public key store for reading keys referenced in signatures. */
|
||||
public PublicKeyChecker setStore(PublicKeyStore store) {
|
||||
if (store == null) {
|
||||
throw new IllegalArgumentException("PublicKeyStore is required");
|
||||
@@ -103,7 +116,7 @@ public class PublicKeyChecker {
|
||||
* @return the result of the check.
|
||||
*/
|
||||
public final CheckResult check(PGPPublicKey key) {
|
||||
if (store == null && trusted != null) {
|
||||
if (store == null) {
|
||||
throw new IllegalStateException("PublicKeyStore is required");
|
||||
}
|
||||
return check(key, 0, true,
|
||||
@@ -165,11 +178,7 @@ public class PublicKeyChecker {
|
||||
|
||||
private CheckResult checkBasic(PGPPublicKey key) {
|
||||
List<String> problems = new ArrayList<>(2);
|
||||
if (key.isRevoked()) {
|
||||
// TODO(dborowitz): isRevoked is overeager:
|
||||
// http://www.bouncycastle.org/jira/browse/BJB-45
|
||||
problems.add("Key is revoked");
|
||||
}
|
||||
gatherRevocationProblems(key, problems);
|
||||
|
||||
long validSecs = key.getValidSeconds();
|
||||
if (validSecs != 0) {
|
||||
@@ -182,6 +191,143 @@ public class PublicKeyChecker {
|
||||
return CheckResult.create(problems);
|
||||
}
|
||||
|
||||
private void gatherRevocationProblems(PGPPublicKey key, List<String> problems) {
|
||||
try {
|
||||
List<PGPSignature> revocations = new ArrayList<>();
|
||||
Map<Long, RevocationKey> revokers = new HashMap<>();
|
||||
PGPSignature selfRevocation = scanRevocations(key, revocations, revokers);
|
||||
if (selfRevocation != null) {
|
||||
problems.add(reasonToString(getRevocationReason(selfRevocation)));
|
||||
} else {
|
||||
checkRevocations(key, revocations, revokers, problems);
|
||||
}
|
||||
} catch (PGPException | IOException e) {
|
||||
problems.add("Error checking key revocation");
|
||||
}
|
||||
}
|
||||
|
||||
private PGPSignature scanRevocations(PGPPublicKey key,
|
||||
List<PGPSignature> revocations, Map<Long, RevocationKey> revokers)
|
||||
throws PGPException {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> allSigs = key.getSignatures();
|
||||
while (allSigs.hasNext()) {
|
||||
PGPSignature sig = allSigs.next();
|
||||
switch (sig.getSignatureType()) {
|
||||
case KEY_REVOCATION:
|
||||
if (sig.getKeyID() == key.getKeyID()) {
|
||||
sig.init(new BcPGPContentVerifierBuilderProvider(), key);
|
||||
if (sig.verifyCertification(key)) {
|
||||
return sig;
|
||||
}
|
||||
} else {
|
||||
revocations.add(sig);
|
||||
}
|
||||
break;
|
||||
case DIRECT_KEY:
|
||||
RevocationKey r = getRevocationKey(key, sig);
|
||||
if (r != null) {
|
||||
revokers.put(Fingerprint.getId(r.getFingerprint()), r);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RevocationKey getRevocationKey(PGPPublicKey key, PGPSignature sig)
|
||||
throws PGPException {
|
||||
if (sig.getKeyID() != key.getKeyID()) {
|
||||
return null;
|
||||
}
|
||||
SignatureSubpacket sub =
|
||||
sig.getHashedSubPackets().getSubpacket(REVOCATION_KEY);
|
||||
if (sub == null) {
|
||||
return null;
|
||||
}
|
||||
sig.init(new BcPGPContentVerifierBuilderProvider(), key);
|
||||
if (!sig.verifyCertification(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RevocationKey(sub.isCritical(), sub.getData());
|
||||
}
|
||||
|
||||
private void checkRevocations(PGPPublicKey key,
|
||||
List<PGPSignature> revocations, Map<Long, RevocationKey> revokers,
|
||||
List<String> problems)
|
||||
throws PGPException, IOException {
|
||||
for (PGPSignature revocation : revocations) {
|
||||
RevocationKey revoker = revokers.get(revocation.getKeyID());
|
||||
if (revoker == null) {
|
||||
continue; // Not a designated revoker.
|
||||
}
|
||||
byte[] rfp = revoker.getFingerprint();
|
||||
PGPPublicKeyRing rkr = store.get(rfp);
|
||||
if (rkr == null
|
||||
|| rkr.getPublicKey().getAlgorithm() != revoker.getAlgorithm()) {
|
||||
// Revoker is authorized and there is a revocation signature by this
|
||||
// revoker, but the key is not in the store so we can't verify the
|
||||
// signature.
|
||||
log.info("Key " + Fingerprint.toString(key.getFingerprint())
|
||||
+ " is revoked by " + Fingerprint.toString(rfp)
|
||||
+ ", which is not in the store. Assuming revocation is valid.");
|
||||
problems.add(reasonToString(getRevocationReason(revocation)));
|
||||
continue;
|
||||
}
|
||||
revocation.init(
|
||||
new BcPGPContentVerifierBuilderProvider(), rkr.getPublicKey());
|
||||
if (revocation.verifyCertification(key)) {
|
||||
problems.add(reasonToString(getRevocationReason(revocation)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RevocationReason getRevocationReason(PGPSignature sig) {
|
||||
if (sig.getSignatureType() != KEY_REVOCATION) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected KEY_REVOCATION signature, got " + sig.getSignatureType());
|
||||
}
|
||||
SignatureSubpacket sub =
|
||||
sig.getHashedSubPackets().getSubpacket(REVOCATION_REASON);
|
||||
if (sub == null) {
|
||||
return null;
|
||||
}
|
||||
return new RevocationReason(sub.isCritical(), sub.getData());
|
||||
}
|
||||
|
||||
private static String reasonToString(RevocationReason reason) {
|
||||
StringBuilder r = new StringBuilder("Key is revoked (");
|
||||
if (reason == null) {
|
||||
return r.append("no reason provided)").toString();
|
||||
}
|
||||
switch (reason.getRevocationReason()) {
|
||||
case NO_REASON:
|
||||
r.append("no reason code specified");
|
||||
break;
|
||||
case KEY_SUPERSEDED:
|
||||
r.append("superseded");
|
||||
break;
|
||||
case KEY_COMPROMISED:
|
||||
r.append("key material has been compromised");
|
||||
break;
|
||||
case KEY_RETIRED:
|
||||
r.append("retired and no longer valid");
|
||||
break;
|
||||
default:
|
||||
r.append("reason code ")
|
||||
.append(Integer.toString(reason.getRevocationReason()))
|
||||
.append(')');
|
||||
break;
|
||||
}
|
||||
r.append(')');
|
||||
String desc = reason.getRevocationDescription();
|
||||
if (!desc.isEmpty()) {
|
||||
r.append(": ").append(desc);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
private CheckResult checkWebOfTrust(PGPPublicKey key, PublicKeyStore store,
|
||||
int depth, Set<Fingerprint> seen) {
|
||||
if (trusted == null) {
|
||||
|
||||
@@ -178,15 +178,39 @@ public class PublicKeyStore implements AutoCloseable {
|
||||
*/
|
||||
public PGPPublicKeyRingCollection get(long keyId)
|
||||
throws PGPException, IOException {
|
||||
return new PGPPublicKeyRingCollection(get(keyId, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read public key with the given fingerprint.
|
||||
* <p>
|
||||
* Keys should not be trusted unless checked with {@link PublicKeyChecker}.
|
||||
* <p>
|
||||
* Multiple calls to this method use the same state of the key ref; to reread
|
||||
* the ref, call {@link #close()} first.
|
||||
*
|
||||
* @param fingerprint key fingerprint.
|
||||
* @return the key if found, or {@code null}.
|
||||
* @throws PGPException if an error occurred parsing the key data.
|
||||
* @throws IOException if an error occurred reading the repository data.
|
||||
*/
|
||||
public PGPPublicKeyRing get(byte[] fingerprint)
|
||||
throws PGPException, IOException {
|
||||
List<PGPPublicKeyRing> keyRings =
|
||||
get(Fingerprint.getId(fingerprint), fingerprint);
|
||||
return !keyRings.isEmpty() ? keyRings.get(0) : null;
|
||||
}
|
||||
|
||||
private List<PGPPublicKeyRing> get(long keyId, byte[] fp) throws IOException {
|
||||
if (reader == null) {
|
||||
load();
|
||||
}
|
||||
if (notes == null) {
|
||||
return empty();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Note note = notes.getNote(keyObjectId(keyId));
|
||||
if (note == null) {
|
||||
return empty();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<PGPPublicKeyRing> keys = new ArrayList<>();
|
||||
@@ -200,12 +224,16 @@ public class PublicKeyStore implements AutoCloseable {
|
||||
}
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPublicKeyRing) {
|
||||
keys.add((PGPPublicKeyRing) obj);
|
||||
PGPPublicKeyRing kr = (PGPPublicKeyRing) obj;
|
||||
if (fp == null
|
||||
|| Arrays.equals(fp, kr.getPublicKey().getFingerprint())) {
|
||||
keys.add(kr);
|
||||
}
|
||||
}
|
||||
checkState(!it.hasNext(),
|
||||
"expected one PGP object per ArmoredInputStream");
|
||||
}
|
||||
return new PGPPublicKeyRingCollection(keys);
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,12 +403,6 @@ public class PublicKeyStore implements AutoCloseable {
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static PGPPublicKeyRingCollection empty()
|
||||
throws PGPException, IOException {
|
||||
return new PGPPublicKeyRingCollection(
|
||||
Collections.<PGPPublicKeyRing> emptyList());
|
||||
}
|
||||
|
||||
public static String keyToString(PGPPublicKey key) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<String> it = key.getUserIDs();
|
||||
|
||||
@@ -208,7 +208,9 @@ public abstract class PushCertificateChecker {
|
||||
CheckResult.bad("Signature by " + keyIdToString(sig.getKeyID())
|
||||
+ " is not valid"));
|
||||
}
|
||||
CheckResult result = publicKeyChecker.check(signer);
|
||||
CheckResult result = publicKeyChecker
|
||||
.setStore(store)
|
||||
.check(signer);
|
||||
if (!result.getProblems().isEmpty()) {
|
||||
StringBuilder err = new StringBuilder("Invalid public key ")
|
||||
.append(keyToString(signer))
|
||||
|
||||
@@ -163,7 +163,7 @@ public class GpgKeys implements
|
||||
found = true;
|
||||
GpgKeyInfo info = toJson(
|
||||
keyRing.getPublicKey(),
|
||||
checkerFactory.create().setExpectedUser(rsrc.getUser()),
|
||||
checkerFactory.create(rsrc.getUser(), store),
|
||||
store);
|
||||
keys.put(info.id, info);
|
||||
info.id = null;
|
||||
|
||||
@@ -194,8 +194,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
|
||||
for (PGPPublicKeyRing keyRing : keyRings) {
|
||||
PGPPublicKey key = keyRing.getPublicKey();
|
||||
// Don't check web of trust; admins can fill in certifications later.
|
||||
CheckResult result = checkerFactory.create()
|
||||
.setExpectedUser(rsrc.getUser())
|
||||
CheckResult result = checkerFactory.create(rsrc.getUser(), store)
|
||||
.disableTrust()
|
||||
.check(key);
|
||||
if (!result.isOk()) {
|
||||
@@ -249,9 +248,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
|
||||
throws IOException {
|
||||
// Unlike when storing keys, include web-of-trust checks when producing
|
||||
// result JSON, so the user at least knows of any issues.
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store);
|
||||
Map<String, GpgKeyInfo> infos =
|
||||
Maps.newHashMapWithExpectedSize(keys.size() + deleted.size());
|
||||
for (PGPPublicKeyRing keyRing : keys) {
|
||||
|
||||
@@ -172,8 +172,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
@Test
|
||||
public void defaultGpgCertificationMatchesEmail() throws Exception {
|
||||
TestKey key = validKeyWithSecondUserId();
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertProblems(
|
||||
checker.check(key.getPublicKey()), Status.BAD,
|
||||
@@ -183,8 +182,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
+ " username:user");
|
||||
|
||||
addExternalId("test", "test", "test5@example.com");
|
||||
checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertNoProblems(checker.check(key.getPublicKey()));
|
||||
}
|
||||
@@ -192,8 +190,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
@Test
|
||||
public void defaultGpgCertificationDoesNotMatchEmail() throws Exception {
|
||||
addExternalId("test", "test", "nobody@example.com");
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertProblems(
|
||||
checker.check(validKeyWithSecondUserId().getPublicKey()), Status.BAD,
|
||||
@@ -208,8 +205,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
@Test
|
||||
public void manualCertificationMatchesExternalId() throws Exception {
|
||||
addExternalId("foo", "myId", null);
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertNoProblems(checker.check(validKeyWithSecondUserId().getPublicKey()));
|
||||
}
|
||||
@@ -217,8 +213,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
@Test
|
||||
public void manualCertificationDoesNotMatchExternalId() throws Exception {
|
||||
addExternalId("foo", "otherId", null);
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertProblems(
|
||||
checker.check(validKeyWithSecondUserId().getPublicKey()), Status.BAD,
|
||||
@@ -236,8 +231,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
reloadUser();
|
||||
|
||||
TestKey key = validKeyWithSecondUserId();
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store)
|
||||
.disableTrust();
|
||||
assertProblems(
|
||||
checker.check(key.getPublicKey()), Status.BAD,
|
||||
@@ -245,6 +239,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
+ " http://test/#/settings/web-identities");
|
||||
|
||||
checker = checkerFactory.create()
|
||||
.setStore(store)
|
||||
.disableTrust();
|
||||
assertProblems(
|
||||
checker.check(key.getPublicKey()), Status.BAD,
|
||||
@@ -277,16 +272,12 @@ public class GerritPublicKeyCheckerTest {
|
||||
add(keyE(), addUser("userE"));
|
||||
|
||||
// Checker for A, checking A.
|
||||
PublicKeyChecker checkerA = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checkerA = checkerFactory.create(user, store);
|
||||
assertNoProblems(checkerA.check(keyA.getPublicKey()));
|
||||
|
||||
// Checker for B, checking B. Trust chain and IDs are correct, so the only
|
||||
// problem is with the key itself.
|
||||
PublicKeyChecker checkerB = checkerFactory.create()
|
||||
.setExpectedUser(userB)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checkerB = checkerFactory.create(userB, store);
|
||||
assertProblems(
|
||||
checkerB.check(keyB.getPublicKey()), Status.BAD,
|
||||
"Key is expired");
|
||||
@@ -311,9 +302,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
add(keyE(), addUser("userE"));
|
||||
|
||||
// Checker for A, checking B.
|
||||
PublicKeyChecker checkerA = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checkerA = checkerFactory.create(user, store);
|
||||
assertProblems(
|
||||
checkerA.check(keyB.getPublicKey()), Status.BAD,
|
||||
"Key is expired",
|
||||
@@ -325,9 +314,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
+ " username:user");
|
||||
|
||||
// Checker for B, checking A.
|
||||
PublicKeyChecker checkerB = checkerFactory.create()
|
||||
.setExpectedUser(userB)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checkerB = checkerFactory.create(userB, store);
|
||||
assertProblems(
|
||||
checkerB.check(keyA.getPublicKey()), Status.BAD,
|
||||
"Key must contain a valid certification for one of the following"
|
||||
@@ -346,9 +333,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
TestKey keyA = add(keyA(), user);
|
||||
TestKey keyB = add(keyB(), addUser("userB"));
|
||||
|
||||
PublicKeyChecker checker = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checker = checkerFactory.create(user, store);
|
||||
assertProblems(
|
||||
checker.check(keyA.getPublicKey()), Status.OK,
|
||||
"No path to a trusted key",
|
||||
@@ -406,9 +391,7 @@ public class GerritPublicKeyCheckerTest {
|
||||
keyRingB = PGPPublicKeyRing.insertPublicKey(keyRingB, keyB);
|
||||
add(keyRingB, addUser("userB"));
|
||||
|
||||
PublicKeyChecker checkerA = checkerFactory.create()
|
||||
.setExpectedUser(user)
|
||||
.setStore(store);
|
||||
PublicKeyChecker checkerA = checkerFactory.create(user, store);
|
||||
assertProblems(checkerA.check(keyA.getPublicKey()), Status.OK,
|
||||
"No path to a trusted key",
|
||||
"Certification by " + keyToString(keyB)
|
||||
|
||||
@@ -16,6 +16,8 @@ package com.google.gerrit.gpg;
|
||||
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.expiredKey;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.revokedCompromisedKey;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.revokedNoLongerUsedKey;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.selfRevokedKey;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
|
||||
@@ -29,10 +31,15 @@ import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyG;
|
||||
import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyH;
|
||||
import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyI;
|
||||
import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyJ;
|
||||
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
|
||||
import static org.bouncycastle.openpgp.PGPSignature.DIRECT_KEY;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.google.gerrit.gpg.testutil.TestKey;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
|
||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
@@ -46,6 +53,7 @@ import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class PublicKeyCheckerTest {
|
||||
@@ -90,7 +98,8 @@ public class PublicKeyCheckerTest {
|
||||
|
||||
@Test
|
||||
public void selfRevokedKeyIsRevoked() throws Exception {
|
||||
assertProblems(selfRevokedKey(), "Key is revoked");
|
||||
assertProblems(selfRevokedKey(),
|
||||
"Key is revoked (key material has been compromised)");
|
||||
}
|
||||
|
||||
// Test keys specific to this test are at the bottom of this class. Each test
|
||||
@@ -174,6 +183,57 @@ public class PublicKeyCheckerTest {
|
||||
"No path to a trusted key", notTrusted(ki));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void revokedKeyDueToCompromise() throws Exception {
|
||||
TestKey k = add(revokedCompromisedKey());
|
||||
add(validKeyWithoutExpiration());
|
||||
save();
|
||||
|
||||
assertProblems(k,
|
||||
"Key is revoked (key material has been compromised):"
|
||||
+ " test6 compromised");
|
||||
|
||||
PGPPublicKeyRing kr = removeRevokers(k.getPublicKeyRing());
|
||||
store.add(kr);
|
||||
save();
|
||||
|
||||
// Key no longer specified as revoker.
|
||||
assertProblems(kr.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void revokedByKeyNotPresentInStore() throws Exception {
|
||||
TestKey k = add(revokedCompromisedKey());
|
||||
save();
|
||||
|
||||
assertProblems(k,
|
||||
"Key is revoked (key material has been compromised):"
|
||||
+ " test6 compromised");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void revokedKeyDueToNoLongerBeingUsed() throws Exception {
|
||||
TestKey k = add(revokedNoLongerUsedKey());
|
||||
add(validKeyWithoutExpiration());
|
||||
save();
|
||||
|
||||
assertProblems(k,
|
||||
"Key is revoked (retired and no longer valid): test7 not used");
|
||||
}
|
||||
|
||||
private PGPPublicKeyRing removeRevokers(PGPPublicKeyRing kr) {
|
||||
PGPPublicKey k = kr.getPublicKey();
|
||||
@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);
|
||||
}
|
||||
|
||||
private PublicKeyChecker newChecker(int maxTrustDepth, TestKey... trusted) {
|
||||
Map<Long, Fingerprint> fps = new HashMap<>();
|
||||
for (TestKey k : trusted) {
|
||||
@@ -208,12 +268,19 @@ public class PublicKeyCheckerTest {
|
||||
|
||||
private void assertProblems(PublicKeyChecker checker, TestKey k,
|
||||
String... expected) {
|
||||
CheckResult result = checker.check(k.getPublicKey());
|
||||
CheckResult result = checker.setStore(store)
|
||||
.check(k.getPublicKey());
|
||||
assertEquals(Arrays.asList(expected), result.getProblems());
|
||||
}
|
||||
|
||||
private void assertProblems(TestKey tk, String... expected) throws Exception {
|
||||
CheckResult result = new PublicKeyChecker().check(tk.getPublicKey());
|
||||
assertProblems(tk.getPublicKey(), expected);
|
||||
}
|
||||
|
||||
private void assertProblems(PGPPublicKey k, String... expected) throws Exception {
|
||||
CheckResult result = new PublicKeyChecker()
|
||||
.setStore(store)
|
||||
.check(k);
|
||||
assertEquals(Arrays.asList(expected), result.getProblems());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.gpg;
|
||||
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.REFS_GPG_KEYS;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
|
||||
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.expiredKey;
|
||||
@@ -33,7 +32,9 @@ import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
|
||||
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
|
||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.transport.PushCertificate;
|
||||
import org.eclipse.jgit.transport.PushCertificateIdent;
|
||||
@@ -49,7 +50,8 @@ import java.io.Reader;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PushCertificateCheckerTest {
|
||||
private TestRepository<?> tr;
|
||||
private InMemoryRepository repo;
|
||||
private PublicKeyStore store;
|
||||
private SignedPushConfig signedPushConfig;
|
||||
private PushCertificateChecker checker;
|
||||
|
||||
@@ -57,14 +59,17 @@ public class PushCertificateCheckerTest {
|
||||
public void setUp() throws Exception {
|
||||
TestKey key1 = validKeyWithoutExpiration();
|
||||
TestKey key3 = expiredKey();
|
||||
tr = new TestRepository<>(new InMemoryRepository(
|
||||
new DfsRepositoryDescription("repo")));
|
||||
tr.branch(REFS_GPG_KEYS).commit()
|
||||
.add(PublicKeyStore.keyObjectId(key1.getPublicKey().getKeyID()).name(),
|
||||
key1.getPublicKeyArmored())
|
||||
.add(PublicKeyStore.keyObjectId(key3.getPublicKey().getKeyID()).name(),
|
||||
key3.getPublicKeyArmored())
|
||||
.create();
|
||||
repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
|
||||
store = new PublicKeyStore(repo);
|
||||
store.add(key1.getPublicKeyRing());
|
||||
store.add(key3.getPublicKeyRing());
|
||||
|
||||
PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
|
||||
CommitBuilder cb = new CommitBuilder();
|
||||
cb.setAuthor(ident);
|
||||
cb.setCommitter(ident);
|
||||
assertEquals(RefUpdate.Result.NEW, store.save(cb));
|
||||
|
||||
signedPushConfig = new SignedPushConfig();
|
||||
signedPushConfig.setCertNonceSeed("sekret");
|
||||
signedPushConfig.setCertNonceSlopLimit(60 * 24);
|
||||
@@ -72,10 +77,11 @@ public class PushCertificateCheckerTest {
|
||||
}
|
||||
|
||||
private PushCertificateChecker newChecker(boolean checkNonce) {
|
||||
return new PushCertificateChecker(new PublicKeyChecker()) {
|
||||
PublicKeyChecker keyChecker = new PublicKeyChecker().setStore(store);
|
||||
return new PushCertificateChecker(keyChecker) {
|
||||
@Override
|
||||
protected Repository getRepository() {
|
||||
return tr.getRepository();
|
||||
return repo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,7 +132,7 @@ public class PushCertificateCheckerTest {
|
||||
|
||||
private String validNonce() {
|
||||
return signedPushConfig.getNonceGenerator()
|
||||
.createNonce(tr.getRepository(), System.currentTimeMillis() / 1000);
|
||||
.createNonce(repo, System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
private PushCertificate newSignedCert(String nonce, TestKey signingKey)
|
||||
@@ -158,7 +164,7 @@ public class PushCertificateCheckerTest {
|
||||
Reader reader =
|
||||
new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)));
|
||||
PushCertificateParser parser =
|
||||
new PushCertificateParser(tr.getRepository(), signedPushConfig);
|
||||
new PushCertificateParser(repo, signedPushConfig);
|
||||
return parser.parse(reader);
|
||||
}
|
||||
|
||||
|
||||
@@ -542,6 +542,246 @@ public class TestKeys {
|
||||
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
|
||||
}
|
||||
|
||||
// TODO(dborowitz): Figure out how to get gpg to revoke a key for someone
|
||||
// else.
|
||||
/**
|
||||
* A key revoked by a valid key, due to key compromise.
|
||||
* <p>
|
||||
* Revoked by {@link #validKeyWithoutExpiration()}.
|
||||
*
|
||||
* <pre>
|
||||
* pub 2048R/3434B39F 2015-10-20 [revoked: 2015-10-20]
|
||||
* Key fingerprint = 931F 047D 7D01 DDEF 367A 8D90 8C4F D28E 3434 B39F
|
||||
* uid Testuser Six <test6@example.com>
|
||||
* </pre>
|
||||
*/
|
||||
public static TestKey revokedCompromisedKey() throws Exception {
|
||||
return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
|
||||
+ "Version: GnuPG v1\n"
|
||||
+ "\n"
|
||||
+ "mQENBFYmpXkBCACqaLz51DcWQmfOJnat9iHSySfSHwbKfVvoN43Ba2cf/D/PadRc\n"
|
||||
+ "HLgc+91k2yk1kV1LnMdvUGj5zZ84ZqrQx3f1WeItnzZpqxtmQS/GSxCp9EY/s7w6\n"
|
||||
+ "5i86R/k9Tzgvk0B7dKZJXbM/OWxxDkkxHWE3Un9wreX7bDU5b9D2knHRiNFqH9ZJ\n"
|
||||
+ "KqDIFZqH9WTUxNZcHz20sTCRIMfvsAwf2vRU5N5xTu4Mbk6JFc7BAj7h1f/mYEPo\n"
|
||||
+ "CTyB1jV/DSDVdn1FjJVocSg6W/CvsYF9hKFYjJHl4VXdePTpnOjHhJLL0QWk0TMe\n"
|
||||
+ "xYeUi/xDr5DeMxTmi7F7BFaQEF+KmUM46e+9ABEBAAGJATAEIAECABoFAlYmq1gT\n"
|
||||
+ "HQJ0ZXN0NiBjb21wcm9taXNlZAAKCRDtBiXcRjKKjIm6B/9YwkyG4w+9KUNESywM\n"
|
||||
+ "bxC2WWGWrFcQGoKxixzt0uT251UY8qxa1IED0wnLsIQmffTQcnrK3B9svd4HhQlk\n"
|
||||
+ "pheKQ3w5iluLeGmGljhDBdAVyS07jYoFUGTXjwzPAgJ3Dxzul8Q8Zj+fOmRcfsP9\n"
|
||||
+ "72kl6g2yEEbevnydWIiOj/vWHVLFb54G8bwXTNwH/FXQsHuPYxXZifwyDwdwEQMq\n"
|
||||
+ "0VTZcrukgeJ+VbSSuq+uX4I3+kJw5hL49KYAQltQBmTo3yhuY/Q+LkgcBv/umtY/\n"
|
||||
+ "DrUqSCBV1bTnfq5SfaObkUu22HWjrtSFSjnXYyh+wyTG3AXG3N9VPrjGQIJIW1j6\n"
|
||||
+ "9QM0iQE3BB8BAgAhBQJWJqYUFwyAAQSup+0vghEz5bEo0e0GJdxGMoqMAgcAAAoJ\n"
|
||||
+ "EIxP0o40NLOfYd4H/3GpfxfJ+nMzBChn1JqFrKOqqYiOO4sUwubXzpRO33V2jUrU\n"
|
||||
+ "V75PTWG/6NlgDbPfKFcU0qZud6M2EQxSS9/I20i/MpRB7qJnWMM/6HxdMDJ0o/pN\n"
|
||||
+ "4ImIGj38QTIWx0DS9n3bwlcobl7ZlM8g2N1kv5jQPEuurffeJRS4ny4pEvCCm2IS\n"
|
||||
+ "SGOuB0DVtYHGDrJLQ0k4mDkEJuU8fP5un8mN8I8eAINlsTFpsTswMXMiptZTm5SI\n"
|
||||
+ "5QZlG3m5MvvckngYdhynvCWc6JHGt1EHXlI4A5Qetr/4FbNE4uYcEEhyzBy4WQfi\n"
|
||||
+ "QCPiIzzm3O4cMnr9N+5HzYqRhu2OveYm86G2Rxq0IFRlc3R1c2VyIFNpeCA8dGVz\n"
|
||||
+ "dDZAZXhhbXBsZS5jb20+iQE4BBMBAgAiBQJWJqV5AhsDBgsJCAcDAgYVCAIJCgsE\n"
|
||||
+ "FgIDAQIeAQIXgAAKCRCMT9KONDSzn2XtB/4wl4ctc3cW9Fwp17cktFi6md8fjRiR\n"
|
||||
+ "wE/ruVKIKmAHzeMLBoZn4LZVunyNCRGLZfP+MUs4JhLkp8ioTzUB7xPl9k94FXel\n"
|
||||
+ "bObn9F0T7htjFLiFAOMeykneylk2kalTt6IBKtaOPn+V6onBwO+YHbwt+xLMhAWj\n"
|
||||
+ "Z/WA0TIC1RIukdzWErhd+9lG8B9kupGC5bPo/AgCPoajPhS1qLrth+lCsNJXT/Rt\n"
|
||||
+ "k6Jx5omypxMXPzgzNtULMFONszaRnHnrCHQg/yJZDCw3ffW5ShfyfWdFM65jgEKo\n"
|
||||
+ "nMKLzy9XV+BM6IJQlgHCBAP8WHKSf4qMG4/hEWLrwA/bTQ7w0DSV88msuQENBFYm\n"
|
||||
+ "pXkBCACzIMFDC6kcV58uvF3XwOrS3DmKNPDNzO/4Ay/iOxZbm+9NP8QWEEm+AzCt\n"
|
||||
+ "ZMfYdZ8C3DjuzxkhcacI/E5agZICds6bs0+VS7VKEeNYp/UrTF9pkZNXseCrJPgr\n"
|
||||
+ "U31eoGVc5bE5c0TGLhAjbMKtR5LZFMpAXgpA7hXJSSuAXGs8gjkJkYSJYnJwIOyd\n"
|
||||
+ "xOi5jmnE/U5QuMjBG0bwxFXxkaDa5mcebJ/6C8mgkKyATbQkCe7YJGl1JLK4vY28\n"
|
||||
+ "ybSMhMDtZiwgvKzd+HcQr+xUQvmgSMApJaMxKPHRA1IrP/STXUEAjcGfk/HCz/0j\n"
|
||||
+ "7mJG2cvCxeOMAmp/pTzhSoXiqUNlABEBAAGJAR8EGAECAAkFAlYmpXkCGwwACgkQ\n"
|
||||
+ "jE/SjjQ0s5/kVAf/QvHOhuoBSlSxPcgvnvCl8V3zbNR1P9lgjYGwMsvLhwCT7Wvm\n"
|
||||
+ "mkUKvtT913uER93N8xJD2svGhKabpiPj9/eo0p3p64dicijsP1UQfpmWKPa/V9sv\n"
|
||||
+ "zep08cpDl/eczSiLqgcTXCoZeewWXoQGqqoXnwa4lwQv4Zvj7TTCN2wRzoGwbRcm\n"
|
||||
+ "G2hmc27uOwA+hXbF+bLe6HOZR/7U93j8a22g2X9OgST/QCsLgyiUSw3YYaEan9tn\n"
|
||||
+ "wuEgAEY/rchOvgeXe5Sl0wTFLHH6OS4BBGgc1LRKnSCM2dgZqvhOOxOvuuieBWY6\n"
|
||||
+ "tULvIEIjNNP8Qizfc4u2O8h7HP2b3yYSrp9MMQ==\n"
|
||||
+ "=Dxr7\n"
|
||||
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
|
||||
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
|
||||
+ "Version: GnuPG v1\n"
|
||||
+ "\n"
|
||||
+ "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\n"
|
||||
+ "i7Y7yHsc/ZvfJhKun0wx\n"
|
||||
+ "=M/kw\n"
|
||||
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* A key revoked by a valid key, due to no longer being used.
|
||||
* <p>
|
||||
* Revoked by {@link #validKeyWithoutExpiration()}.
|
||||
*
|
||||
* <pre>
|
||||
* pub 2048R/3D6C52D0 2015-10-20 [revoked: 2015-10-20]
|
||||
* Key fingerprint = 32DB 6C31 2ED7 A98D 11B2 43EA FAD2 ABE2 3D6C 52D0
|
||||
* uid Testuser Seven <test7@example.com>
|
||||
* </pre>
|
||||
*/
|
||||
public static TestKey revokedNoLongerUsedKey() throws Exception {
|
||||
return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
|
||||
+ "Version: GnuPG v1\n"
|
||||
+ "\n"
|
||||
+ "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\n"
|
||||
+ "+B6paw/s8P7XCWznTBRdlFWWgrhcpzQ8fefC\n"
|
||||
+ "=CHer\n"
|
||||
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
|
||||
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
|
||||
+ "Version: GnuPG v1\n"
|
||||
+ "\n"
|
||||
+ "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\n"
|
||||
+ "bOdMFF2UVZaCuFynNDx958I=\n"
|
||||
+ "=aoJv\n"
|
||||
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user