Line data Source code
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 : 15 : package com.google.gerrit.gpg; 16 : 17 : import static com.google.gerrit.server.project.ProjectCache.illegalState; 18 : 19 : import com.google.common.base.Strings; 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.entities.BooleanProjectConfig; 22 : import com.google.gerrit.entities.Project; 23 : import com.google.gerrit.extensions.registration.DynamicSet; 24 : import com.google.gerrit.server.EnableSignedPush; 25 : import com.google.gerrit.server.config.AllUsersName; 26 : import com.google.gerrit.server.config.GerritServerConfig; 27 : import com.google.gerrit.server.git.GitRepositoryManager; 28 : import com.google.gerrit.server.git.ReceivePackInitializer; 29 : import com.google.gerrit.server.project.ProjectCache; 30 : import com.google.gerrit.server.project.ProjectState; 31 : import com.google.inject.AbstractModule; 32 : import com.google.inject.Inject; 33 : import com.google.inject.Provider; 34 : import com.google.inject.ProvisionException; 35 : import com.google.inject.Singleton; 36 : import java.io.IOException; 37 : import java.security.NoSuchAlgorithmException; 38 : import java.security.SecureRandom; 39 : import java.util.ArrayList; 40 : import java.util.List; 41 : import java.util.Random; 42 : import org.eclipse.jgit.lib.Config; 43 : import org.eclipse.jgit.lib.Repository; 44 : import org.eclipse.jgit.transport.PreReceiveHook; 45 : import org.eclipse.jgit.transport.PreReceiveHookChain; 46 : import org.eclipse.jgit.transport.ReceivePack; 47 : import org.eclipse.jgit.transport.SignedPushConfig; 48 : 49 7 : class SignedPushModule extends AbstractModule { 50 7 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 51 : 52 : @Override 53 : protected void configure() { 54 7 : if (!BouncyCastleUtil.havePGP()) { 55 0 : throw new ProvisionException("Bouncy Castle PGP not installed"); 56 : } 57 7 : bind(PublicKeyStore.class).toProvider(StoreProvider.class); 58 7 : DynamicSet.bind(binder(), ReceivePackInitializer.class).to(Initializer.class); 59 7 : } 60 : 61 : @Singleton 62 : private static class Initializer implements ReceivePackInitializer { 63 : private final SignedPushConfig signedPushConfig; 64 : private final SignedPushPreReceiveHook hook; 65 : private final ProjectCache projectCache; 66 : 67 : @Inject 68 : Initializer( 69 : @GerritServerConfig Config cfg, 70 : @EnableSignedPush boolean enableSignedPush, 71 : SignedPushPreReceiveHook hook, 72 7 : ProjectCache projectCache) { 73 7 : this.hook = hook; 74 7 : this.projectCache = projectCache; 75 : 76 7 : if (enableSignedPush) { 77 7 : String seed = cfg.getString("receive", null, "certNonceSeed"); 78 7 : if (Strings.isNullOrEmpty(seed)) { 79 7 : seed = randomString(64); 80 : } 81 7 : signedPushConfig = new SignedPushConfig(); 82 7 : signedPushConfig.setCertNonceSeed(seed); 83 7 : signedPushConfig.setCertNonceSlopLimit( 84 7 : cfg.getInt("receive", null, "certNonceSlop", 5 * 60)); 85 7 : } else { 86 0 : signedPushConfig = null; 87 : } 88 7 : } 89 : 90 : @Override 91 : public void init(Project.NameKey project, ReceivePack rp) { 92 6 : ProjectState ps = projectCache.get(project).orElseThrow(illegalState(project)); 93 6 : if (!ps.is(BooleanProjectConfig.ENABLE_SIGNED_PUSH)) { 94 3 : rp.setSignedPushConfig(null); 95 3 : return; 96 3 : } else if (signedPushConfig == null) { 97 0 : logger.atSevere().log( 98 : "receive.enableSignedPush is true for project %s but" 99 : + " false in gerrit.config, so signed push verification is" 100 : + " disabled", 101 0 : project.get()); 102 0 : rp.setSignedPushConfig(null); 103 0 : return; 104 : } 105 3 : rp.setSignedPushConfig(signedPushConfig); 106 : 107 3 : List<PreReceiveHook> hooks = new ArrayList<>(3); 108 3 : if (ps.is(BooleanProjectConfig.REQUIRE_SIGNED_PUSH)) { 109 3 : hooks.add(SignedPushPreReceiveHook.Required.INSTANCE); 110 : } 111 3 : hooks.add(hook); 112 3 : hooks.add(rp.getPreReceiveHook()); 113 3 : rp.setPreReceiveHook(PreReceiveHookChain.newChain(hooks)); 114 3 : } 115 : } 116 : 117 : @Singleton 118 : private static class StoreProvider implements Provider<PublicKeyStore> { 119 : private final GitRepositoryManager repoManager; 120 : private final AllUsersName allUsers; 121 : 122 : @Inject 123 2 : StoreProvider(GitRepositoryManager repoManager, AllUsersName allUsers) { 124 2 : this.repoManager = repoManager; 125 2 : this.allUsers = allUsers; 126 2 : } 127 : 128 : @Override 129 : public PublicKeyStore get() { 130 : final Repository repo; 131 : try { 132 2 : repo = repoManager.openRepository(allUsers); 133 0 : } catch (IOException e) { 134 0 : throw new ProvisionException("Cannot open " + allUsers, e); 135 2 : } 136 2 : return new PublicKeyStore(repo) { 137 : @Override 138 : public void close() { 139 : try { 140 2 : super.close(); 141 : } finally { 142 2 : repo.close(); 143 : } 144 2 : } 145 : }; 146 : } 147 : } 148 : 149 : private static String randomString(int len) { 150 : Random random; 151 : try { 152 7 : random = SecureRandom.getInstance("SHA1PRNG"); 153 0 : } catch (NoSuchAlgorithmException e) { 154 0 : throw new IllegalStateException(e); 155 7 : } 156 7 : StringBuilder sb = new StringBuilder(len); 157 7 : for (int i = 0; i < len; i++) { 158 7 : sb.append((char) random.nextInt()); 159 : } 160 7 : return sb.toString(); 161 : } 162 : }