blob: 266f8684fae40e3c2f20c3888a4aa548582c88c5 [file] [log] [blame]
// 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.gpg;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static com.google.gerrit.gpg.testing.TestKeys.expiredKey;
import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithExpiration;
import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithoutExpiration;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testing.TestKey;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
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.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;
import org.eclipse.jgit.transport.PushCertificateParser;
import org.eclipse.jgit.transport.SignedPushConfig;
import org.junit.Before;
import org.junit.Test;
public class PushCertificateCheckerTest {
private InMemoryRepository repo;
private PublicKeyStore store;
private SignedPushConfig signedPushConfig;
private PushCertificateChecker checker;
@Before
public void setUp() throws Exception {
TestKey key1 = validKeyWithoutExpiration();
TestKey key3 = expiredKey();
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);
checker = newChecker(true);
}
private PushCertificateChecker newChecker(boolean checkNonce) {
PublicKeyChecker keyChecker = new PublicKeyChecker().setStore(store);
return new PushCertificateChecker(keyChecker) {
@Override
protected Repository getRepository() {
return repo;
}
@Override
protected boolean shouldClose(Repository repo) {
return false;
}
}.setCheckNonce(checkNonce);
}
@Test
public void validCert() throws Exception {
PushCertificate cert = newSignedCert(validNonce(), validKeyWithoutExpiration());
assertNoProblems(cert);
}
@Test
public void invalidNonce() throws Exception {
PushCertificate cert = newSignedCert("invalid-nonce", validKeyWithoutExpiration());
assertProblems(cert, "Invalid nonce");
}
@Test
public void invalidNonceNotChecked() throws Exception {
checker = newChecker(false);
PushCertificate cert = newSignedCert("invalid-nonce", validKeyWithoutExpiration());
assertNoProblems(cert);
}
@Test
public void missingKey() throws Exception {
TestKey key2 = validKeyWithExpiration();
PushCertificate cert = newSignedCert(validNonce(), key2);
assertProblems(cert, "No public keys found for key ID " + keyIdToString(key2.getKeyId()));
}
@Test
public void invalidKey() throws Exception {
TestKey key3 = expiredKey();
PushCertificate cert = newSignedCert(validNonce(), key3);
assertProblems(
cert, "Invalid public key " + keyToString(key3.getPublicKey()) + ":\n Key is expired");
}
@Test
public void signatureByExpiredKeyBeforeExpiration() throws Exception {
TestKey key3 = expiredKey();
Date now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse("2005-07-10 12:00:00 -0400");
PushCertificate cert = newSignedCert(validNonce(), key3, now);
assertNoProblems(cert);
}
private String validNonce() {
return signedPushConfig
.getNonceGenerator()
.createNonce(repo, System.currentTimeMillis() / 1000);
}
private PushCertificate newSignedCert(String nonce, TestKey signingKey) throws Exception {
return newSignedCert(nonce, signingKey, null);
}
private PushCertificate newSignedCert(String nonce, TestKey signingKey, Date now)
throws Exception {
PushCertificateIdent ident =
new PushCertificateIdent(signingKey.getFirstUserId(), System.currentTimeMillis(), -7 * 60);
String payload =
"certificate version 0.1\n"
+ "pusher "
+ ident.getRaw()
+ "\n"
+ "pushee test://localhost/repo.git\n"
+ "nonce "
+ nonce
+ "\n"
+ "\n"
+ "0000000000000000000000000000000000000000"
+ " deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+ " refs/heads/master\n";
PGPSignatureGenerator gen =
new PGPSignatureGenerator(
new BcPGPContentSignerBuilder(signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1));
if (now != null) {
PGPSignatureSubpacketGenerator subGen = new PGPSignatureSubpacketGenerator();
subGen.setSignatureCreationTime(false, now);
gen.setHashedSubpackets(subGen.generate());
}
gen.init(PGPSignature.BINARY_DOCUMENT, signingKey.getPrivateKey());
gen.update(payload.getBytes(UTF_8));
PGPSignature sig = gen.generate();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (BCPGOutputStream out = new BCPGOutputStream(new ArmoredOutputStream(bout))) {
sig.encode(out);
}
String cert = payload + new String(bout.toByteArray(), UTF_8);
Reader reader = new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)), UTF_8);
PushCertificateParser parser = new PushCertificateParser(repo, signedPushConfig);
return parser.parse(reader);
}
private void assertProblems(PushCertificate cert, String first, String... rest) throws Exception {
List<String> expected = new ArrayList<>();
expected.add(first);
expected.addAll(Arrays.asList(rest));
CheckResult result = checker.check(cert).getCheckResult();
assertEquals(expected, result.getProblems());
}
private void assertNoProblems(PushCertificate cert) {
CheckResult result = checker.check(cert).getCheckResult();
assertEquals(Collections.emptyList(), result.getProblems());
}
}