in sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java [2054:2161]
protected Map<KexProposalOption, String> negotiate() throws Exception {
Map<KexProposalOption, String> c2sOptions = getClientKexProposals();
Map<KexProposalOption, String> s2cOptions = getServerKexProposals();
signalNegotiationStart(c2sOptions, s2cOptions);
Map<KexProposalOption, String> guess = new EnumMap<>(KexProposalOption.class);
Map<KexProposalOption, String> negotiatedGuess = Collections.unmodifiableMap(guess);
try {
boolean debugEnabled = log.isDebugEnabled();
boolean traceEnabled = log.isTraceEnabled();
SessionDisconnectHandler discHandler = getSessionDisconnectHandler();
KexExtensionHandler extHandler = getKexExtensionHandler();
for (KexProposalOption paramType : KexProposalOption.VALUES) {
String clientParamValue = c2sOptions.get(paramType);
String serverParamValue = s2cOptions.get(paramType);
String[] c = GenericUtils.split(clientParamValue, ',');
String[] s = GenericUtils.split(serverParamValue, ',');
/*
* According to https://tools.ietf.org/html/rfc8308#section-2.2:
*
* Implementations MAY disconnect if the counterpart sends an incorrect (KEX extension) indicator
*
* TODO - for now we do not enforce this
*/
for (String ci : c) {
for (String si : s) {
if (ci.equals(si)) {
guess.put(paramType, ci);
break;
}
}
String value = guess.get(paramType);
if (value != null) {
break;
}
}
// check if reached an agreement
String value = guess.get(paramType);
if (extHandler != null) {
extHandler.handleKexExtensionNegotiation(
this, paramType, value, c2sOptions, clientParamValue, s2cOptions, serverParamValue);
}
if (value != null) {
if (traceEnabled) {
log.trace("negotiate({})[{}] guess={} (client={} / server={})",
this, paramType.getDescription(), value, clientParamValue, serverParamValue);
}
continue;
}
try {
if ((discHandler != null)
&& discHandler.handleKexDisconnectReason(
this, c2sOptions, s2cOptions, negotiatedGuess, paramType)) {
if (debugEnabled) {
log.debug("negotiate({}) ignore missing value for KEX option={}", this, paramType);
}
continue;
}
} catch (IOException | RuntimeException e) {
// If disconnect handler throws an exception continue with the disconnect
debug("negotiate({}) failed ({}) to invoke disconnect handler due to mismatched KEX option={}: {}",
this, e.getClass().getSimpleName(), paramType, e.getMessage(), e);
}
String message = "Unable to negotiate key exchange for " + paramType.getDescription()
+ " (client: " + clientParamValue + " / server: " + serverParamValue + ")";
// OK if could not negotiate languages
if (KexProposalOption.S2CLANG.equals(paramType) || KexProposalOption.C2SLANG.equals(paramType)) {
if (traceEnabled) {
log.trace("negotiate({}) {}", this, message);
}
} else {
throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, message);
}
}
/*
* According to https://tools.ietf.org/html/rfc8308#section-2.2:
*
* If "ext-info-c" or "ext-info-s" ends up being negotiated as a key exchange method, the parties MUST
* disconnect.
*/
String kexOption = guess.get(KexProposalOption.ALGORITHMS);
if (KexExtensions.IS_KEX_EXTENSION_SIGNAL.test(kexOption)) {
if ((discHandler != null)
&& discHandler.handleKexDisconnectReason(
this, c2sOptions, s2cOptions, negotiatedGuess, KexProposalOption.ALGORITHMS)) {
if (debugEnabled) {
log.debug("negotiate({}) ignore violating {} KEX option={}", this, KexProposalOption.ALGORITHMS,
kexOption);
}
} else {
throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
"Illegal KEX option negotiated: " + kexOption);
}
}
} catch (IOException | RuntimeException | Error e) {
signalNegotiationEnd(c2sOptions, s2cOptions, negotiatedGuess, e);
throw e;
}
signalNegotiationEnd(c2sOptions, s2cOptions, negotiatedGuess, null);
return setNegotiationResult(guess);
}