]> source.dussan.org Git - jgit.git/blob
bfe11cb74558c645c630d0ec8337538571e4f94e
[jgit.git] /
1 /*
2  * Copyright (C) 2018, 2021 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.internal.transport.sshd;
11
12 import static java.text.MessageFormat.format;
13 import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS;
14
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;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28 import java.util.Objects;
29 import java.util.stream.Collectors;
30
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;
47
48 /**
49  * Custom {@link UserAuthPublicKey} implementation for handling SSH config
50  * PubkeyAcceptedAlgorithms and interaction with the SSH agent.
51  */
52 public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
53
54         private SshAgent agent;
55
56         private HostConfigEntry hostConfig;
57
58         JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
59                 super(factories);
60         }
61
62         @Override
63         public void init(ClientSession rawSession, String service)
64                         throws Exception {
65                 if (!(rawSession instanceof JGitClientSession)) {
66                         throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$
67                                         + rawSession.getClass().getCanonicalName());
68                 }
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);
81                                 }
82                                 setSignatureFactoriesNames(signatures);
83                         } else {
84                                 log.warn(format(SshdText.get().configNoKnownAlgorithms,
85                                                 PUBKEY_ACCEPTED_ALGORITHMS, pubkeyAlgos));
86                         }
87                 }
88                 // If we don't set signature factories here, the default ones from the
89                 // session will be used.
90                 super.init(session, service);
91         }
92
93         @Override
94         protected Iterator<PublicKeyIdentity> createPublicKeyIterator(
95                         ClientSession session, SignatureFactoriesManager manager)
96                         throws Exception {
97                 agent = getAgent(session);
98                 return new KeyIterator(session, manager);
99         }
100
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) {
106                         return null;
107                 }
108                 return factory.createClient(session, manager);
109         }
110
111         @Override
112         protected void releaseKeys() throws IOException {
113                 try {
114                         if (agent != null) {
115                                 try {
116                                         agent.close();
117                                 } finally {
118                                         agent = null;
119                                 }
120                         }
121                 } finally {
122                         super.releaseKeys();
123                 }
124         }
125
126         private class KeyIterator extends UserAuthPublicKeyIterator {
127
128                 private Iterable<? extends Map.Entry<PublicKey, String>> agentKeys;
129
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
132                 // getIdentities().
133                 private Collection<PublicKey> identityFiles;
134
135                 public KeyIterator(ClientSession session,
136                                 SignatureFactoriesManager manager)
137                                 throws Exception {
138                         super(session, manager);
139                 }
140
141                 private List<PublicKey> getExplicitKeys(
142                                 Collection<String> explicitFiles) {
143                         if (explicitFiles == null) {
144                                 return null;
145                         }
146                         return explicitFiles.stream().map(s -> {
147                                 try {
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);
153                                         }
154                                 } catch (InvalidPathException | IOException
155                                                 | GeneralSecurityException e) {
156                                         log.warn(format(SshdText.get().cannotReadPublicKey, s), e);
157                                 }
158                                 return null;
159                         }).filter(Objects::nonNull).collect(Collectors.toList());
160                 }
161
162                 @Override
163                 protected Iterable<KeyAgentIdentity> initializeAgentIdentities(
164                                 ClientSession session) throws IOException {
165                         if (agent == null) {
166                                 return null;
167                         }
168                         agentKeys = agent.getIdentities();
169                         if (hostConfig != null && hostConfig.isIdentitiesOnly()) {
170                                 identityFiles = getExplicitKeys(hostConfig.getIdentities());
171                         }
172                         return () -> new Iterator<>() {
173
174                                 private final Iterator<? extends Map.Entry<PublicKey, String>> iter = agentKeys
175                                                 .iterator();
176
177                                 private Map.Entry<PublicKey, String> next;
178
179                                 @Override
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))) {
189                                                         next = val;
190                                                         return true;
191                                                 }
192                                                 if (log.isTraceEnabled()) {
193                                                         log.trace(
194                                                                         "Ignoring SSH agent {} key not in explicit IdentityFile in SSH config: {}", //$NON-NLS-1$
195                                                                         KeyUtils.getKeyType(pk),
196                                                                         KeyUtils.getFingerPrint(pk));
197                                                 }
198                                         }
199                                         return next != null;
200                                 }
201
202                                 @Override
203                                 public KeyAgentIdentity next() {
204                                         if (!hasNext()) {
205                                                 throw new NoSuchElementException();
206                                         }
207                                         KeyAgentIdentity result = new KeyAgentIdentity(agent,
208                                                         next.getKey(), next.getValue());
209                                         next = null;
210                                         return result;
211                                 }
212                         };
213                 }
214         }
215
216         @Override
217         protected PublicKeyIdentity resolveAttemptedPublicKeyIdentity(
218                         ClientSession session, String service) throws Exception {
219                 PublicKeyIdentity result = super.resolveAttemptedPublicKeyIdentity(
220                                 session, service);
221                 // This fixes SSHD-1231. Can be removed once we're using Apache MINA
222                 // sshd > 2.8.0.
223                 //
224                 // See https://issues.apache.org/jira/browse/SSHD-1231
225                 currentAlgorithms.clear();
226                 return result;
227         }
228
229 }