PageRenderTime 26ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java

https://gitlab.com/chenfengxu/gerrit
Java | 439 lines | 333 code | 51 blank | 55 comment | 6 complexity | 693907024233b9a07d96e047bb56442d MD5 | raw file
  1. // Copyright (C) 2015 The Android Open Source Project
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package com.google.gerrit.gpg;
  15. import static com.google.common.truth.Truth.assertThat;
  16. import static com.google.gerrit.gpg.GerritPublicKeyChecker.toExtIdKey;
  17. import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
  18. import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithSecondUserId;
  19. import static com.google.gerrit.gpg.testing.TestTrustKeys.keyA;
  20. import static com.google.gerrit.gpg.testing.TestTrustKeys.keyB;
  21. import static com.google.gerrit.gpg.testing.TestTrustKeys.keyC;
  22. import static com.google.gerrit.gpg.testing.TestTrustKeys.keyD;
  23. import static com.google.gerrit.gpg.testing.TestTrustKeys.keyE;
  24. import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
  25. import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
  26. import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
  27. import com.google.common.collect.ImmutableList;
  28. import com.google.common.collect.Iterators;
  29. import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
  30. import com.google.gerrit.gpg.testing.TestKey;
  31. import com.google.gerrit.lifecycle.LifecycleManager;
  32. import com.google.gerrit.reviewdb.client.Account;
  33. import com.google.gerrit.reviewdb.server.ReviewDb;
  34. import com.google.gerrit.server.CurrentUser;
  35. import com.google.gerrit.server.IdentifiedUser;
  36. import com.google.gerrit.server.ServerInitiated;
  37. import com.google.gerrit.server.account.AccountManager;
  38. import com.google.gerrit.server.account.AccountsUpdate;
  39. import com.google.gerrit.server.account.AuthRequest;
  40. import com.google.gerrit.server.account.externalids.ExternalId;
  41. import com.google.gerrit.server.schema.SchemaCreator;
  42. import com.google.gerrit.server.util.RequestContext;
  43. import com.google.gerrit.server.util.ThreadLocalRequestContext;
  44. import com.google.gerrit.testing.InMemoryDatabase;
  45. import com.google.gerrit.testing.InMemoryModule;
  46. import com.google.gerrit.testing.NoteDbMode;
  47. import com.google.inject.Guice;
  48. import com.google.inject.Inject;
  49. import com.google.inject.Injector;
  50. import com.google.inject.Provider;
  51. import com.google.inject.util.Providers;
  52. import java.util.ArrayList;
  53. import java.util.Arrays;
  54. import java.util.List;
  55. import org.bouncycastle.openpgp.PGPPublicKey;
  56. import org.bouncycastle.openpgp.PGPPublicKeyRing;
  57. import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
  58. import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
  59. import org.eclipse.jgit.lib.CommitBuilder;
  60. import org.eclipse.jgit.lib.Config;
  61. import org.eclipse.jgit.lib.PersonIdent;
  62. import org.eclipse.jgit.lib.Repository;
  63. import org.eclipse.jgit.transport.PushCertificateIdent;
  64. import org.junit.After;
  65. import org.junit.Before;
  66. import org.junit.Test;
  67. /** Unit tests for {@link GerritPublicKeyChecker}. */
  68. public class GerritPublicKeyCheckerTest {
  69. @Inject @ServerInitiated private Provider<AccountsUpdate> accountsUpdateProvider;
  70. @Inject private AccountManager accountManager;
  71. @Inject private GerritPublicKeyChecker.Factory checkerFactory;
  72. @Inject private IdentifiedUser.GenericFactory userFactory;
  73. @Inject private InMemoryDatabase schemaFactory;
  74. @Inject private SchemaCreator schemaCreator;
  75. @Inject private ThreadLocalRequestContext requestContext;
  76. private LifecycleManager lifecycle;
  77. private ReviewDb db;
  78. private Account.Id userId;
  79. private IdentifiedUser user;
  80. private Repository storeRepo;
  81. private PublicKeyStore store;
  82. @Before
  83. public void setUpInjector() throws Exception {
  84. Config cfg = InMemoryModule.newDefaultConfig();
  85. cfg.setInt("receive", null, "maxTrustDepth", 2);
  86. cfg.setStringList(
  87. "receive",
  88. null,
  89. "trustedKey",
  90. ImmutableList.of(
  91. Fingerprint.toString(keyB().getPublicKey().getFingerprint()),
  92. Fingerprint.toString(keyD().getPublicKey().getFingerprint())));
  93. Injector injector =
  94. Guice.createInjector(new InMemoryModule(cfg, NoteDbMode.newNotesMigrationFromEnv()));
  95. lifecycle = new LifecycleManager();
  96. lifecycle.add(injector);
  97. injector.injectMembers(this);
  98. lifecycle.start();
  99. db = schemaFactory.open();
  100. schemaCreator.create(db);
  101. userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
  102. // Note: does not match any key in TestKeys.
  103. accountsUpdateProvider
  104. .get()
  105. .update("Set Preferred Email", userId, u -> u.setPreferredEmail("user@example.com"));
  106. user = reloadUser();
  107. requestContext.setContext(
  108. new RequestContext() {
  109. @Override
  110. public CurrentUser getUser() {
  111. return user;
  112. }
  113. @Override
  114. public Provider<ReviewDb> getReviewDbProvider() {
  115. return Providers.of(db);
  116. }
  117. });
  118. storeRepo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
  119. store = new PublicKeyStore(storeRepo);
  120. }
  121. @After
  122. public void tearDown() throws Exception {
  123. store.close();
  124. storeRepo.close();
  125. }
  126. private IdentifiedUser addUser(String name) throws Exception {
  127. AuthRequest req = AuthRequest.forUser(name);
  128. Account.Id id = accountManager.authenticate(req).getAccountId();
  129. return userFactory.create(id);
  130. }
  131. private IdentifiedUser reloadUser() {
  132. user = userFactory.create(userId);
  133. return user;
  134. }
  135. @After
  136. public void tearDownInjector() {
  137. if (lifecycle != null) {
  138. lifecycle.stop();
  139. }
  140. if (db != null) {
  141. db.close();
  142. }
  143. InMemoryDatabase.drop(schemaFactory);
  144. }
  145. @Test
  146. public void defaultGpgCertificationMatchesEmail() throws Exception {
  147. TestKey key = validKeyWithSecondUserId();
  148. PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
  149. assertProblems(
  150. checker.check(key.getPublicKey()),
  151. Status.BAD,
  152. "Key must contain a valid certification for one of the following "
  153. + "identities:\n"
  154. + " gerrit:user\n"
  155. + " username:user");
  156. addExternalId("test", "test", "test5@example.com");
  157. checker = checkerFactory.create(user, store).disableTrust();
  158. assertNoProblems(checker.check(key.getPublicKey()));
  159. }
  160. @Test
  161. public void defaultGpgCertificationDoesNotMatchEmail() throws Exception {
  162. addExternalId("test", "test", "nobody@example.com");
  163. PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
  164. assertProblems(
  165. checker.check(validKeyWithSecondUserId().getPublicKey()),
  166. Status.BAD,
  167. "Key must contain a valid certification for one of the following "
  168. + "identities:\n"
  169. + " gerrit:user\n"
  170. + " nobody@example.com\n"
  171. + " test:test\n"
  172. + " username:user");
  173. }
  174. @Test
  175. public void manualCertificationMatchesExternalId() throws Exception {
  176. addExternalId("foo", "myId", null);
  177. PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
  178. assertNoProblems(checker.check(validKeyWithSecondUserId().getPublicKey()));
  179. }
  180. @Test
  181. public void manualCertificationDoesNotMatchExternalId() throws Exception {
  182. addExternalId("foo", "otherId", null);
  183. PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
  184. assertProblems(
  185. checker.check(validKeyWithSecondUserId().getPublicKey()),
  186. Status.BAD,
  187. "Key must contain a valid certification for one of the following "
  188. + "identities:\n"
  189. + " foo:otherId\n"
  190. + " gerrit:user\n"
  191. + " username:user");
  192. }
  193. @Test
  194. public void noExternalIds() throws Exception {
  195. accountsUpdateProvider
  196. .get()
  197. .update(
  198. "Delete External IDs",
  199. user.getAccountId(),
  200. (a, u) -> u.deleteExternalIds(a.getExternalIds()));
  201. reloadUser();
  202. TestKey key = validKeyWithSecondUserId();
  203. PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
  204. assertProblems(
  205. checker.check(key.getPublicKey()),
  206. Status.BAD,
  207. "No identities found for user; check http://test/#/settings/web-identities");
  208. checker = checkerFactory.create().setStore(store).disableTrust();
  209. assertProblems(
  210. checker.check(key.getPublicKey()), Status.BAD, "Key is not associated with any users");
  211. insertExtId(ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
  212. assertProblems(checker.check(key.getPublicKey()), Status.BAD, "No identities found for user");
  213. }
  214. @Test
  215. public void checkValidTrustChainAndCorrectExternalIds() throws Exception {
  216. // A---Bx
  217. // \
  218. // \---C---D
  219. // \
  220. // \---Ex
  221. //
  222. // The server ultimately trusts B and D.
  223. // D and E trust C to be a valid introducer of depth 2.
  224. IdentifiedUser userB = addUser("userB");
  225. TestKey keyA = add(keyA(), user);
  226. TestKey keyB = add(keyB(), userB);
  227. add(keyC(), addUser("userC"));
  228. add(keyD(), addUser("userD"));
  229. add(keyE(), addUser("userE"));
  230. // Checker for A, checking A.
  231. PublicKeyChecker checkerA = checkerFactory.create(user, store);
  232. assertNoProblems(checkerA.check(keyA.getPublicKey()));
  233. // Checker for B, checking B. Trust chain and IDs are correct, so the only
  234. // problem is with the key itself.
  235. PublicKeyChecker checkerB = checkerFactory.create(userB, store);
  236. assertProblems(checkerB.check(keyB.getPublicKey()), Status.BAD, "Key is expired");
  237. }
  238. @Test
  239. public void checkWithValidKeyButWrongExpectedUserInChecker() throws Exception {
  240. // A---Bx
  241. // \
  242. // \---C---D
  243. // \
  244. // \---Ex
  245. //
  246. // The server ultimately trusts B and D.
  247. // D and E trust C to be a valid introducer of depth 2.
  248. IdentifiedUser userB = addUser("userB");
  249. TestKey keyA = add(keyA(), user);
  250. TestKey keyB = add(keyB(), userB);
  251. add(keyC(), addUser("userC"));
  252. add(keyD(), addUser("userD"));
  253. add(keyE(), addUser("userE"));
  254. // Checker for A, checking B.
  255. PublicKeyChecker checkerA = checkerFactory.create(user, store);
  256. assertProblems(
  257. checkerA.check(keyB.getPublicKey()),
  258. Status.BAD,
  259. "Key is expired",
  260. "Key must contain a valid certification for one of the following"
  261. + " identities:\n"
  262. + " gerrit:user\n"
  263. + " mailto:testa@example.com\n"
  264. + " testa@example.com\n"
  265. + " username:user");
  266. // Checker for B, checking A.
  267. PublicKeyChecker checkerB = checkerFactory.create(userB, store);
  268. assertProblems(
  269. checkerB.check(keyA.getPublicKey()),
  270. Status.BAD,
  271. "Key must contain a valid certification for one of the following"
  272. + " identities:\n"
  273. + " gerrit:userB\n"
  274. + " mailto:testb@example.com\n"
  275. + " testb@example.com\n"
  276. + " username:userB");
  277. }
  278. @Test
  279. public void checkTrustChainWithExpiredKey() throws Exception {
  280. // A---Bx
  281. //
  282. // The server ultimately trusts B.
  283. TestKey keyA = add(keyA(), user);
  284. TestKey keyB = add(keyB(), addUser("userB"));
  285. PublicKeyChecker checker = checkerFactory.create(user, store);
  286. assertProblems(
  287. checker.check(keyA.getPublicKey()),
  288. Status.OK,
  289. "No path to a trusted key",
  290. "Certification by "
  291. + keyToString(keyB.getPublicKey())
  292. + " is valid, but key is not trusted",
  293. "Key D24FE467 used for certification is not in store");
  294. }
  295. @Test
  296. public void checkTrustChainUsingCheckerWithoutExpectedKey() throws Exception {
  297. // A---Bx
  298. // \
  299. // \---C---D
  300. // \
  301. // \---Ex
  302. //
  303. // The server ultimately trusts B and D.
  304. // D and E trust C to be a valid introducer of depth 2.
  305. TestKey keyA = add(keyA(), user);
  306. TestKey keyB = add(keyB(), addUser("userB"));
  307. TestKey keyC = add(keyC(), addUser("userC"));
  308. TestKey keyD = add(keyD(), addUser("userD"));
  309. TestKey keyE = add(keyE(), addUser("userE"));
  310. // This checker can check any key, so the only problems come from issues
  311. // with the keys themselves, not having invalid user IDs.
  312. PublicKeyChecker checker = checkerFactory.create().setStore(store);
  313. assertNoProblems(checker.check(keyA.getPublicKey()));
  314. assertProblems(checker.check(keyB.getPublicKey()), Status.BAD, "Key is expired");
  315. assertNoProblems(checker.check(keyC.getPublicKey()));
  316. assertNoProblems(checker.check(keyD.getPublicKey()));
  317. assertProblems(
  318. checker.check(keyE.getPublicKey()),
  319. Status.BAD,
  320. "Key is expired",
  321. "No path to a trusted key");
  322. }
  323. @Test
  324. public void keyLaterInTrustChainMissingUserId() throws Exception {
  325. // A---Bx
  326. // \
  327. // \---C
  328. //
  329. // The server ultimately trusts B.
  330. // C signed A's key but is not in the store.
  331. TestKey keyA = add(keyA(), user);
  332. PGPPublicKeyRing keyRingB = keyB().getPublicKeyRing();
  333. PGPPublicKey keyB = keyRingB.getPublicKey();
  334. keyB = PGPPublicKey.removeCertification(keyB, keyB.getUserIDs().next());
  335. keyRingB = PGPPublicKeyRing.insertPublicKey(keyRingB, keyB);
  336. add(keyRingB, addUser("userB"));
  337. PublicKeyChecker checkerA = checkerFactory.create(user, store);
  338. assertProblems(
  339. checkerA.check(keyA.getPublicKey()),
  340. Status.OK,
  341. "No path to a trusted key",
  342. "Certification by " + keyToString(keyB) + " is valid, but key is not trusted",
  343. "Key D24FE467 used for certification is not in store");
  344. }
  345. private void add(PGPPublicKeyRing kr, IdentifiedUser user) throws Exception {
  346. Account.Id id = user.getAccountId();
  347. List<ExternalId> newExtIds = new ArrayList<>(2);
  348. newExtIds.add(ExternalId.create(toExtIdKey(kr.getPublicKey()), id));
  349. String userId = Iterators.getOnlyElement(kr.getPublicKey().getUserIDs(), null);
  350. if (userId != null) {
  351. String email = PushCertificateIdent.parse(userId).getEmailAddress();
  352. assertThat(email).contains("@");
  353. newExtIds.add(ExternalId.createEmail(id, email));
  354. }
  355. store.add(kr);
  356. PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
  357. CommitBuilder cb = new CommitBuilder();
  358. cb.setAuthor(ident);
  359. cb.setCommitter(ident);
  360. assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
  361. accountsUpdateProvider.get().update("Add External IDs", id, u -> u.addExternalIds(newExtIds));
  362. }
  363. private TestKey add(TestKey k, IdentifiedUser user) throws Exception {
  364. add(k.getPublicKeyRing(), user);
  365. return k;
  366. }
  367. private void assertProblems(
  368. CheckResult result, Status expectedStatus, String first, String... rest) throws Exception {
  369. List<String> expectedProblems = new ArrayList<>();
  370. expectedProblems.add(first);
  371. expectedProblems.addAll(Arrays.asList(rest));
  372. assertThat(result.getStatus()).isEqualTo(expectedStatus);
  373. assertThat(result.getProblems()).containsExactlyElementsIn(expectedProblems).inOrder();
  374. }
  375. private void assertNoProblems(CheckResult result) {
  376. assertThat(result.getStatus()).isEqualTo(Status.TRUSTED);
  377. assertThat(result.getProblems()).isEmpty();
  378. }
  379. private void addExternalId(String scheme, String id, String email) throws Exception {
  380. insertExtId(ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
  381. }
  382. private void insertExtId(ExternalId extId) throws Exception {
  383. accountsUpdateProvider
  384. .get()
  385. .update("Add External ID", extId.accountId(), u -> u.addExternalId(extId));
  386. reloadUser();
  387. }
  388. }