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