diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index c94da7c40c..2a96fd0abb 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -2771,6 +2771,40 @@ behavior of Gerrit's 'receive-pack' mechanism. maxObjectSizeLimit = 40 m ---- +[[receive.enableSignedPush]]receive.enableSignedPush:: ++ +If true, server-side signed push validation is enabled. ++ +When a client pushes with `git push --signed`, this ensures that the +push certificate is valid and signed with a valid public key stored in +the `refs/gpg-keys` branch of `All-Users`. ++ +Defaults to false. + +[[receive.certNonceSeed]]receive.certNonceSeed:: ++ +If set to a non-empty value and server-side signed push validation is +link:#receive.enableSignedPush[enabled], use this value as the seed to +the HMAC SHA-1 nonce generator. If unset, a 64-byte random seed will be +generated at server startup. ++ +As this is used as the seed of a cryptographic algorithm, it is +recommended to be placed in link:#secure-config[`secure.config`]. ++ +Defaults to unset. + +[[receive.certNonceSlop]]receive.certNonceSlop:: ++ +When validating the nonce passed as part of the signed push protocol, +accept valid nonces up to this many seconds old. This allows +certificate verification to work over HTTP where there is a lag between +the HTTP response providing the nonce to sign and the next request +containing the signed nonce. This can be significant on large +repositories, since the lag also includes the time to count objects on +the client. ++ +Default is 5 minutes. + [[receive.checkMagicRefs]]receive.checkMagicRefs:: + If true, Gerrit will verify the destination repository has @@ -3640,7 +3674,7 @@ notifications if the full name of the user is not set. By default "Anonymous Coward" is used. -== File `etc/secure.config` +== [[secure.config]]File `etc/secure.config` The optional file `'$site_path'/etc/secure.config` overrides (or supplements) the settings supplied by `'$site_path'/etc/gerrit.config`. The file should be readable only by the daemon process and can be diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java index 050756ba60..e6a73942fb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java @@ -14,23 +14,35 @@ package com.google.gerrit.server.git; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.util.BouncyCastleUtil; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Singleton; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.transport.PreReceiveHookChain; import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.SignedPushConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + public class SignedPushModule extends AbstractModule { private static final Logger log = LoggerFactory.getLogger(SignedPushModule.class); + public static boolean isEnabled(Config cfg) { + return cfg.getBoolean("receive", null, "enableSignedPush", false); + } + @Override protected void configure() { if (BouncyCastleUtil.havePGP()) { @@ -44,17 +56,50 @@ public class SignedPushModule extends AbstractModule { @Singleton private static class Initializer implements ReceivePackInitializer { + private final SignedPushConfig signedPushConfig; private final SignedPushPreReceiveHook hook; @Inject - Initializer(SignedPushPreReceiveHook hook) { + Initializer(@GerritServerConfig Config cfg, + SignedPushPreReceiveHook hook) { this.hook = hook; + + if (isEnabled(cfg)) { + String seed = cfg.getString("receive", null, "certNonceSeed"); + if (Strings.isNullOrEmpty(seed)) { + seed = randomString(64); + } + signedPushConfig = new SignedPushConfig(); + signedPushConfig.setCertNonceSeed(seed); + signedPushConfig.setCertNonceSlopLimit( + cfg.getInt("receive", null, "certNonceSlop", 5 * 60)); + } else { + signedPushConfig = null; + } } @Override public void init(Project.NameKey project, ReceivePack rp) { - rp.setPreReceiveHook(PreReceiveHookChain.newChain(Lists.newArrayList( - hook, rp.getPreReceiveHook()))); + rp.setSignedPushConfig(signedPushConfig); + if (signedPushConfig != null) { + rp.setPreReceiveHook(PreReceiveHookChain.newChain(Lists.newArrayList( + hook, rp.getPreReceiveHook()))); + } } } + + private static String randomString(int len) { + Random random; + try { + random = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + sb.append((char) random.nextInt()); + } + return sb.toString(); + } + } diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java index dc43537a7f..807f78de6a 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java @@ -94,6 +94,8 @@ public class InMemoryModule extends FactoryModule { cfg.setInt("index", "lucene", "testVersion", ChangeSchemas.getLatest().getVersion()); cfg.setInt("sendemail", null, "threadPoolSize", 0); + cfg.setBoolean("receive", null, "enableSignedPush", false); + cfg.setString("receive", null, "certNonceSeed", "sekret"); } private final Config cfg;