View Javadoc
1   /*
2    * Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.transport.sshd;
11  
12  import java.io.Closeable;
13  import java.io.File;
14  import java.io.IOException;
15  import java.nio.file.Files;
16  import java.nio.file.InvalidPathException;
17  import java.nio.file.Path;
18  import java.security.KeyPair;
19  import java.time.Duration;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  import java.util.function.Supplier;
30  import java.util.stream.Collectors;
31  
32  import org.apache.sshd.client.ClientBuilder;
33  import org.apache.sshd.client.SshClient;
34  import org.apache.sshd.client.auth.UserAuthFactory;
35  import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
36  import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
37  import org.apache.sshd.common.NamedFactory;
38  import org.apache.sshd.common.compression.BuiltinCompressions;
39  import org.apache.sshd.common.config.keys.FilePasswordProvider;
40  import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
41  import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
42  import org.apache.sshd.common.signature.BuiltinSignatures;
43  import org.apache.sshd.common.signature.Signature;
44  import org.eclipse.jgit.annotations.NonNull;
45  import org.eclipse.jgit.errors.TransportException;
46  import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
47  import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
48  import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
49  import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory;
50  import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory;
51  import org.eclipse.jgit.internal.transport.sshd.JGitServerKeyVerifier;
52  import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
53  import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
54  import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction;
55  import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
56  import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
57  import org.eclipse.jgit.internal.transport.sshd.SshdText;
58  import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory;
59  import org.eclipse.jgit.transport.CredentialsProvider;
60  import org.eclipse.jgit.transport.SshConfigStore;
61  import org.eclipse.jgit.transport.SshConstants;
62  import org.eclipse.jgit.transport.SshSessionFactory;
63  import org.eclipse.jgit.transport.URIish;
64  import org.eclipse.jgit.transport.sshd.agent.Connector;
65  import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
66  import org.eclipse.jgit.util.FS;
67  
68  /**
69   * A {@link SshSessionFactory} that uses Apache MINA sshd. Classes from Apache
70   * MINA sshd are kept private to avoid API evolution problems when Apache MINA
71   * sshd interfaces change.
72   *
73   * @since 5.2
74   */
75  public class SshdSessionFactory extends SshSessionFactory implements Closeable {
76  
77  	private static final String MINA_SSHD = "mina-sshd"; //$NON-NLS-1$
78  
79  	private final AtomicBoolean closing = new AtomicBoolean();
80  
81  	private final Set<SshdSession> sessions = new HashSet<>();
82  
83  	private final Map<Tuple, HostConfigEntryResolver> defaultHostConfigEntryResolver = new ConcurrentHashMap<>();
84  
85  	private final Map<Tuple, ServerKeyDatabase> defaultServerKeyDatabase = new ConcurrentHashMap<>();
86  
87  	private final Map<Tuple, Iterable<KeyPair>> defaultKeys = new ConcurrentHashMap<>();
88  
89  	private final KeyCache keyCache;
90  
91  	private final ProxyDataFactory proxies;
92  
93  	private File sshDirectory;
94  
95  	private File homeDirectory;
96  
97  	/**
98  	 * Creates a new {@link SshdSessionFactory} without key cache and a
99  	 * {@link DefaultProxyDataFactory}.
100 	 */
101 	public SshdSessionFactory() {
102 		this(null, new DefaultProxyDataFactory());
103 	}
104 
105 	/**
106 	 * Creates a new {@link SshdSessionFactory} using the given {@link KeyCache}
107 	 * and {@link ProxyDataFactory}. The {@code keyCache} is used for all
108 	 * sessions created through this session factory; cached keys are destroyed
109 	 * when the session factory is {@link #close() closed}.
110 	 * <p>
111 	 * Caching ssh keys in memory for an extended period of time is generally
112 	 * considered bad practice, but there may be circumstances where using a
113 	 * {@link KeyCache} is still the right choice, for instance to avoid that a
114 	 * user gets prompted several times for the same password for the same key.
115 	 * In general, however, it is preferable <em>not</em> to use a key cache but
116 	 * to use a {@link #createKeyPasswordProvider(CredentialsProvider)
117 	 * KeyPasswordProvider} that has access to some secure storage and can save
118 	 * and retrieve passwords from there without user interaction. Another
119 	 * approach is to use an SSH agent.
120 	 * </p>
121 	 * <p>
122 	 * Note that the underlying ssh library (Apache MINA sshd) may or may not
123 	 * keep ssh keys in memory for unspecified periods of time irrespective of
124 	 * the use of a {@link KeyCache}.
125 	 * </p>
126 	 * <p>
127 	 * By default, the factory uses the {@link java.util.ServiceLoader} to find
128 	 * a {@link ConnectorFactory} for creating a {@link Connector} to connect to
129 	 * a running SSH agent. If it finds one, the SSH agent is used in publickey
130 	 * authentication. If there is none, no SSH agent will ever be contacted.
131 	 * Note that one can define {@code IdentitiesOnly yes} for a host entry in
132 	 * the {@code ~/.ssh/config} file to bypass the SSH agent in any case.
133 	 * </p>
134 	 *
135 	 * @param keyCache
136 	 *            {@link KeyCache} to use for caching ssh keys, or {@code null}
137 	 *            to not use a key cache
138 	 * @param proxies
139 	 *            {@link ProxyDataFactory} to use, or {@code null} to not use a
140 	 *            proxy database (in which case connections through proxies will
141 	 *            not be possible)
142 	 */
143 	public SshdSessionFactory(KeyCache keyCache, ProxyDataFactory proxies) {
144 		super();
145 		this.keyCache = keyCache;
146 		this.proxies = proxies;
147 		// sshd limits the number of BCrypt KDF rounds to 255 by default.
148 		// Decrypting such a key takes about two seconds on my machine.
149 		// I consider this limit too low. The time increases linearly with the
150 		// number of rounds.
151 		BCryptKdfOptions.setMaxAllowedRounds(16384);
152 	}
153 
154 	@Override
155 	public String getType() {
156 		return MINA_SSHD;
157 	}
158 
159 	/** A simple general map key. */
160 	private static final class Tuple {
161 		private Object[] objects;
162 
163 		public Tuple(Object[] objects) {
164 			this.objects = objects;
165 		}
166 
167 		@Override
168 		public boolean equals(Object obj) {
169 			if (obj == this) {
170 				return true;
171 			}
172 			if (obj != null && obj.getClass() == Tuple.class) {
173 				Tuple other = (Tuple) obj;
174 				return Arrays.equals(objects, other.objects);
175 			}
176 			return false;
177 		}
178 
179 		@Override
180 		public int hashCode() {
181 			return Arrays.hashCode(objects);
182 		}
183 	}
184 
185 	// We can't really use a single client. Clients need to be stopped
186 	// properly, and we don't really know when to do that. Instead we use
187 	// a dedicated SshClient instance per session. We need a bit of caching to
188 	// avoid re-loading the ssh config and keys repeatedly.
189 
190 	@Override
191 	public SshdSession getSession(URIish uri,
192 			CredentialsProvider credentialsProvider, FS fs, int tms)
193 			throws TransportException {
194 		SshdSession session = null;
195 		try {
196 			session = new SshdSession(uri, () -> {
197 				File home = getHomeDirectory();
198 				if (home == null) {
199 					// Always use the detected filesystem for the user home!
200 					// It makes no sense to have different "user home"
201 					// directories depending on what file system a repository
202 					// is.
203 					home = FS.DETECTED.userHome();
204 				}
205 				File sshDir = getSshDirectory();
206 				if (sshDir == null) {
207 					sshDir = new File(home, SshConstants.SSH_DIR);
208 				}
209 				HostConfigEntryResolver configFile = getHostConfigEntryResolver(
210 						home, sshDir);
211 				KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(
212 						getDefaultKeys(sshDir));
213 				SshClient client = ClientBuilder.builder()
214 						.factory(JGitSshClient::new)
215 						.filePasswordProvider(createFilePasswordProvider(
216 								() -> createKeyPasswordProvider(
217 										credentialsProvider)))
218 						.hostConfigEntryResolver(configFile)
219 						.serverKeyVerifier(new JGitServerKeyVerifier(
220 								getServerKeyDatabase(home, sshDir)))
221 						.signatureFactories(getSignatureFactories())
222 						.compressionFactories(
223 								new ArrayList<>(BuiltinCompressions.VALUES))
224 						.build();
225 				client.setUserInteraction(
226 						new JGitUserInteraction(credentialsProvider));
227 				client.setUserAuthFactories(getUserAuthFactories());
228 				client.setKeyIdentityProvider(defaultKeysProvider);
229 				ConnectorFactory connectors = getConnectorFactory();
230 				if (connectors != null) {
231 					client.setAgentFactory(
232 							new JGitSshAgentFactory(connectors, home));
233 				}
234 				// JGit-specific things:
235 				JGitSshClient jgitClient = (JGitSshClient) client;
236 				jgitClient.setKeyCache(getKeyCache());
237 				jgitClient.setCredentialsProvider(credentialsProvider);
238 				jgitClient.setProxyDatabase(proxies);
239 				String defaultAuths = getDefaultPreferredAuthentications();
240 				if (defaultAuths != null) {
241 					jgitClient.setAttribute(
242 							JGitSshClient.PREFERRED_AUTHENTICATIONS,
243 							defaultAuths);
244 				}
245 				try {
246 					jgitClient.setAttribute(JGitSshClient.HOME_DIRECTORY,
247 							home.getAbsoluteFile().toPath());
248 				} catch (SecurityException | InvalidPathException e) {
249 					// Ignore
250 				}
251 				// Other things?
252 				return client;
253 			});
254 			session.addCloseListener(s -> unregister(s));
255 			register(session);
256 			session.connect(Duration.ofMillis(tms));
257 			return session;
258 		} catch (Exception e) {
259 			unregister(session);
260 			if (e instanceof TransportException) {
261 				throw (TransportException) e;
262 			}
263 			throw new TransportException(uri, e.getMessage(), e);
264 		}
265 	}
266 
267 	@Override
268 	public void close() {
269 		closing.set(true);
270 		boolean cleanKeys = false;
271 		synchronized (this) {
272 			cleanKeys = sessions.isEmpty();
273 		}
274 		if (cleanKeys) {
275 			KeyCache cache = getKeyCache();
276 			if (cache != null) {
277 				cache.close();
278 			}
279 		}
280 	}
281 
282 	private void register(SshdSession newSession) throws IOException {
283 		if (newSession == null) {
284 			return;
285 		}
286 		if (closing.get()) {
287 			throw new IOException(SshdText.get().sshClosingDown);
288 		}
289 		synchronized (this) {
290 			sessions.add(newSession);
291 		}
292 	}
293 
294 	private void unregister(SshdSession oldSession) {
295 		boolean cleanKeys = false;
296 		synchronized (this) {
297 			sessions.remove(oldSession);
298 			cleanKeys = closing.get() && sessions.isEmpty();
299 		}
300 		if (cleanKeys) {
301 			KeyCache cache = getKeyCache();
302 			if (cache != null) {
303 				cache.close();
304 			}
305 		}
306 	}
307 
308 	/**
309 	 * Set a global directory to use as the user's home directory
310 	 *
311 	 * @param homeDir
312 	 *            to use
313 	 */
314 	public void setHomeDirectory(@NonNull File homeDir) {
315 		if (homeDir.isAbsolute()) {
316 			homeDirectory = homeDir;
317 		} else {
318 			homeDirectory = homeDir.getAbsoluteFile();
319 		}
320 	}
321 
322 	/**
323 	 * Retrieves the global user home directory
324 	 *
325 	 * @return the directory, or {@code null} if not set
326 	 */
327 	public File getHomeDirectory() {
328 		return homeDirectory;
329 	}
330 
331 	/**
332 	 * Set a global directory to use as the .ssh directory
333 	 *
334 	 * @param sshDir
335 	 *            to use
336 	 */
337 	public void setSshDirectory(@NonNull File sshDir) {
338 		if (sshDir.isAbsolute()) {
339 			sshDirectory = sshDir;
340 		} else {
341 			sshDirectory = sshDir.getAbsoluteFile();
342 		}
343 	}
344 
345 	/**
346 	 * Retrieves the global .ssh directory
347 	 *
348 	 * @return the directory, or {@code null} if not set
349 	 */
350 	public File getSshDirectory() {
351 		return sshDirectory;
352 	}
353 
354 	/**
355 	 * Obtain a {@link HostConfigEntryResolver} to read the ssh config file and
356 	 * to determine host entries for connections.
357 	 *
358 	 * @param homeDir
359 	 *            home directory to use for ~ replacement
360 	 * @param sshDir
361 	 *            to use for looking for the config file
362 	 * @return the resolver
363 	 */
364 	@NonNull
365 	private HostConfigEntryResolver getHostConfigEntryResolver(
366 			@NonNull File homeDir, @NonNull File sshDir) {
367 		return defaultHostConfigEntryResolver.computeIfAbsent(
368 				new Tuple(new Object[] { homeDir, sshDir }),
369 				t -> new JGitSshConfig(createSshConfigStore(homeDir,
370 						getSshConfig(sshDir), getLocalUserName())));
371 	}
372 
373 	/**
374 	 * Determines the ssh config file. The default implementation returns
375 	 * ~/.ssh/config. If the file does not exist and is created later it will be
376 	 * picked up. To not use a config file at all, return {@code null}.
377 	 *
378 	 * @param sshDir
379 	 *            representing ~/.ssh/
380 	 * @return the file (need not exist), or {@code null} if no config file
381 	 *         shall be used
382 	 * @since 5.5
383 	 */
384 	protected File getSshConfig(@NonNull File sshDir) {
385 		return new File(sshDir, SshConstants.CONFIG);
386 	}
387 
388 	/**
389 	 * Obtains a {@link SshConfigStore}, or {@code null} if not SSH config is to
390 	 * be used. The default implementation returns {@code null} if
391 	 * {@code configFile == null} and otherwise an OpenSSH-compatible store
392 	 * reading host entries from the given file.
393 	 *
394 	 * @param homeDir
395 	 *            may be used for ~-replacements by the returned config store
396 	 * @param configFile
397 	 *            to use, or {@code null} if none
398 	 * @param localUserName
399 	 *            user name of the current user on the local OS
400 	 * @return A {@link SshConfigStore}, or {@code null} if none is to be used
401 	 *
402 	 * @since 5.8
403 	 */
404 	protected SshConfigStore createSshConfigStore(@NonNull File homeDir,
405 			File configFile, String localUserName) {
406 		return configFile == null ? null
407 				: new OpenSshConfigFile(homeDir, configFile, localUserName);
408 	}
409 
410 	/**
411 	 * Obtains a {@link ServerKeyDatabase} to verify server host keys. The
412 	 * default implementation returns a {@link ServerKeyDatabase} that
413 	 * recognizes the two openssh standard files {@code ~/.ssh/known_hosts} and
414 	 * {@code ~/.ssh/known_hosts2} as well as any files configured via the
415 	 * {@code UserKnownHostsFile} option in the ssh config file.
416 	 *
417 	 * @param homeDir
418 	 *            home directory to use for ~ replacement
419 	 * @param sshDir
420 	 *            representing ~/.ssh/
421 	 * @return the {@link ServerKeyDatabase}
422 	 * @since 5.5
423 	 */
424 	@NonNull
425 	protected ServerKeyDatabase getServerKeyDatabase(@NonNull File homeDir,
426 			@NonNull File sshDir) {
427 		return defaultServerKeyDatabase.computeIfAbsent(
428 				new Tuple(new Object[] { homeDir, sshDir }),
429 				t -> createServerKeyDatabase(homeDir, sshDir));
430 
431 	}
432 
433 	/**
434 	 * Creates a {@link ServerKeyDatabase} to verify server host keys. The
435 	 * default implementation returns a {@link ServerKeyDatabase} that
436 	 * recognizes the two openssh standard files {@code ~/.ssh/known_hosts} and
437 	 * {@code ~/.ssh/known_hosts2} as well as any files configured via the
438 	 * {@code UserKnownHostsFile} option in the ssh config file.
439 	 *
440 	 * @param homeDir
441 	 *            home directory to use for ~ replacement
442 	 * @param sshDir
443 	 *            representing ~/.ssh/
444 	 * @return the {@link ServerKeyDatabase}
445 	 * @since 5.8
446 	 */
447 	@NonNull
448 	protected ServerKeyDatabase createServerKeyDatabase(@NonNull File homeDir,
449 			@NonNull File sshDir) {
450 		return new OpenSshServerKeyDatabase(true,
451 				getDefaultKnownHostsFiles(sshDir));
452 	}
453 
454 	/**
455 	 * Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents
456 	 * are not supported.
457 	 * <p>
458 	 * The default implementation uses {@link ConnectorFactory#getDefault()}
459 	 * </p>
460 	 *
461 	 * @return the factory, or {@code null} if no SSH agent support is desired
462 	 * @since 6.0
463 	 */
464 	protected ConnectorFactory getConnectorFactory() {
465 		return ConnectorFactory.getDefault();
466 	}
467 
468 	/**
469 	 * Gets the list of default user known hosts files. The default returns
470 	 * ~/.ssh/known_hosts and ~/.ssh/known_hosts2. The ssh config
471 	 * {@code UserKnownHostsFile} overrides this default.
472 	 *
473 	 * @param sshDir
474 	 * @return the possibly empty list of default known host file paths.
475 	 */
476 	@NonNull
477 	protected List<Path> getDefaultKnownHostsFiles(@NonNull File sshDir) {
478 		return Arrays.asList(sshDir.toPath().resolve(SshConstants.KNOWN_HOSTS),
479 				sshDir.toPath().resolve(SshConstants.KNOWN_HOSTS + '2'));
480 	}
481 
482 	/**
483 	 * Determines the default keys. The default implementation will lazy load
484 	 * the {@link #getDefaultIdentities(File) default identity files}.
485 	 * <p>
486 	 * Subclasses may override and return an {@link Iterable} of whatever keys
487 	 * are appropriate. If the returned iterable lazily loads keys, it should be
488 	 * an instance of
489 	 * {@link org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider
490 	 * AbstractResourceKeyPairProvider} so that the session can later pass it
491 	 * the {@link #createKeyPasswordProvider(CredentialsProvider) password
492 	 * provider} wrapped as a {@link FilePasswordProvider} via
493 	 * {@link org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider#setPasswordFinder(FilePasswordProvider)
494 	 * AbstractResourceKeyPairProvider#setPasswordFinder(FilePasswordProvider)}
495 	 * so that encrypted, password-protected keys can be loaded.
496 	 * </p>
497 	 * <p>
498 	 * The default implementation uses exactly this mechanism; class
499 	 * {@link CachingKeyPairProvider} may serve as a model for a customized
500 	 * lazy-loading {@link Iterable} implementation
501 	 * </p>
502 	 * <p>
503 	 * If the {@link Iterable} returned has the keys already pre-loaded or
504 	 * otherwise doesn't need to decrypt encrypted keys, it can be any
505 	 * {@link Iterable}, for instance a simple {@link java.util.List List}.
506 	 * </p>
507 	 *
508 	 * @param sshDir
509 	 *            to look in for keys
510 	 * @return an {@link Iterable} over the default keys
511 	 * @since 5.3
512 	 */
513 	@NonNull
514 	protected Iterable<KeyPair> getDefaultKeys(@NonNull File sshDir) {
515 		List<Path> defaultIdentities = getDefaultIdentities(sshDir);
516 		return defaultKeys.computeIfAbsent(
517 				new Tuple(defaultIdentities.toArray(new Path[0])),
518 				t -> new CachingKeyPairProvider(defaultIdentities,
519 						getKeyCache()));
520 	}
521 
522 	/**
523 	 * Converts an {@link Iterable} of {link KeyPair}s into a
524 	 * {@link KeyIdentityProvider}.
525 	 *
526 	 * @param keys
527 	 *            to provide via the returned {@link KeyIdentityProvider}
528 	 * @return a {@link KeyIdentityProvider} that provides the given
529 	 *         {@code keys}
530 	 */
531 	private KeyIdentityProvider toKeyIdentityProvider(Iterable<KeyPair> keys) {
532 		if (keys instanceof KeyIdentityProvider) {
533 			return (KeyIdentityProvider) keys;
534 		}
535 		return (session) -> keys;
536 	}
537 
538 	/**
539 	 * Gets a list of default identities, i.e., private key files that shall
540 	 * always be tried for public key authentication. Typically those are
541 	 * ~/.ssh/id_dsa, ~/.ssh/id_rsa, and so on. The default implementation
542 	 * returns the files defined in {@link SshConstants#DEFAULT_IDENTITIES}.
543 	 *
544 	 * @param sshDir
545 	 *            the directory that represents ~/.ssh/
546 	 * @return a possibly empty list of paths containing default identities
547 	 *         (private keys)
548 	 */
549 	@NonNull
550 	protected List<Path> getDefaultIdentities(@NonNull File sshDir) {
551 		return Arrays
552 				.asList(SshConstants.DEFAULT_IDENTITIES).stream()
553 				.map(s -> new File(sshDir, s).toPath()).filter(Files::exists)
554 				.collect(Collectors.toList());
555 	}
556 
557 	/**
558 	 * Obtains the {@link KeyCache} to use to cache loaded keys.
559 	 *
560 	 * @return the {@link KeyCache}, or {@code null} if none.
561 	 */
562 	protected final KeyCache getKeyCache() {
563 		return keyCache;
564 	}
565 
566 	/**
567 	 * Creates a {@link KeyPasswordProvider} for a new session.
568 	 *
569 	 * @param provider
570 	 *            the {@link CredentialsProvider} to delegate to for user
571 	 *            interactions
572 	 * @return a new {@link KeyPasswordProvider}
573 	 */
574 	@NonNull
575 	protected KeyPasswordProvider createKeyPasswordProvider(
576 			CredentialsProvider provider) {
577 		return new IdentityPasswordProvider(provider);
578 	}
579 
580 	/**
581 	 * Creates a {@link FilePasswordProvider} for a new session.
582 	 *
583 	 * @param providerFactory
584 	 *            providing the {@link KeyPasswordProvider} to delegate to
585 	 * @return a new {@link FilePasswordProvider}
586 	 */
587 	@NonNull
588 	private FilePasswordProvider createFilePasswordProvider(
589 			Supplier<KeyPasswordProvider> providerFactory) {
590 		return new PasswordProviderWrapper(providerFactory);
591 	}
592 
593 	/**
594 	 * Gets the user authentication mechanisms (or rather, factories for them).
595 	 * By default this returns gssapi-with-mic, public-key, password, and
596 	 * keyboard-interactive, in that order. The order is only significant if the
597 	 * ssh config does <em>not</em> set {@code PreferredAuthentications}; if it
598 	 * is set, the order defined there will be taken.
599 	 *
600 	 * @return the non-empty list of factories.
601 	 */
602 	@NonNull
603 	private List<UserAuthFactory> getUserAuthFactories() {
604 		// About the order of password and keyboard-interactive, see upstream
605 		// bug https://issues.apache.org/jira/projects/SSHD/issues/SSHD-866 .
606 		// Password auth doesn't have this problem.
607 		return Collections.unmodifiableList(
608 				Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
609 						JGitPublicKeyAuthFactory.FACTORY,
610 						JGitPasswordAuthFactory.INSTANCE,
611 						UserAuthKeyboardInteractiveFactory.INSTANCE));
612 	}
613 
614 	/**
615 	 * Gets the list of default preferred authentication mechanisms. If
616 	 * {@code null} is returned the openssh default list will be in effect. If
617 	 * the ssh config defines {@code PreferredAuthentications} the value from
618 	 * the ssh config takes precedence.
619 	 *
620 	 * @return a comma-separated list of mechanism names, or {@code null} if
621 	 *         none
622 	 */
623 	protected String getDefaultPreferredAuthentications() {
624 		return null;
625 	}
626 
627 	/**
628 	 * Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to
629 	 * set it up explicitly to still allow users to connect with DSA keys.
630 	 *
631 	 * @return a list of supported signature factories
632 	 */
633 	@SuppressWarnings("deprecation")
634 	private static List<NamedFactory<Signature>> getSignatureFactories() {
635 		// @formatter:off
636 		return Arrays.asList(
637 				BuiltinSignatures.nistp256_cert,
638 				BuiltinSignatures.nistp384_cert,
639 				BuiltinSignatures.nistp521_cert,
640 				BuiltinSignatures.ed25519_cert,
641 				BuiltinSignatures.rsaSHA512_cert,
642 				BuiltinSignatures.rsaSHA256_cert,
643 				BuiltinSignatures.rsa_cert,
644 				BuiltinSignatures.nistp256,
645 				BuiltinSignatures.nistp384,
646 				BuiltinSignatures.nistp521,
647 				BuiltinSignatures.ed25519,
648 				BuiltinSignatures.sk_ecdsa_sha2_nistp256,
649 				BuiltinSignatures.sk_ssh_ed25519,
650 				BuiltinSignatures.rsaSHA512,
651 				BuiltinSignatures.rsaSHA256,
652 				BuiltinSignatures.rsa,
653 				BuiltinSignatures.dsa_cert,
654 				BuiltinSignatures.dsa);
655 		// @formatter:on
656 	}
657 }