2 * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
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.
8 * SPDX-License-Identifier: BSD-3-Clause
10 package org.eclipse.jgit.internal.transport.sshd;
12 import static java.text.MessageFormat.format;
13 import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS;
15 import java.io.IOException;
16 import java.nio.file.Files;
17 import java.nio.file.InvalidPathException;
18 import java.nio.file.LinkOption;
19 import java.nio.file.Path;
20 import java.nio.file.Paths;
21 import java.security.GeneralSecurityException;
22 import java.security.PublicKey;
23 import java.util.Collection;
24 import java.util.Iterator;
25 import java.util.List;
27 import java.util.NoSuchElementException;
28 import java.util.Objects;
29 import java.util.stream.Collectors;
31 import org.apache.sshd.agent.SshAgent;
32 import org.apache.sshd.agent.SshAgentFactory;
33 import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity;
34 import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity;
35 import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
36 import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator;
37 import org.apache.sshd.client.config.hosts.HostConfigEntry;
38 import org.apache.sshd.client.session.ClientSession;
39 import org.apache.sshd.common.FactoryManager;
40 import org.apache.sshd.common.NamedFactory;
41 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
42 import org.apache.sshd.common.config.keys.KeyUtils;
43 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
44 import org.apache.sshd.common.signature.Signature;
45 import org.apache.sshd.common.signature.SignatureFactoriesManager;
46 import org.eclipse.jgit.util.StringUtils;
49 * Custom {@link UserAuthPublicKey} implementation for handling SSH config
50 * PubkeyAcceptedAlgorithms and interaction with the SSH agent.
52 public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
54 private SshAgent agent;
56 private HostConfigEntry hostConfig;
58 JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
63 public void init(ClientSession rawSession, String service)
65 if (!(rawSession instanceof JGitClientSession)) {
66 throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$
67 + rawSession.getClass().getCanonicalName());
69 JGitClientSession session = (JGitClientSession) rawSession;
70 hostConfig = session.getHostConfigEntry();
71 // Set signature algorithms for public key authentication
72 String pubkeyAlgos = hostConfig.getProperty(PUBKEY_ACCEPTED_ALGORITHMS);
73 if (!StringUtils.isEmptyOrNull(pubkeyAlgos)) {
74 List<String> signatures = session.getSignatureFactoriesNames();
75 signatures = session.modifyAlgorithmList(signatures,
76 session.getAllAvailableSignatureAlgorithms(), pubkeyAlgos,
77 PUBKEY_ACCEPTED_ALGORITHMS);
78 if (!signatures.isEmpty()) {
79 if (log.isDebugEnabled()) {
80 log.debug(PUBKEY_ACCEPTED_ALGORITHMS + ' ' + signatures);
82 setSignatureFactoriesNames(signatures);
84 log.warn(format(SshdText.get().configNoKnownAlgorithms,
85 PUBKEY_ACCEPTED_ALGORITHMS, pubkeyAlgos));
88 // If we don't set signature factories here, the default ones from the
89 // session will be used.
90 super.init(session, service);
94 protected Iterator<PublicKeyIdentity> createPublicKeyIterator(
95 ClientSession session, SignatureFactoriesManager manager)
97 agent = getAgent(session);
98 return new KeyIterator(session, manager);
101 private SshAgent getAgent(ClientSession session) throws Exception {
102 FactoryManager manager = Objects.requireNonNull(
103 session.getFactoryManager(), "No session factory manager"); //$NON-NLS-1$
104 SshAgentFactory factory = manager.getAgentFactory();
105 if (factory == null) {
108 return factory.createClient(session, manager);
112 protected void releaseKeys() throws IOException {
126 private class KeyIterator extends UserAuthPublicKeyIterator {
128 private Iterable<? extends Map.Entry<PublicKey, String>> agentKeys;
130 // If non-null, all the public keys from explicitly given key files. Any
131 // agent key not matching one of these public keys will be ignored in
133 private Collection<PublicKey> identityFiles;
135 public KeyIterator(ClientSession session,
136 SignatureFactoriesManager manager)
138 super(session, manager);
141 private List<PublicKey> getExplicitKeys(
142 Collection<String> explicitFiles) {
143 if (explicitFiles == null) {
146 return explicitFiles.stream().map(s -> {
148 Path p = Paths.get(s + ".pub"); //$NON-NLS-1$
149 if (Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) {
150 return AuthorizedKeyEntry.readAuthorizedKeys(p).get(0)
151 .resolvePublicKey(null,
152 PublicKeyEntryResolver.IGNORING);
154 } catch (InvalidPathException | IOException
155 | GeneralSecurityException e) {
156 log.warn(format(SshdText.get().cannotReadPublicKey, s), e);
159 }).filter(Objects::nonNull).collect(Collectors.toList());
163 protected Iterable<KeyAgentIdentity> initializeAgentIdentities(
164 ClientSession session) throws IOException {
168 agentKeys = agent.getIdentities();
169 if (hostConfig != null && hostConfig.isIdentitiesOnly()) {
170 identityFiles = getExplicitKeys(hostConfig.getIdentities());
172 return () -> new Iterator<>() {
174 private final Iterator<? extends Map.Entry<PublicKey, String>> iter = agentKeys
177 private Map.Entry<PublicKey, String> next;
180 public boolean hasNext() {
181 while (next == null && iter.hasNext()) {
182 Map.Entry<PublicKey, String> val = iter.next();
183 PublicKey pk = val.getKey();
184 // This checks against all explicit keys for any agent
185 // key, but since identityFiles.size() is typically 1,
186 // it should be fine.
187 if (identityFiles == null || identityFiles.stream()
188 .anyMatch(k -> KeyUtils.compareKeys(k, pk))) {
192 if (log.isTraceEnabled()) {
194 "Ignoring SSH agent {} key not in explicit IdentityFile in SSH config: {}", //$NON-NLS-1$
195 KeyUtils.getKeyType(pk),
196 KeyUtils.getFingerPrint(pk));
203 public KeyAgentIdentity next() {
205 throw new NoSuchElementException();
207 KeyAgentIdentity result = new KeyAgentIdentity(agent,
208 next.getKey(), next.getValue());
217 protected PublicKeyIdentity resolveAttemptedPublicKeyIdentity(
218 ClientSession session, String service) throws Exception {
219 PublicKeyIdentity result = super.resolveAttemptedPublicKeyIdentity(
221 // This fixes SSHD-1231. Can be removed once we're using Apache MINA
224 // See https://issues.apache.org/jira/browse/SSHD-1231
225 currentAlgorithms.clear();