Move most GPG-related code to a gerrit-gpg module

Bouncy Castle is still an optional dependency for Gerrit, so we want
to avoid accidentally attempting to load Bouncy Castle classes when
they might not be available. Rather than try to guard every
org.bouncycastle.* reference with a hasPGP() check, reduce the surface
area of calls that actually require Bouncy Castle. Move almost all
code that calls Bouncy Castle into a new module, gerrit-gpg.

Callers need only interact with this module by installing the
GpgModule, which is careful to protect all Bouncy Castle class loading
with the appropriate havePGP() check. Moreover, this module doesn't
need to be installed in the gerrit-server package at all, so we can
break the compile-time dependency between gerrit-server and Bouncy
Castle, so accidentally introducing a dependency on Bouncy Castle
results in a compile error.

The REST API and extension APIs dealing with GPG keys only refer to
the GpgKeyInfo POJO, and don't need to actually refer to Bouncy Castle
classes. Add a shim interface, GpgApiAdapter, that is used by
AccountApiImpl to process GPG keys. GpgModule binds this interface to
either the Bouncy Castle enabled implementation, or a not-implemented
implementation.

Since there are various places in the server code where we want to
inspect whether signed push is enabled at the server level, but we
don't want to have to call into gerrit-gpg code to do this, bind a
boolean with @EnableSignedPush from GpgModule.

Change-Id: Idbab00a52d86216cae73d02876d56be54aef6581
This commit is contained in:
Dave Borowitz
2015-08-26 09:51:27 -04:00
parent b84c227162
commit b8336f176a
45 changed files with 521 additions and 164 deletions

View File

@@ -0,0 +1,202 @@
// 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.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.util.Providers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Collections;
/** Unit tests for {@link GerritPublicKeyChecker}. */
public class GerritPublicKeyCheckerTest {
@Inject
private AccountCache accountCache;
@Inject
private AccountManager accountManager;
@Inject
private GerritPublicKeyChecker checker;
@Inject
private IdentifiedUser.GenericFactory userFactory;
@Inject
private InMemoryDatabase schemaFactory;
@Inject
private SchemaCreator schemaCreator;
@Inject
private ThreadLocalRequestContext requestContext;
private LifecycleManager lifecycle;
private ReviewDb db;
private Account.Id userId;
private IdentifiedUser user;
@Before
public void setUpInjector() throws Exception {
Injector injector = Guice.createInjector(new InMemoryModule());
lifecycle = new LifecycleManager();
lifecycle.add(injector);
injector.injectMembers(this);
lifecycle.start();
db = schemaFactory.open();
schemaCreator.create(db);
userId =
accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
Account userAccount = db.accounts().get(userId);
// Note: does not match any key in TestKey.
userAccount.setPreferredEmail("user@example.com");
db.accounts().update(ImmutableList.of(userAccount));
user = reloadUser();
requestContext.setContext(new RequestContext() {
@Override
public CurrentUser getCurrentUser() {
return user;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return Providers.of(db);
}
});
}
private IdentifiedUser reloadUser() {
accountCache.evict(userId);
user = userFactory.create(Providers.of(db), userId);
return user;
}
@After
public void tearDownInjector() {
if (lifecycle != null) {
lifecycle.stop();
}
if (db != null) {
db.close();
}
InMemoryDatabase.drop(schemaFactory);
}
@Test
public void defaultGpgCertificationMatchesEmail() throws Exception {
TestKey key = TestKey.key5();
assertProblems(
TestKey.key5(),
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " gerrit:user\n"
+ " username:user");
addExternalId("test", "test", "test5@example.com");
assertNoProblems(key);
}
@Test
public void defaultGpgCertificationDoesNotMatchEmail() throws Exception {
addExternalId("test", "test", "nobody@example.com");
assertProblems(
TestKey.key5(),
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " gerrit:user\n"
+ " nobody@example.com\n"
+ " test:test\n"
+ " username:user");
}
@Test
public void manualCertificationMatchesExternalId() throws Exception {
addExternalId("foo", "myId", null);
assertNoProblems(TestKey.key5());
}
@Test
public void manualCertificationDoesNotExternalId() throws Exception {
addExternalId("foo", "otherId", null);
assertProblems(
TestKey.key5(),
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " foo:otherId\n"
+ " gerrit:user\n"
+ " username:user");
}
@Test
public void noExternalIds() throws Exception {
db.accountExternalIds().delete(
db.accountExternalIds().byAccount(user.getAccountId()));
reloadUser();
assertProblems(
TestKey.key5(),
"No identities found for user; check"
+ " http://test/#/settings/web-identities");
}
private void assertNoProblems(TestKey key) throws Exception {
assertThat(checker.check(key.getPublicKey()).getProblems()).isEmpty();
}
private void assertProblems(TestKey key, String... expected)
throws Exception {
checkArgument(expected.length > 0);
assertThat(checker.check(key.getPublicKey()).getProblems())
.containsExactly((Object[]) expected)
.inOrder();
}
private void addExternalId(String scheme, String id, String email)
throws Exception {
AccountExternalId extId = new AccountExternalId(user.getAccountId(),
new AccountExternalId.Key(scheme, id));
if (email != null) {
extId.setEmailAddress(email);
}
db.accountExternalIds().insert(Collections.singleton(extId));
reloadUser();
}
}

View File

@@ -0,0 +1,68 @@
// 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 org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testutil.TestKey;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
public class PublicKeyCheckerTest {
private PublicKeyChecker checker;
@Before
public void setUp() {
checker = new PublicKeyChecker();
}
@Test
public void validKey() throws Exception {
assertProblems(TestKey.key1());
}
@Test
public void wrongKeyId() throws Exception {
TestKey k = TestKey.key1();
long badId = k.getKeyId() + 1;
CheckResult result = checker.check(k.getPublicKey(), badId);
assertEquals(
Arrays.asList("Public key does not match ID 46328A8D"),
result.getProblems());
}
@Test
public void keyExpiringInFuture() throws Exception {
assertProblems(TestKey.key2());
}
@Test
public void expiredKey() throws Exception {
assertProblems(TestKey.key3(), "Key is expired");
}
@Test
public void selfRevokedKey() throws Exception {
assertProblems(TestKey.key4(), "Key is revoked");
}
private void assertProblems(TestKey tk, String... expected) throws Exception {
CheckResult result = checker.check(tk.getPublicKey(), tk.getKeyId());
assertEquals(Arrays.asList(expected), result.getProblems());
}
}

View File

@@ -0,0 +1,252 @@
// 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.keyObjectId;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.RefNames;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
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.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class PublicKeyStoreTest {
private TestRepository<?> tr;
private PublicKeyStore store;
@Before
public void setUp() throws Exception {
tr = new TestRepository<>(new InMemoryRepository(
new DfsRepositoryDescription("pubkeys")));
store = new PublicKeyStore(tr.getRepository());
}
@Test
public void testKeyIdToString() throws Exception {
PGPPublicKey key = TestKey.key1().getPublicKey();
assertEquals("46328A8C", keyIdToString(key.getKeyID()));
}
@Test
public void testKeyToString() throws Exception {
PGPPublicKey key = TestKey.key1().getPublicKey();
assertEquals("46328A8C Testuser One <test1@example.com>"
+ " (04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C)",
keyToString(key));
}
@Test
public void testKeyObjectId() throws Exception {
PGPPublicKey key = TestKey.key1().getPublicKey();
String objId = keyObjectId(key.getKeyID()).name();
assertEquals("ed0625dc46328a8c000000000000000000000000", objId);
assertEquals(keyIdToString(key.getKeyID()).toLowerCase(),
objId.substring(8, 16));
}
@Test
public void testGet() throws Exception {
TestKey key1 = TestKey.key1();
tr.branch(RefNames.REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key1.getKeyId()).name(),
key1.getPublicKeyArmored())
.create();
TestKey key2 = TestKey.key2();
tr.branch(RefNames.REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key2.getKeyId()).name(),
key2.getPublicKeyArmored())
.create();
assertKeys(key1.getKeyId(), key1);
assertKeys(key2.getKeyId(), key2);
}
@Test
public void testGetMultiple() throws Exception {
TestKey key1 = TestKey.key1();
TestKey key2 = TestKey.key2();
tr.branch(RefNames.REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key1.getKeyId()).name(),
key1.getPublicKeyArmored()
// Mismatched for this key ID, but we can still read it out.
+ key2.getPublicKeyArmored())
.create();
assertKeys(key1.getKeyId(), key1, key2);
}
@Test
public void save() throws Exception {
TestKey key1 = TestKey.key1();
TestKey key2 = TestKey.key2();
store.add(key1.getPublicKeyRing());
store.add(key2.getPublicKeyRing());
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1);
assertKeys(key2.getKeyId(), key2);
}
@Test
public void saveAppendsToExistingList() throws Exception {
TestKey key1 = TestKey.key1();
TestKey key2 = TestKey.key2();
tr.branch(RefNames.REFS_GPG_KEYS)
.commit()
// Mismatched for this key ID, but we can still read it out.
.add(keyObjectId(key1.getKeyId()).name(), key2.getPublicKeyArmored())
.create();
store.add(key1.getPublicKeyRing());
assertEquals(RefUpdate.Result.FAST_FORWARD, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1, key2);
try (ObjectReader reader = tr.getRepository().newObjectReader();
RevWalk rw = new RevWalk(reader)) {
NoteMap notes = NoteMap.read(
reader, tr.getRevWalk().parseCommit(
tr.getRepository().getRef(RefNames.REFS_GPG_KEYS).getObjectId()));
String contents = new String(
reader.open(notes.get(keyObjectId(key1.getKeyId()))).getBytes(),
UTF_8);
String header = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
int i1 = contents.indexOf(header);
assertTrue(i1 >= 0);
int i2 = contents.indexOf(header, i1 + header.length());
assertTrue(i2 >= 0);
}
}
@Test
public void updateExisting() throws Exception {
TestKey key5 = TestKey.key5();
PGPPublicKeyRing keyRing = key5.getPublicKeyRing();
PGPPublicKey key = keyRing.getPublicKey();
store.add(keyRing);
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
assertUserIds(store.get(key5.getKeyId()).iterator().next(),
"Testuser Five <test5@example.com>",
"foo:myId");
keyRing = PGPPublicKeyRing.removePublicKey(keyRing, key);
key = PGPPublicKey.removeCertification(key, "foo:myId");
keyRing = PGPPublicKeyRing.insertPublicKey(keyRing, key);
store.add(keyRing);
assertEquals(RefUpdate.Result.FAST_FORWARD, store.save(newCommitBuilder()));
Iterator<PGPPublicKeyRing> keyRings = store.get(key.getKeyID()).iterator();
keyRing = keyRings.next();
assertFalse(keyRings.hasNext());
assertUserIds(keyRing, "Testuser Five <test5@example.com>");
}
@Test
public void remove() throws Exception {
TestKey key1 = TestKey.key1();
store.add(key1.getPublicKeyRing());
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1);
store.remove(key1.getPublicKey().getFingerprint());
assertEquals(RefUpdate.Result.FAST_FORWARD, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId());
}
@Test
public void removeNonexisting() throws Exception {
TestKey key1 = TestKey.key1();
store.add(key1.getPublicKeyRing());
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
TestKey key2 = TestKey.key2();
store.remove(key2.getPublicKey().getFingerprint());
assertEquals(RefUpdate.Result.NO_CHANGE, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1);
}
@Test
public void addThenRemove() throws Exception {
TestKey key1 = TestKey.key1();
store.add(key1.getPublicKeyRing());
store.remove(key1.getPublicKey().getFingerprint());
assertEquals(RefUpdate.Result.NO_CHANGE, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId());
}
private void assertKeys(long keyId, TestKey... expected)
throws Exception {
Set<String> expectedStrings = new TreeSet<>();
for (TestKey k : expected) {
expectedStrings.add(keyToString(k.getPublicKey()));
}
PGPPublicKeyRingCollection actual = store.get(keyId);
Set<String> actualStrings = new TreeSet<>();
for (PGPPublicKeyRing k : actual) {
actualStrings.add(keyToString(k.getPublicKey()));
}
assertEquals(expectedStrings, actualStrings);
}
private void assertUserIds(PGPPublicKeyRing keyRing, String... expected)
throws Exception {
List<String> actual = new ArrayList<>();
@SuppressWarnings("unchecked")
Iterator<String> userIds = store.get(keyRing.getPublicKey().getKeyID())
.iterator().next().getPublicKey().getUserIDs();
while (userIds.hasNext()) {
actual.add(userIds.next());
}
assertEquals(actual, Arrays.asList(expected));
}
private CommitBuilder newCommitBuilder() {
CommitBuilder cb = new CommitBuilder();
PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
cb.setAuthor(ident);
cb.setCommitter(ident);
return cb;
}
}

View File

@@ -0,0 +1,154 @@
// 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 java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.RefNames;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
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.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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Arrays;
public class PushCertificateCheckerTest {
private TestRepository<?> tr;
private SignedPushConfig signedPushConfig;
private PushCertificateChecker checker;
@Before
public void setUp() throws Exception {
TestKey key1 = TestKey.key1();
TestKey key3 = TestKey.key3();
tr = new TestRepository<>(new InMemoryRepository(
new DfsRepositoryDescription("repo")));
tr.branch(RefNames.REFS_GPG_KEYS).commit()
.add(PublicKeyStore.keyObjectId(key1.getPublicKey().getKeyID()).name(),
key1.getPublicKeyArmored())
.add(PublicKeyStore.keyObjectId(key3.getPublicKey().getKeyID()).name(),
key3.getPublicKeyArmored())
.create();
signedPushConfig = new SignedPushConfig();
signedPushConfig.setCertNonceSeed("sekret");
signedPushConfig.setCertNonceSlopLimit(60 * 24);
checker = new PushCertificateChecker(new PublicKeyChecker()) {
@Override
protected Repository getRepository() {
return tr.getRepository();
}
@Override
protected boolean shouldClose(Repository repo) {
return false;
}
};
}
@Test
public void validCert() throws Exception {
PushCertificate cert = newSignedCert(validNonce(), TestKey.key1());
assertProblems(cert);
}
@Test
public void invalidNonce() throws Exception {
PushCertificate cert = newSignedCert("invalid-nonce", TestKey.key1());
assertProblems(cert, "Invalid nonce");
}
@Test
public void missingKey() throws Exception {
TestKey key2 = TestKey.key2();
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 = TestKey.key3();
PushCertificate cert = newSignedCert(validNonce(), key3);
assertProblems(cert,
"Invalid public key (" + keyToString(key3.getPublicKey())
+ "):\n Key is expired");
}
private String validNonce() {
return signedPushConfig.getNonceGenerator()
.createNonce(tr.getRepository(), System.currentTimeMillis() / 1000);
}
private PushCertificate newSignedCert(String nonce, TestKey signingKey)
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));
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)));
PushCertificateParser parser =
new PushCertificateParser(tr.getRepository(), signedPushConfig);
return parser.parse(reader);
}
private void assertProblems(PushCertificate cert, String... expected)
throws Exception {
CheckResult result = checker.check(cert);
assertEquals(Arrays.asList(expected), result.getProblems());
}
}

View File

@@ -0,0 +1,622 @@
// 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.testutil;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import com.google.common.collect.ImmutableList;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.eclipse.jgit.lib.Constants;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class TestKey {
public static ImmutableList<TestKey> allValidKeys() {
return ImmutableList.of(key1(), key2(), key5());
}
/**
* A valid key with no expiration.
*
* <pre>
* pub 2048R/46328A8C 2015-07-08
* Key fingerprint = 04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C
* uid Testuser One &lt;test1@example.com&gt;
* sub 2048R/F0AF69C0 2015-07-08
* </pre>
*/
public static TestKey key1() {
return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "mQENBFWdTIkBCADOaygDKjLuRX6LXAvBAYB91cmTf1MSlmEy+qsG3c9ijjQixPkr\n"
+ "atdYkocrrT2S0R9UGjksTOI2WN5S0lQfLA1RSk63KURQE+OF+IfNqdD6nQdLBs1w\n"
+ "va+GDj/uvuI05I0oXf/M7POdFphutrS4EUDBnFPj6ns/0C2sTRTxliD+Y9Y9a84V\n"
+ "DfVVUbJB6wc3LP3L6ImT+cSM7dLq3hZHya+9FNeYPmPYnBrkJyqf2NDd38Sddsro\n"
+ "7smw/GgCZHnnuVNS4C7NsHr6900VKC+JDtdx+fqptixcAEJWiGoQfWqU+hYmia3p\n"
+ "9+Xw02+3FcjOT6ONUCmHX+xlz0pXW4iIYlPpABEBAAG0IFRlc3R1c2VyIE9uZSA8\n"
+ "dGVzdDFAZXhhbXBsZS5jb20+iQE4BBMBAgAiBQJVnUyJAhsDBgsJCAcDAgYVCAIJ\n"
+ "CgsEFgIDAQIeAQIXgAAKCRDtBiXcRjKKjHblB/9RaFO5+GTDIphAL/aVj2u+d8Lq\n"
+ "yUpBrDp3P06QDGpKGFMAovBuh+NLH76VKNIzQLQC8rdTj651fLcLMuJ1enQ3Rblg\n"
+ "RKr1oc+wqqtFHr4QyOQjE/N3C9GQjEzfqn4qnp5KtZxYFnlvU5NGehid7M1HTZMx\n"
+ "jRcHbM9KQnsE5Z4fh4wmN5ynG+5nbaF4O9otPOpFzYRvIhxFmHscWyOgRaMZiYEX\n"
+ "7Qkzze+scAlc9E/EWRJQIFcxnxV/SYIT4qCTT1g2aKA8OCBO/ZTOleH8SzvTODjy\n"
+ "W0lGHnh/ZqH6XGVcGUaJZZ2uHTck1+czuVVShNcXPW1W20T6E9UqzHbJHN0guQEN\n"
+ "BFWdTIkBCACoLVdPr3gpQwzI+2NGXjdtoyqYoPlgfeyI2M1XQD/7+rLZTbi14ZjN\n"
+ "vYkS/+/oGtVEmiYOiAVTwmkjCYkKGDgNcCiJVekiPAN6JryVv488wRc999b5LpFE\n"
+ "fhLGwI0YxjcS4KFFnpMC3wSb6tJUnHRLVoE5d8icdiaOpgYdp7uqWkSx2oxqHgIb\n"
+ "nuyrk3ydEcS4ZeGD+w+taIxMc9F1DS9kiXALD7xWgUkmqZLEQoNgF6KlwCHXRd3m\n"
+ "rBCo97sE95yKcq98ZMIWuQtTcEccZsN/6jlsei+9RI0tqs+FbZnIFm/go9zk11Vl\n"
+ "IQ9QFSj6ruqoKrYvNZuDDLD1lHvZPD4/ABEBAAGJAR8EGAECAAkFAlWdTIkCGwwA\n"
+ "CgkQ7QYl3EYyiox+HAf/Z/OCQO3jxALAcn3oUb1g/IlHm6qZv7RJOFUsj/16fGiF\n"
+ "rRTP15zMXzyqV+L/LGV/owvOsdD/o7boZz4C/U98COx0Nl1jOrmPATOl+xqsgpEj\n"
+ "Fhk+eAR7exO2XxW+u2g4cYoSMosIOX5w1GrdsxQeaZDwiSJMEOR2cVLs3YI19Ci/\n"
+ "FuzActZ0wJNk0nlNF6l8CAbzwN6pM9OIc/iBIwDjz92KUco0NF8XKZnxqhH4wfHB\n"
+ "PGkTx8RwOvELUTDMtvYnG5R0QtND0RbOnmp4ZVZmeOjKSLo1mZliUZB1H2PPSxrA\n"
+ "0oLr8+wLntz1SU7uS4ddvhSQW+j2M/0pa352KUwmrw==\n"
+ "=o/aU\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "=MuAn\n"
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
}
/**
* A valid key expiring in 2065.
*
* <pre>
* pub 2048R/378A0AED 2015-07-08 [expires: 2065-06-25]
* Key fingerprint = C378 369A CBCD 34CC 138D 90B1 4531 1A6F 378A 0AED
* uid Testuser Two &lt;test2@example.com&gt;
* sub 2048R/46D4F204 2015-07-08 [expires: 2065-06-25]
* </pre>
*/
public static final TestKey key2() {
return new TestKey(
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "mQENBFWdTP8BCADRxNpasIv0jtNXTK6VYIS2VJ2Xk0ZD6gtxeoXCpjQ+TsB9fxh3\n"
+ "vAMPt2Zu5LqoGwygKOJj1zquG8xk7GUCCHJk3+qG8xxB1xGtSz2vLyfRm7fOZmHj\n"
+ "3W/C/25lynSPDrfvcwvwA4PN8iP5EWbWU10L6WOZGMwwwtVDUSEouSOw2LEepxLV\n"
+ "rkKuZcyHaivheDbUlZliwe9rGXd4hh1h4qyNQWG3q+ytlL28sVkOzUh6IMBTvqhe\n"
+ "IRsvxvaVSLV8jRVKfUTqw0g57ft4ZD2/L46yUTXzr9aUCBjTNxvWLlyboqql/D8P\n"
+ "inp51h3cvAg7NW5RdG1GEYmylH8SygT5utPxABEBAAG0IFRlc3R1c2VyIFR3byA8\n"
+ "dGVzdDJAZXhhbXBsZS5jb20+iQE+BBMBAgAoBQJVnUz/AhsDBQld/A8ABgsJCAcD\n"
+ "AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBFMRpvN4oK7UZqCACWwQL/YvBK4b0m+R0d\n"
+ "UdvAXeBx7DwOAnAodis9ZVqChb7RxcZQxF1Ti9mtCBPPQGuEs5wE2Ocrrq+L13r6\n"
+ "bgW+1WOB1tZSDVxwL1PnZFw/SyADRIDCZrOHiAkp82UnZwWAkk39GzNJtt1wTYDZ\n"
+ "FMTFUr2SPscXk1k7muS+ZfEFwNPD4tODo/poJKDYEJ80Z5UXXFQLDtsfdeIXMFIT\n"
+ "449CYoq8XBMBfvyWl/LLpw0r3JI6pV/YdH3Oeuz8XkkEVzRxaxB6Zmeo5jSwjR/T\n"
+ "8TKDGwwiuwiiT3SfkFSVdcjKulRuXSRNs1Ouf7/UC3cq4bG2WXWa85X1+HQRm7iu\n"
+ "RHSOuQENBFWdTP8BCADhhGxAA0pX5yBHwIgM1j0gw2h5nSsopDrO6t/sbRUcNxnR\n"
+ "tBScgKZnP0sjRTYEUIwmZuseHMBohtVCuMaDt06qyZDvDk/98j3AeE5t2dgFnOIe\n"
+ "qCrm/6aejbFcQOpxe6U29KJRCAxuwNtB15X1VH1Kj7B0gRSTu13n/5sUsi2lunoZ\n"
+ "oIvpIe9tZH4aXitCY2MCQH+hTyCyNBzlEa44kWz6LxUsPdo7I6rXkTr6Ot7wQh+9\n"
+ "7HCe042GIq65h0apgujyjhJidjch5ur1mngaSNSEyvbji2MGC+cd3wAIstG5a7xP\n"
+ "d9MncY5Q/eH+hn96694k5bckottSyGm/3f2Ihfj1ABEBAAGJASUEGAECAA8FAlWd\n"
+ "TP8CGwwFCV38DwAACgkQRTEabzeKCu1FNwgAif4eK2v7R3QubL2S6wmb1nsgRMgV\n"
+ "YoxGBeUk2EK6WZ5IPor93ySd0ixRVNMRmJ8BLH3EMjZQTzkDG+BH6zFyxo6lLHw9\n"
+ "NxQjI06tqQWgyyK0mEweVwB/zqtxiB4lNUpsNbqOZWnBJ3d6o1SsnD2Q3uwvP5fb\n"
+ "fSIgdmUk3c0VMdgA+KzWjPD/PJIPujE+ckHhjn5cbDNw35/FuyhkLJfqlOG7SPvM\n"
+ "NmCdJ1Pcqju9t7sf6b0BGPDOCL4gpuWKK7HJz9WxngNb3FSziLbyPLk13ynADO+v\n"
+ "EOR44LPyXE9kVxPusazsXlt9ayTOhELhwzw7sGFFu8E17Cpn7GnVj3tN9A==\n"
+ "=1e/A\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "9A==\n"
+ "=qbV3\n"
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
}
/**
* A key that expired in 2006.
*
* <pre>
* pub 2048R/17DE1ACD 2005-07-08 [expired: 2006-07-08]
* Key fingerprint = 1D9E EB79 DD38 B049 939D 9CAF 3CEC 781B 17DE 1ACD
* uid Testuser Three &lt;test3@example.com&gt;
* </pre>
*/
public static final TestKey key3() {
return new TestKey(
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "mQENBELOp0UBCACxholOPWuKhK+TYb88nvLUSCMvTLIFEpb5u3Eavr0wiluEzq6H\n"
+ "55nswAD3dQm8DWxA7yUlEYjPr5btpw7V9441bb1+qtgZMJ10RTdEb/WjyctdGA99\n"
+ "uOKBEarWbt8W+w6lyJ9NXy5bS/x5EwHHfoTFp4ff6ffHI5hbx1a00K8oxmitgd0X\n"
+ "Mx86UmauFNJYupZOZG9gEcP4RbRp7e2pm4Jy1WLEOeg9Fdgm5e5Hj2nMkCSZ9BKV\n"
+ "cxuOllSVzM/Zp0/4+RS9R57jKo3/V74Whwh9yQNgL9UxdNk7L0eGqvaT3EjXxjOc\n"
+ "RCeJiucGN/0W2iq+V01/QGspp4SKtAogWBozABEBAAG0IlRlc3R1c2VyIFRocmVl\n"
+ "IDx0ZXN0M0BleGFtcGxlLmNvbT6JAT4EEwECACgFAkLOp0UCGwMFCQHhM4AGCwkI\n"
+ "BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEDzseBsX3hrNYg0H/2CMm5/JDQNSuRFC\n"
+ "ECWLrcOeimuvwbmkonNzOkvKbGXl73GStISAksRWAHBQED1rEPC0NkFCDeVZO7df\n"
+ "SYLlsqKwV6uSh05Ra0F5XeniC12YpAyzoQyCGRS2wLaS822j0zUPXA8XLaO2blCu\n"
+ "R+8sNu/oecMRcFK4S9NaApi3vdqBNhLiN1/Lpqn1LfB8uIO+eaUf4PmCWbaPgzSk\n"
+ "qcPfKZmocNXdgLV5Q80n3hc2y2nrl+vDW2M+eVZuDHAok2BOD9uGKFfLAbaXLbX5\n"
+ "btBW2L0UHtoEyiqkRfD6lX2laSLQmA6+eup7e4GS+s0vXBuVh8XEYddV6Yjt8H7/\n"
+ "2thO41K5AQ0EQs6nRQEIAM/833UHK1DuFlOm7/n18dRMvs7BkXvg+hPquKWMG3be\n"
+ "eE4sh1NG5DbRCdo6iacZLarWr3FDz7J9+wswRhtHCh3pGHEuaJk52vRjQxlkNh5F\n"
+ "p5u2R4WF546bWqX45xPdLfHVTPyWB9q7aVxE+6Q+MHa6lMoyTVnTVCOy3nshiihw\n"
+ "dxLsxaga+QmaL0bAR+dRcO6ucj7TDQXz1AJAVp26c0LXV9iErhFuuybUZKT0a9Aj\n"
+ "FoumMZ6l+k30sSdjSjpBMsNvPos0dTPPRXUMu77o5sj+pHa4o8WctgB3o7BHQELp\n"
+ "KgujZ2sKC9Nm395u6Q4cqUWihzb/Y7rIRuNHJarI7vUAEQEAAYkBJQQYAQIADwUC\n"
+ "Qs6nRQIbDAUJAeEzgAAKCRA87HgbF94azRiBB/4vAyOOjUjK3lDWjHGs7mvEWJI/\n"
+ "1MeLlGPswCSInJBa+HMiMI4tzq+hu5ejGThojNbmnL96GdzfDkMlP4Feyxb2rjtb\n"
+ "NrD/R5tlXHmjX/QLzep4LCeMziP80fu8qUeiOej/Ecdny0w365PlMdt10RaYR8VE\n"
+ "ZX/DAie6JfElnfQcG5q8TIOH3i71qxV+kIoPqKWfQ0MXrNEJ3BYFfDGdUt8U1Kq9\n"
+ "OuIHVRgGS7mMSyjgNqqp7MBeMY+PFFZaZel5yoYVjb9d3L8XvVv2eoa/jPj5FUEU\n"
+ "kE9uxNmwaD1PiV8DvBTYI+eQL4qzfu+3NTG2SfgQYtj5oiGHw8aL3U6QHDJb\n"
+ "=d/Xp\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "HDJb\n"
+ "=RrXv\n"
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
}
/**
* A self-revoked key with no expiration.
*
* <pre>
* pub 2048R/7CA87821 2015-07-08 [revoked: 2015-07-08]
* Key fingerprint = E328 CAB1 1F7E B1BC 1451 ABA5 0855 2A17 7CA8 7821
* uid Testuser Four &lt;test4@example.com&gt;
* </pre>
*/
public static final TestKey key4() {
return new TestKey(
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "=477N\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "=5aNq\n"
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
}
/**
* A key with an additional user ID.
*
* <pre>
* pub 2048R/98C51DBF 2015-07-30
* Key fingerprint = 42B3 294D 1924 D7EB AF4A A99F 5024 BB44 98C5 1DBF
* uid foo:myId
* uid Testuser Five <test5@example.com>
* sub 2048R/C781A9E3 2015-07-30
* </pre>
*/
public static TestKey key5() {
return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "54w16Vp/SVViJs7nTMSug0WREyd2\n"
+ "=ldwB\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "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\n"
+ "=uND5\n"
+ "-----END PGP PRIVATE KEY BLOCK-----\n");
}
// TODO(dborowitz): Figure out how to get gpg to revoke a key for someone
// else.
private final String pubArmored;
private final String secArmored;
private final PGPPublicKeyRing pubRing;
private final PGPSecretKeyRing secRing;
private TestKey(String pubArmored, String secArmored) {
this.pubArmored = pubArmored;
this.secArmored = secArmored;
BcKeyFingerprintCalculator fc = new BcKeyFingerprintCalculator();
try {
this.pubRing = new PGPPublicKeyRing(newStream(pubArmored), fc);
this.secRing = new PGPSecretKeyRing(newStream(secArmored), fc);
} catch (PGPException | IOException e) {
throw new AssertionError(e);
}
}
public String getPublicKeyArmored() {
return pubArmored;
}
public String getSecretKeyArmored() {
return secArmored;
}
public PGPPublicKeyRing getPublicKeyRing() {
return pubRing;
}
public PGPPublicKey getPublicKey() {
return pubRing.getPublicKey();
}
public PGPSecretKey getSecretKey() {
return secRing.getSecretKey();
}
public long getKeyId() {
return getPublicKey().getKeyID();
}
public String getKeyIdString() {
return keyIdToString(getPublicKey().getKeyID());
}
public String getFirstUserId() {
return (String) getPublicKey().getUserIDs().next();
}
public PGPPrivateKey getPrivateKey() throws PGPException {
return getSecretKey().extractPrivateKey(
new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
// All test keys have no passphrase.
.build(new char[0]));
}
private static ArmoredInputStream newStream(String armored)
throws IOException {
return new ArmoredInputStream(
new ByteArrayInputStream(Constants.encode(armored)));
}
}