Line data Source code
1 : // Copyright (C) 2008 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.sshd;
16 :
17 : import static com.google.gerrit.server.ssh.SshAddressesModule.IANA_SSH_PORT;
18 : import static java.util.concurrent.TimeUnit.MILLISECONDS;
19 : import static java.util.concurrent.TimeUnit.SECONDS;
20 : import static org.apache.sshd.core.CoreModuleProperties.AUTH_TIMEOUT;
21 : import static org.apache.sshd.core.CoreModuleProperties.IDLE_TIMEOUT;
22 : import static org.apache.sshd.core.CoreModuleProperties.MAX_AUTH_REQUESTS;
23 : import static org.apache.sshd.core.CoreModuleProperties.MAX_CONCURRENT_SESSIONS;
24 : import static org.apache.sshd.core.CoreModuleProperties.NIO2_READ_TIMEOUT;
25 : import static org.apache.sshd.core.CoreModuleProperties.REKEY_BYTES_LIMIT;
26 : import static org.apache.sshd.core.CoreModuleProperties.REKEY_TIME_LIMIT;
27 : import static org.apache.sshd.core.CoreModuleProperties.SERVER_IDENTIFICATION;
28 : import static org.apache.sshd.core.CoreModuleProperties.WAIT_FOR_SPACE_TIMEOUT;
29 :
30 : import com.google.common.base.Strings;
31 : import com.google.common.collect.Iterables;
32 : import com.google.common.flogger.FluentLogger;
33 : import com.google.gerrit.common.Version;
34 : import com.google.gerrit.extensions.events.LifecycleListener;
35 : import com.google.gerrit.metrics.Counter0;
36 : import com.google.gerrit.metrics.Description;
37 : import com.google.gerrit.metrics.MetricMaker;
38 : import com.google.gerrit.server.config.ConfigUtil;
39 : import com.google.gerrit.server.config.GerritServerConfig;
40 : import com.google.gerrit.server.ssh.HostKey;
41 : import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
42 : import com.google.gerrit.server.ssh.SshInfo;
43 : import com.google.gerrit.server.ssh.SshListenAddresses;
44 : import com.google.gerrit.server.util.IdGenerator;
45 : import com.google.gerrit.server.util.SocketUtil;
46 : import com.google.inject.Inject;
47 : import com.google.inject.Singleton;
48 : import java.io.File;
49 : import java.io.IOException;
50 : import java.net.InetAddress;
51 : import java.net.InetSocketAddress;
52 : import java.net.SocketAddress;
53 : import java.net.UnknownHostException;
54 : import java.security.GeneralSecurityException;
55 : import java.security.InvalidKeyException;
56 : import java.security.KeyPair;
57 : import java.security.PublicKey;
58 : import java.time.Duration;
59 : import java.util.ArrayList;
60 : import java.util.Arrays;
61 : import java.util.Collection;
62 : import java.util.Collections;
63 : import java.util.Iterator;
64 : import java.util.List;
65 : import java.util.concurrent.CountDownLatch;
66 : import java.util.concurrent.ExecutorService;
67 : import java.util.concurrent.TimeUnit;
68 : import java.util.concurrent.atomic.AtomicInteger;
69 : import org.apache.mina.transport.socket.SocketSessionConfig;
70 : import org.apache.sshd.common.BaseBuilder;
71 : import org.apache.sshd.common.NamedFactory;
72 : import org.apache.sshd.common.NamedResource;
73 : import org.apache.sshd.common.cipher.Cipher;
74 : import org.apache.sshd.common.compression.BuiltinCompressions;
75 : import org.apache.sshd.common.compression.Compression;
76 : import org.apache.sshd.common.file.nonefs.NoneFileSystemFactory;
77 : import org.apache.sshd.common.forward.DefaultForwarderFactory;
78 : import org.apache.sshd.common.future.CloseFuture;
79 : import org.apache.sshd.common.future.SshFutureListener;
80 : import org.apache.sshd.common.io.AbstractIoServiceFactory;
81 : import org.apache.sshd.common.io.IoAcceptor;
82 : import org.apache.sshd.common.io.IoServiceFactory;
83 : import org.apache.sshd.common.io.IoServiceFactoryFactory;
84 : import org.apache.sshd.common.io.IoSession;
85 : import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
86 : import org.apache.sshd.common.kex.BuiltinDHFactories;
87 : import org.apache.sshd.common.kex.KeyExchangeFactory;
88 : import org.apache.sshd.common.kex.extension.DefaultServerKexExtensionHandler;
89 : import org.apache.sshd.common.keyprovider.KeyPairProvider;
90 : import org.apache.sshd.common.mac.Mac;
91 : import org.apache.sshd.common.random.Random;
92 : import org.apache.sshd.common.random.SingletonRandomFactory;
93 : import org.apache.sshd.common.session.Session;
94 : import org.apache.sshd.common.session.helpers.AbstractSession;
95 : import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
96 : import org.apache.sshd.common.util.buffer.Buffer;
97 : import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
98 : import org.apache.sshd.common.util.net.SshdSocketAddress;
99 : import org.apache.sshd.common.util.security.SecurityUtils;
100 : import org.apache.sshd.mina.MinaServiceFactoryFactory;
101 : import org.apache.sshd.mina.MinaSession;
102 : import org.apache.sshd.server.ServerBuilder;
103 : import org.apache.sshd.server.SshServer;
104 : import org.apache.sshd.server.auth.UserAuthFactory;
105 : import org.apache.sshd.server.auth.gss.GSSAuthenticator;
106 : import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
107 : import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
108 : import org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
109 : import org.apache.sshd.server.command.CommandFactory;
110 : import org.apache.sshd.server.forward.ForwardingFilter;
111 : import org.apache.sshd.server.global.CancelTcpipForwardHandler;
112 : import org.apache.sshd.server.global.KeepAliveHandler;
113 : import org.apache.sshd.server.global.NoMoreSessionsHandler;
114 : import org.apache.sshd.server.global.TcpipForwardHandler;
115 : import org.apache.sshd.server.session.ServerSessionImpl;
116 : import org.apache.sshd.server.session.SessionFactory;
117 : import org.bouncycastle.crypto.prng.RandomGenerator;
118 : import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
119 : import org.eclipse.jgit.lib.Config;
120 :
121 : /**
122 : * SSH daemon to communicate with Gerrit.
123 : *
124 : * <p>Use a Git URL such as <code>ssh://${email}@${host}:${port}/${path}</code>, e.g. {@code
125 : * ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git} to access the SSH daemon itself.
126 : *
127 : * <p>Versions of Git before 1.5.3 may require setting the username and port properties in the
128 : * user's {@code ~/.ssh/config} file, and using a host alias through a URL such as {@code
129 : * gerrit-alias:/tools/gerrit.git}:
130 : *
131 : * <pre>{@code
132 : * Host gerrit-alias
133 : * User sop@google.com
134 : * Hostname gerrit.com
135 : * Port 8010
136 : * }</pre>
137 : */
138 : @Singleton
139 : public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
140 17 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
141 :
142 17 : public enum SshSessionBackend {
143 17 : MINA,
144 17 : NIO2
145 : }
146 :
147 : private final List<SocketAddress> listen;
148 : private final List<String> advertised;
149 : private final boolean keepAlive;
150 : private final List<HostKey> hostKeys;
151 : private volatile IoAcceptor daemonAcceptor;
152 : private final Config cfg;
153 : private final long gracefulStopTimeout;
154 :
155 : @Inject
156 : SshDaemon(
157 : CommandFactory commandFactory,
158 : NoShell noShell,
159 : PublickeyAuthenticator userAuth,
160 : GerritGSSAuthenticator kerberosAuth,
161 : KeyPairProvider hostKeyProvider,
162 : IdGenerator idGenerator,
163 : @GerritServerConfig Config cfg,
164 : SshLog sshLog,
165 : @SshListenAddresses List<SocketAddress> listen,
166 : @SshAdvertisedAddresses List<String> advertised,
167 : MetricMaker metricMaker,
168 17 : LogMaxConnectionsPerUserExceeded logMaxConnectionsPerUserExceeded) {
169 17 : setPort(IANA_SSH_PORT /* never used */);
170 :
171 17 : this.cfg = cfg;
172 17 : this.listen = listen;
173 17 : this.advertised = advertised;
174 17 : keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
175 :
176 17 : SERVER_IDENTIFICATION.set(
177 : this,
178 : "GerritCodeReview_"
179 17 : + Version.getVersion() //
180 : + " ("
181 17 : + super.getVersion()
182 : + ")");
183 17 : MAX_AUTH_REQUESTS.set(this, cfg.getInt("sshd", "maxAuthTries", 6));
184 17 : AUTH_TIMEOUT.set(
185 : this,
186 17 : Duration.ofSeconds(
187 17 : MILLISECONDS.convert(
188 17 : ConfigUtil.getTimeUnit(cfg, "sshd", null, "loginGraceTime", 120, SECONDS),
189 : SECONDS)));
190 :
191 17 : long idleTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null, "idleTimeout", 0, SECONDS);
192 17 : IDLE_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(idleTimeoutSeconds)));
193 17 : NIO2_READ_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(idleTimeoutSeconds)));
194 :
195 17 : long rekeyTimeLimit =
196 17 : ConfigUtil.getTimeUnit(cfg, "sshd", null, "rekeyTimeLimit", 3600, SECONDS);
197 17 : REKEY_TIME_LIMIT.set(this, Duration.ofSeconds(SECONDS.toMillis(rekeyTimeLimit)));
198 :
199 17 : REKEY_BYTES_LIMIT.set(
200 17 : this, cfg.getLong("sshd", "rekeyBytesLimit", 1024 * 1024 * 1024 /* 1GB */));
201 :
202 17 : long waitTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null, "waitTimeout", 30, SECONDS);
203 17 : WAIT_FOR_SPACE_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(waitTimeoutSeconds)));
204 :
205 17 : final int maxConnectionsPerUser = cfg.getInt("sshd", "maxConnectionsPerUser", 64);
206 17 : if (0 < maxConnectionsPerUser) {
207 17 : MAX_CONCURRENT_SESSIONS.set(this, maxConnectionsPerUser);
208 : }
209 :
210 17 : final String kerberosKeytab = cfg.getString("sshd", null, "kerberosKeytab");
211 17 : final String kerberosPrincipal = cfg.getString("sshd", null, "kerberosPrincipal");
212 :
213 17 : final boolean enableCompression = cfg.getBoolean("sshd", "enableCompression", false);
214 :
215 17 : SshSessionBackend backend = cfg.getEnum("sshd", null, "backend", SshSessionBackend.NIO2);
216 17 : boolean channelIdTracking = cfg.getBoolean("sshd", "enableChannelIdTracking", true);
217 :
218 17 : gracefulStopTimeout = cfg.getTimeUnit("sshd", null, "gracefulStopTimeout", 0, TimeUnit.SECONDS);
219 :
220 17 : System.setProperty(
221 17 : IoServiceFactoryFactory.class.getName(),
222 17 : backend == SshSessionBackend.MINA
223 0 : ? MinaServiceFactoryFactory.class.getName()
224 17 : : Nio2ServiceFactoryFactory.class.getName());
225 :
226 17 : initProviderBouncyCastle(cfg);
227 17 : initCiphers(cfg);
228 17 : initKeyExchanges(cfg);
229 17 : initMacs(cfg);
230 17 : initSignatures();
231 17 : initChannels();
232 17 : initUnknownChannelReferenceHandler(channelIdTracking);
233 17 : initForwarding();
234 17 : initFileSystemFactory();
235 17 : initSubsystems();
236 17 : initCompression(enableCompression);
237 17 : initUserAuth(userAuth, kerberosAuth, kerberosKeytab, kerberosPrincipal);
238 17 : setKeyPairProvider(hostKeyProvider);
239 17 : setCommandFactory(commandFactory);
240 17 : setShellFactory(noShell);
241 17 : setSessionDisconnectHandler(logMaxConnectionsPerUserExceeded);
242 :
243 17 : final AtomicInteger connected = new AtomicInteger();
244 17 : metricMaker.newCallbackMetric(
245 : "sshd/sessions/connected",
246 : Integer.class,
247 17 : new Description("Currently connected SSH sessions").setGauge().setUnit("sessions"),
248 17 : connected::get);
249 :
250 17 : final Counter0 sessionsCreated =
251 17 : metricMaker.newCounter(
252 : "sshd/sessions/created",
253 17 : new Description("Rate of new SSH sessions").setRate().setUnit("sessions"));
254 :
255 17 : final Counter0 authFailures =
256 17 : metricMaker.newCounter(
257 : "sshd/sessions/authentication_failures",
258 17 : new Description("Rate of SSH authentication failures").setRate().setUnit("failures"));
259 :
260 17 : setSessionFactory(
261 17 : new SessionFactory(this) {
262 : @Override
263 : protected ServerSessionImpl createSession(IoSession io) throws Exception {
264 17 : connected.incrementAndGet();
265 17 : sessionsCreated.increment();
266 17 : if (io instanceof MinaSession) {
267 0 : if (((MinaSession) io).getSession().getConfig() instanceof SocketSessionConfig) {
268 0 : ((SocketSessionConfig) ((MinaSession) io).getSession().getConfig())
269 0 : .setKeepAlive(keepAlive);
270 : }
271 : }
272 :
273 17 : ServerSessionImpl s = super.createSession(io);
274 17 : int id = idGenerator.next();
275 17 : SocketAddress peer = io.getRemoteAddress();
276 17 : final SshSession sd = new SshSession(id, peer);
277 17 : s.setAttribute(SshSession.KEY, sd);
278 :
279 : // Log a session close without authentication as a failure.
280 : //
281 17 : s.addCloseFutureListener(
282 : future -> {
283 17 : connected.decrementAndGet();
284 17 : if (sd.isAuthenticationError()) {
285 1 : authFailures.increment();
286 1 : sshLog.onAuthFail(sd);
287 : }
288 17 : });
289 17 : return s;
290 : }
291 :
292 : @Override
293 : protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception {
294 17 : return new ServerSessionImpl(getServer(), ioSession);
295 : }
296 : });
297 17 : setGlobalRequestHandlers(
298 17 : Arrays.asList(
299 : new KeepAliveHandler(),
300 : new NoMoreSessionsHandler(),
301 : new TcpipForwardHandler(),
302 : new CancelTcpipForwardHandler()));
303 :
304 17 : hostKeys = computeHostKeys();
305 17 : }
306 :
307 : @Override
308 : public List<HostKey> getHostKeys() {
309 12 : return hostKeys;
310 : }
311 :
312 : public IoAcceptor getIoAcceptor() {
313 1 : return daemonAcceptor;
314 : }
315 :
316 : @Override
317 : public synchronized void start() {
318 17 : if (daemonAcceptor == null && !listen.isEmpty()) {
319 17 : checkConfig();
320 17 : if (getSessionFactory() == null) {
321 0 : setSessionFactory(createSessionFactory());
322 : }
323 17 : setupSessionTimeout(getSessionFactory());
324 17 : daemonAcceptor = createAcceptor();
325 :
326 : try {
327 17 : String listenAddress = cfg.getString("sshd", null, "listenAddress");
328 17 : boolean rewrite = !Strings.isNullOrEmpty(listenAddress) && listenAddress.endsWith(":0");
329 17 : daemonAcceptor.bind(listen);
330 17 : if (rewrite) {
331 17 : SocketAddress bound = Iterables.getOnlyElement(daemonAcceptor.getBoundAddresses());
332 17 : cfg.setString("sshd", null, "listenAddress", format((InetSocketAddress) bound));
333 : }
334 0 : } catch (IOException e) {
335 0 : throw new IllegalStateException("Cannot bind to " + addressList(), e);
336 17 : }
337 :
338 17 : logger.atInfo().log("Started Gerrit %s on %s", getVersion(), addressList());
339 : }
340 17 : }
341 :
342 : private static String format(InetSocketAddress s) {
343 17 : return String.format("%s:%d", s.getAddress().getHostAddress(), s.getPort());
344 : }
345 :
346 : @Override
347 : public synchronized void stop() {
348 17 : if (daemonAcceptor != null) {
349 : try {
350 17 : if (gracefulStopTimeout > 0) {
351 1 : logger.atInfo().log(
352 : "Stopping SSHD sessions gracefully with %d seconds timeout.", gracefulStopTimeout);
353 1 : daemonAcceptor.unbind(daemonAcceptor.getBoundAddresses());
354 1 : waitForSessionClose();
355 : }
356 17 : daemonAcceptor.close(true).await();
357 17 : shutdownExecutors();
358 17 : logger.atInfo().log("Stopped Gerrit SSHD");
359 0 : } catch (IOException e) {
360 0 : logger.atWarning().withCause(e).log("Exception caught while closing");
361 : } finally {
362 17 : daemonAcceptor = null;
363 : }
364 : }
365 17 : }
366 :
367 : private void waitForSessionClose() {
368 1 : Collection<IoSession> ioSessions = daemonAcceptor.getManagedSessions().values();
369 1 : CountDownLatch allSessionsClosed = new CountDownLatch(ioSessions.size());
370 1 : for (IoSession io : ioSessions) {
371 1 : AbstractSession serverSession = AbstractSession.getSession(io, true);
372 : SshSession sshSession =
373 1 : serverSession != null ? serverSession.getAttribute(SshSession.KEY) : null;
374 1 : if (sshSession != null && sshSession.requiresGracefulStop()) {
375 1 : logger.atFine().log("Waiting for session %s to stop.", io.getId());
376 1 : io.addCloseFutureListener(
377 1 : new SshFutureListener<CloseFuture>() {
378 : @Override
379 : public void operationComplete(CloseFuture future) {
380 1 : logger.atFine().log("Session %s was stopped.", io.getId());
381 1 : allSessionsClosed.countDown();
382 1 : }
383 : });
384 : } else {
385 1 : logger.atFine().log("Stopping session %s immediately.", io.getId());
386 1 : io.close(true);
387 1 : allSessionsClosed.countDown();
388 : }
389 1 : }
390 : try {
391 1 : if (!allSessionsClosed.await(gracefulStopTimeout, TimeUnit.SECONDS)) {
392 1 : logger.atWarning().log(
393 : "Timeout waiting for SSH session to close. SSHD will be shut down immediately.");
394 : }
395 0 : } catch (InterruptedException e) {
396 0 : logger.atWarning().withCause(e).log(
397 : "Interrupted waiting for SSH-sessions to close. SSHD will be shut down immediately.");
398 1 : }
399 1 : }
400 :
401 : private void shutdownExecutors() {
402 17 : if (executor != null) {
403 17 : executor.shutdownNow();
404 : }
405 :
406 17 : IoServiceFactory serviceFactory = getIoServiceFactory();
407 17 : if (serviceFactory instanceof AbstractIoServiceFactory) {
408 17 : shutdownServiceFactoryExecutor((AbstractIoServiceFactory) serviceFactory);
409 : }
410 17 : }
411 :
412 : private void shutdownServiceFactoryExecutor(AbstractIoServiceFactory ioServiceFactory) {
413 17 : ioServiceFactory.close(true);
414 17 : ExecutorService serviceFactoryExecutor = ioServiceFactory.getExecutorService();
415 17 : if (serviceFactoryExecutor != null && serviceFactoryExecutor != executor) {
416 17 : serviceFactoryExecutor.shutdownNow();
417 : }
418 17 : }
419 :
420 : @Override
421 : protected void checkConfig() {
422 17 : super.checkConfig();
423 17 : if (myHostKeys().isEmpty()) {
424 0 : throw new IllegalStateException("No SSHD host key");
425 : }
426 17 : }
427 :
428 : private List<HostKey> computeHostKeys() {
429 17 : if (listen.isEmpty()) {
430 0 : return Collections.emptyList();
431 : }
432 :
433 17 : List<HostKey> r = new ArrayList<>();
434 17 : List<PublicKey> keys = myHostKeys();
435 17 : for (PublicKey pub : keys) {
436 17 : Buffer buf = new ByteArrayBuffer();
437 17 : buf.putRawPublicKey(pub);
438 17 : byte[] keyBin = buf.getCompactData();
439 :
440 17 : for (String addr : advertised) {
441 17 : r.add(new HostKey(addr, keyBin));
442 17 : }
443 17 : }
444 :
445 17 : return Collections.unmodifiableList(r);
446 : }
447 :
448 : private List<PublicKey> myHostKeys() {
449 17 : KeyPairProvider p = getKeyPairProvider();
450 17 : List<PublicKey> keys = new ArrayList<>(6);
451 : try {
452 17 : addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
453 17 : addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
454 17 : addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
455 17 : addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
456 17 : addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
457 17 : addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
458 0 : } catch (IOException | GeneralSecurityException e) {
459 0 : throw new IllegalStateException("Cannot load SSHD host key", e);
460 17 : }
461 17 : return keys;
462 : }
463 :
464 : private static void addPublicKey(final Collection<PublicKey> out, KeyPairProvider p, String type)
465 : throws IOException, GeneralSecurityException {
466 17 : final KeyPair pair = p.loadKey(null, type);
467 17 : if (pair != null && pair.getPublic() != null) {
468 17 : out.add(pair.getPublic());
469 : }
470 17 : }
471 :
472 : private String addressList() {
473 17 : final StringBuilder r = new StringBuilder();
474 17 : for (Iterator<SocketAddress> i = listen.iterator(); i.hasNext(); ) {
475 17 : r.append(SocketUtil.format(i.next(), IANA_SSH_PORT));
476 17 : if (i.hasNext()) {
477 0 : r.append(", ");
478 : }
479 : }
480 17 : return r.toString();
481 : }
482 :
483 : private void initKeyExchanges(Config cfg) {
484 17 : setKexExtensionHandler(DefaultServerKexExtensionHandler.INSTANCE);
485 17 : List<KeyExchangeFactory> a =
486 17 : NamedFactory.setUpTransformedFactories(
487 : true,
488 17 : cfg.getBoolean("sshd", null, "enableDeprecatedKexAlgorithms", false)
489 0 : ? BuiltinDHFactories.VALUES
490 17 : : BaseBuilder.DEFAULT_KEX_PREFERENCE,
491 : ServerBuilder.DH2KEX);
492 17 : setKeyExchangeFactories(filter(cfg, "kex", a.toArray(new KeyExchangeFactory[a.size()])));
493 17 : }
494 :
495 : private void initProviderBouncyCastle(Config cfg) {
496 : NamedFactory<Random> factory;
497 17 : if (cfg.getBoolean("sshd", null, "testUseInsecureRandom", false)) {
498 17 : factory = new InsecureBouncyCastleRandom.Factory();
499 : } else {
500 0 : factory = SecurityUtils.getRandomFactory();
501 : }
502 17 : setRandomFactory(new SingletonRandomFactory(factory));
503 17 : }
504 :
505 : private static class InsecureBouncyCastleRandom implements Random {
506 : private static class Factory implements NamedFactory<Random> {
507 : @Override
508 : public String getName() {
509 0 : return "INSECURE_bouncycastle";
510 : }
511 :
512 : @Override
513 : public Random create() {
514 17 : return new InsecureBouncyCastleRandom();
515 : }
516 : }
517 :
518 : private final RandomGenerator random;
519 :
520 17 : private InsecureBouncyCastleRandom() {
521 17 : random = new VMPCRandomGenerator();
522 17 : random.addSeedMaterial(1234);
523 17 : }
524 :
525 : @Override
526 : public String getName() {
527 0 : return "InsecureBouncyCastleRandom";
528 : }
529 :
530 : @Override
531 : public void fill(byte[] bytes, int start, int len) {
532 17 : random.nextBytes(bytes, start, len);
533 17 : }
534 :
535 : @Override
536 : public void fill(byte[] bytes) {
537 0 : random.nextBytes(bytes);
538 0 : }
539 :
540 : @Override
541 : public int random(int n) {
542 17 : if (n > 0) {
543 17 : if ((n & -n) == n) {
544 17 : return (int) ((n * (long) next(31)) >> 31);
545 : }
546 : int bits;
547 : int val;
548 : do {
549 0 : bits = next(31);
550 0 : val = bits % n;
551 0 : } while (bits - val + (n - 1) < 0);
552 0 : return val;
553 : }
554 0 : throw new IllegalArgumentException();
555 : }
556 :
557 : protected final int next(int numBits) {
558 17 : int bytes = (numBits + 7) / 8;
559 17 : byte[] next = new byte[bytes];
560 17 : int ret = 0;
561 17 : random.nextBytes(next);
562 17 : for (int i = 0; i < bytes; i++) {
563 17 : ret = (next[i] & 0xFF) | (ret << 8);
564 : }
565 17 : return ret >>> (bytes * 8 - numBits);
566 : }
567 : }
568 :
569 : @SuppressWarnings("unchecked")
570 : private void initCiphers(Config cfg) {
571 17 : List<NamedFactory<Cipher>> a = BaseBuilder.setUpDefaultCiphers(true);
572 :
573 17 : for (Iterator<NamedFactory<Cipher>> i = a.iterator(); i.hasNext(); ) {
574 17 : NamedFactory<Cipher> f = i.next();
575 : try {
576 17 : Cipher c = f.create();
577 17 : byte[] key = new byte[c.getKdfSize()];
578 17 : byte[] iv = new byte[c.getIVSize()];
579 17 : c.init(Cipher.Mode.Encrypt, key, iv);
580 0 : } catch (InvalidKeyException e) {
581 0 : logger.atWarning().log(
582 : "Disabling cipher %s: %s; try installing unlimited cryptography extension",
583 0 : f.getName(), e.getMessage());
584 0 : i.remove();
585 0 : } catch (Exception e) {
586 0 : logger.atWarning().log("Disabling cipher %s: %s", f.getName(), e.getMessage());
587 0 : i.remove();
588 17 : }
589 17 : }
590 :
591 17 : a.add(null);
592 17 : setCipherFactories(
593 17 : filter(cfg, "cipher", (NamedFactory<Cipher>[]) a.toArray(new NamedFactory<?>[a.size()])));
594 17 : }
595 :
596 : @SuppressWarnings("unchecked")
597 : private void initMacs(Config cfg) {
598 17 : List<NamedFactory<Mac>> m = BaseBuilder.setUpDefaultMacs(true);
599 17 : setMacFactories(
600 17 : filter(cfg, "mac", (NamedFactory<Mac>[]) m.toArray(new NamedFactory<?>[m.size()])));
601 17 : }
602 :
603 : @SafeVarargs
604 : private static <T extends NamedResource> List<T> filter(Config cfg, String key, T... avail) {
605 17 : List<T> def = new ArrayList<>();
606 17 : for (T n : avail) {
607 17 : if (n == null) {
608 17 : break;
609 : }
610 17 : def.add(n);
611 : }
612 :
613 17 : String[] want = cfg.getStringList("sshd", null, key);
614 17 : if (want == null || want.length == 0) {
615 17 : return def;
616 : }
617 :
618 0 : boolean didClear = false;
619 0 : for (String setting : want) {
620 0 : String name = setting.trim();
621 0 : boolean add = true;
622 0 : if (name.startsWith("-")) {
623 0 : add = false;
624 0 : name = name.substring(1).trim();
625 0 : } else if (name.startsWith("+")) {
626 0 : name = name.substring(1).trim();
627 0 : } else if (!didClear) {
628 0 : didClear = true;
629 0 : def.clear();
630 : }
631 :
632 0 : T n = find(name, avail);
633 0 : if (n == null) {
634 0 : StringBuilder msg = new StringBuilder();
635 0 : msg.append("sshd.").append(key).append(" = ").append(name).append(" unsupported; only ");
636 0 : for (int i = 0; i < avail.length; i++) {
637 0 : if (avail[i] == null) {
638 0 : continue;
639 : }
640 0 : if (i > 0) {
641 0 : msg.append(", ");
642 : }
643 0 : msg.append(avail[i].getName());
644 : }
645 0 : msg.append(" is supported");
646 0 : logger.atSevere().log("%s", msg);
647 0 : } else if (add) {
648 0 : if (!def.contains(n)) {
649 0 : def.add(n);
650 : }
651 : } else {
652 0 : def.remove(n);
653 : }
654 : }
655 :
656 0 : return def;
657 : }
658 :
659 : @SafeVarargs
660 : private static <T extends NamedResource> T find(String name, T... avail) {
661 0 : for (T n : avail) {
662 0 : if (n != null && name.equals(n.getName())) {
663 0 : return n;
664 : }
665 : }
666 0 : return null;
667 : }
668 :
669 : private void initSignatures() {
670 17 : setSignatureFactories(ServerBuilder.setUpDefaultSignatureFactories(false));
671 17 : }
672 :
673 : private void initCompression(boolean enableCompression) {
674 17 : List<NamedFactory<Compression>> compressionFactories = new ArrayList<>();
675 :
676 : // Always support no compression over SSHD.
677 17 : compressionFactories.add(BuiltinCompressions.none);
678 :
679 : // In the general case, we want to disable transparent compression, since
680 : // the majority of our data transfer is highly compressed Git pack files
681 : // and we cannot make them any smaller than they already are.
682 : //
683 : // However, if there are CPU in abundance and the server is reachable through
684 : // slow networks, gits with huge amount of refs can benefit from SSH-compression
685 : // since git does not compress the ref announcement during the handshake.
686 : // Compression can be especially useful when Gerrit replica are being used
687 : // for the larger clones and fetches and the primary server handling write
688 : // operations mostly takes small receive-packs.
689 :
690 17 : if (enableCompression) {
691 0 : compressionFactories.add(BuiltinCompressions.zlib);
692 : }
693 :
694 17 : setCompressionFactories(compressionFactories);
695 17 : }
696 :
697 : private void initChannels() {
698 17 : setChannelFactories(ServerBuilder.DEFAULT_CHANNEL_FACTORIES);
699 17 : }
700 :
701 : private void initUnknownChannelReferenceHandler(boolean enableChannelIdTracking) {
702 17 : setUnknownChannelReferenceHandler(
703 17 : enableChannelIdTracking
704 17 : ? ChannelIdTrackingUnknownChannelReferenceHandler.TRACKER
705 0 : : DefaultUnknownChannelReferenceHandler.INSTANCE);
706 17 : }
707 :
708 : private void initSubsystems() {
709 17 : setSubsystemFactories(Collections.emptyList());
710 17 : }
711 :
712 : private void initUserAuth(
713 : final PublickeyAuthenticator pubkey,
714 : final GSSAuthenticator kerberosAuthenticator,
715 : String kerberosKeytab,
716 : String kerberosPrincipal) {
717 17 : List<UserAuthFactory> authFactories = new ArrayList<>();
718 17 : if (kerberosKeytab != null) {
719 0 : authFactories.add(UserAuthGSSFactory.INSTANCE);
720 0 : logger.atInfo().log("Enabling kerberos with keytab %s", kerberosKeytab);
721 0 : if (!new File(kerberosKeytab).canRead()) {
722 0 : logger.atSevere().log(
723 : "Keytab %s does not exist or is not readable; further errors are possible",
724 : kerberosKeytab);
725 : }
726 0 : kerberosAuthenticator.setKeytabFile(kerberosKeytab);
727 0 : if (kerberosPrincipal == null) {
728 : try {
729 0 : kerberosPrincipal = "host/" + InetAddress.getLocalHost().getCanonicalHostName();
730 0 : } catch (UnknownHostException e) {
731 0 : kerberosPrincipal = "host/localhost";
732 0 : }
733 : }
734 0 : logger.atInfo().log("Using kerberos principal %s", kerberosPrincipal);
735 0 : if (!kerberosPrincipal.startsWith("host/")) {
736 0 : logger.atWarning().log(
737 : "Host principal does not start with host/ "
738 : + "which most SSH clients will supply automatically");
739 : }
740 0 : kerberosAuthenticator.setServicePrincipalName(kerberosPrincipal);
741 0 : setGSSAuthenticator(kerberosAuthenticator);
742 : }
743 17 : authFactories.add(UserAuthPublicKeyFactory.INSTANCE);
744 17 : setUserAuthFactories(authFactories);
745 17 : setPublickeyAuthenticator(pubkey);
746 17 : }
747 :
748 : private void initForwarding() {
749 17 : setForwardingFilter(
750 17 : new ForwardingFilter() {
751 : @Override
752 : public boolean canForwardAgent(Session session, String requestType) {
753 0 : return false;
754 : }
755 :
756 : @Override
757 : public boolean canForwardX11(Session session, String requestType) {
758 0 : return false;
759 : }
760 :
761 : @Override
762 : public boolean canListen(SshdSocketAddress address, Session session) {
763 0 : return false;
764 : }
765 :
766 : @Override
767 : public boolean canConnect(Type type, SshdSocketAddress address, Session session) {
768 0 : return false;
769 : }
770 : });
771 17 : setForwarderFactory(new DefaultForwarderFactory());
772 17 : }
773 :
774 : private void initFileSystemFactory() {
775 17 : setFileSystemFactory(NoneFileSystemFactory.INSTANCE);
776 17 : }
777 : }
|