]> source.dussan.org Git - jgit.git/blob
489c77d7367b00b05ae40adf1db6cb6efd216da2
[jgit.git] /
1 /*
2  * Copyright (C) 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 java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.TreeSet;
20
21 import org.apache.sshd.common.AttributeRepository.AttributeKey;
22 import org.apache.sshd.common.NamedFactory;
23 import org.apache.sshd.common.kex.KexProposalOption;
24 import org.apache.sshd.common.kex.extension.KexExtensionHandler;
25 import org.apache.sshd.common.kex.extension.KexExtensions;
26 import org.apache.sshd.common.kex.extension.parser.ServerSignatureAlgorithms;
27 import org.apache.sshd.common.session.Session;
28 import org.apache.sshd.common.signature.Signature;
29 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
30 import org.eclipse.jgit.util.StringUtils;
31
32 /**
33  * Do not use the DefaultClientKexExtensionHandler from sshd; it doesn't work
34  * properly because of misconceptions. See SSHD-1141.
35  *
36  * @see <a href="https://issues.apache.org/jira/browse/SSHD-1141">SSHD-1141</a>
37  */
38 public class JGitKexExtensionHandler extends AbstractLoggingBean
39                 implements KexExtensionHandler {
40
41         /** Singleton instance. */
42         public static final JGitKexExtensionHandler INSTANCE = new JGitKexExtensionHandler();
43
44         /**
45          * Session {@link AttributeKey} used to store whether the extension
46          * indicator was already sent.
47          */
48         private static final AttributeKey<Boolean> CLIENT_PROPOSAL_MADE = new AttributeKey<>();
49
50         /**
51          * Session {@link AttributeKey} storing the algorithms announced by the
52          * server as known.
53          */
54         public static final AttributeKey<Set<String>> SERVER_ALGORITHMS = new AttributeKey<>();
55
56         private JGitKexExtensionHandler() {
57                 // No public instantiation for singleton
58         }
59
60         @Override
61         public boolean isKexExtensionsAvailable(Session session,
62                         AvailabilityPhase phase) throws IOException {
63                 return !AvailabilityPhase.PREKEX.equals(phase);
64         }
65
66         @Override
67         public void handleKexInitProposal(Session session, boolean initiator,
68                         Map<KexProposalOption, String> proposal) throws IOException {
69                 // If it's the very first time, we may add the marker telling the server
70                 // that we are ready to handle SSH_MSG_EXT_INFO
71                 if (session == null || session.isServerSession() || !initiator) {
72                         return;
73                 }
74                 if (session.getAttribute(CLIENT_PROPOSAL_MADE) != null) {
75                         return;
76                 }
77                 String kexAlgorithms = proposal.get(KexProposalOption.SERVERKEYS);
78                 if (StringUtils.isEmptyOrNull(kexAlgorithms)) {
79                         return;
80                 }
81                 List<String> algorithms = new ArrayList<>();
82                 // We're a client. We mustn't send the server extension, and we should
83                 // send the client extension only once.
84                 for (String algo : kexAlgorithms.split(",")) { //$NON-NLS-1$
85                         if (KexExtensions.CLIENT_KEX_EXTENSION.equalsIgnoreCase(algo)
86                                         || KexExtensions.SERVER_KEX_EXTENSION
87                                                         .equalsIgnoreCase(algo)) {
88                                 continue;
89                         }
90                         algorithms.add(algo);
91                 }
92                 // Tell the server that we want to receive SSH2_MSG_EXT_INFO
93                 algorithms.add(KexExtensions.CLIENT_KEX_EXTENSION);
94                 if (log.isDebugEnabled()) {
95                         log.debug(
96                                         "handleKexInitProposal({}): proposing HostKeyAlgorithms {}", //$NON-NLS-1$
97                                         session, algorithms);
98                 }
99                 proposal.put(KexProposalOption.SERVERKEYS,
100                                 String.join(",", algorithms)); //$NON-NLS-1$
101                 session.setAttribute(CLIENT_PROPOSAL_MADE, Boolean.TRUE);
102         }
103
104         @Override
105         public boolean handleKexExtensionRequest(Session session, int index,
106                         int count, String name, byte[] data) throws IOException {
107                 if (ServerSignatureAlgorithms.NAME.equals(name)) {
108                         handleServerSignatureAlgorithms(session,
109                                         ServerSignatureAlgorithms.INSTANCE.parseExtension(data));
110                 }
111                 return true;
112         }
113
114         /**
115          * Perform updates after a server-sig-algs extension has been received.
116          *
117          * @param session
118          *            the message was received for
119          * @param serverAlgorithms
120          *            signature algorithm names announced by the server
121          */
122         protected void handleServerSignatureAlgorithms(Session session,
123                         Collection<String> serverAlgorithms) {
124                 if (log.isDebugEnabled()) {
125                         log.debug("handleServerSignatureAlgorithms({}): {}", session, //$NON-NLS-1$
126                                         serverAlgorithms);
127                 }
128                 // Client determines order; server says what it supports. Re-order
129                 // such that supported ones are at the front, in client order,
130                 // followed by unsupported ones, also in client order.
131                 if (serverAlgorithms != null && !serverAlgorithms.isEmpty()) {
132                         List<NamedFactory<Signature>> clientAlgorithms = session
133                                         .getSignatureFactories();
134                         if (log.isDebugEnabled()) {
135                                 log.debug(
136                                                 "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms before: {}", //$NON-NLS-1$
137                                                 session, clientAlgorithms);
138                         }
139                         List<NamedFactory<Signature>> unknown = new ArrayList<>();
140                         Set<String> known = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
141                         known.addAll(serverAlgorithms);
142                         for (Iterator<NamedFactory<Signature>> iter = clientAlgorithms
143                                         .iterator(); iter.hasNext();) {
144                                 NamedFactory<Signature> algo = iter.next();
145                                 if (!known.contains(algo.getName())) {
146                                         unknown.add(algo);
147                                         iter.remove();
148                                 }
149                         }
150                         // Re-add the unknown ones at the end. Per RFC 8308, some
151                         // servers may not announce _all_ their supported algorithms,
152                         // and a client may use unknown algorithms.
153                         clientAlgorithms.addAll(unknown);
154                         if (log.isDebugEnabled()) {
155                                 log.debug(
156                                                 "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms after: {}", //$NON-NLS-1$
157                                                 session, clientAlgorithms);
158                         }
159                         session.setAttribute(SERVER_ALGORITHMS, known);
160                         session.setSignatureFactories(clientAlgorithms);
161                 }
162         }
163 }