2 * Copyright (C) 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 java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Iterator;
16 import java.util.List;
19 import java.util.TreeSet;
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;
33 * Do not use the DefaultClientKexExtensionHandler from sshd; it doesn't work
34 * properly because of misconceptions. See SSHD-1141.
36 * @see <a href="https://issues.apache.org/jira/browse/SSHD-1141">SSHD-1141</a>
38 public class JGitKexExtensionHandler extends AbstractLoggingBean
39 implements KexExtensionHandler {
41 /** Singleton instance. */
42 public static final JGitKexExtensionHandler INSTANCE = new JGitKexExtensionHandler();
45 * Session {@link AttributeKey} used to store whether the extension
46 * indicator was already sent.
48 private static final AttributeKey<Boolean> CLIENT_PROPOSAL_MADE = new AttributeKey<>();
51 * Session {@link AttributeKey} storing the algorithms announced by the
54 public static final AttributeKey<Set<String>> SERVER_ALGORITHMS = new AttributeKey<>();
56 private JGitKexExtensionHandler() {
57 // No public instantiation for singleton
61 public boolean isKexExtensionsAvailable(Session session,
62 AvailabilityPhase phase) throws IOException {
63 return !AvailabilityPhase.PREKEX.equals(phase);
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) {
74 if (session.getAttribute(CLIENT_PROPOSAL_MADE) != null) {
77 String kexAlgorithms = proposal.get(KexProposalOption.SERVERKEYS);
78 if (StringUtils.isEmptyOrNull(kexAlgorithms)) {
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)) {
92 // Tell the server that we want to receive SSH2_MSG_EXT_INFO
93 algorithms.add(KexExtensions.CLIENT_KEX_EXTENSION);
94 if (log.isDebugEnabled()) {
96 "handleKexInitProposal({}): proposing HostKeyAlgorithms {}", //$NON-NLS-1$
99 proposal.put(KexProposalOption.SERVERKEYS,
100 String.join(",", algorithms)); //$NON-NLS-1$
101 session.setAttribute(CLIENT_PROPOSAL_MADE, Boolean.TRUE);
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));
115 * Perform updates after a server-sig-algs extension has been received.
118 * the message was received for
119 * @param serverAlgorithms
120 * signature algorithm names announced by the server
122 protected void handleServerSignatureAlgorithms(Session session,
123 Collection<String> serverAlgorithms) {
124 if (log.isDebugEnabled()) {
125 log.debug("handleServerSignatureAlgorithms({}): {}", session, //$NON-NLS-1$
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()) {
136 "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms before: {}", //$NON-NLS-1$
137 session, clientAlgorithms);
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())) {
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()) {
156 "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms after: {}", //$NON-NLS-1$
157 session, clientAlgorithms);
159 session.setAttribute(SERVER_ALGORITHMS, known);
160 session.setSignatureFactories(clientAlgorithms);