123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- /*
- * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.internal.transport.sshd;
-
- import java.io.IOException;
- import java.security.PublicKey;
- import java.security.spec.InvalidKeySpecException;
- import java.text.MessageFormat;
- import java.util.Deque;
- import java.util.HashSet;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Set;
-
- import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
- import org.apache.sshd.client.session.ClientSession;
- import org.apache.sshd.common.NamedFactory;
- import org.apache.sshd.common.NamedResource;
- import org.apache.sshd.common.RuntimeSshException;
- import org.apache.sshd.common.SshConstants;
- import org.apache.sshd.common.config.keys.KeyUtils;
- import org.apache.sshd.common.signature.Signature;
- import org.apache.sshd.common.signature.SignatureFactoriesHolder;
- import org.apache.sshd.common.util.buffer.Buffer;
-
- /**
- * Custom {@link UserAuthPublicKey} implementation fixing SSHD-1105: if there
- * are several signature algorithms applicable for a public key type, we must
- * try them all, in the correct order.
- *
- * @see <a href="https://issues.apache.org/jira/browse/SSHD-1105">SSHD-1105</a>
- * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">Bug
- * 572056</a>
- */
- public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
-
- private final Deque<String> currentAlgorithms = new LinkedList<>();
-
- private String chosenAlgorithm;
-
- JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
- super(factories);
- }
-
- @Override
- protected boolean sendAuthDataRequest(ClientSession session, String service)
- throws Exception {
- if (current == null) {
- currentAlgorithms.clear();
- chosenAlgorithm = null;
- }
- String currentAlgorithm = null;
- if (current != null && !currentAlgorithms.isEmpty()) {
- currentAlgorithm = currentAlgorithms.poll();
- if (chosenAlgorithm != null) {
- Set<String> knownServerAlgorithms = session.getAttribute(
- JGitKexExtensionHandler.SERVER_ALGORITHMS);
- if (knownServerAlgorithms != null
- && knownServerAlgorithms.contains(chosenAlgorithm)) {
- // We've tried key 'current' with 'chosenAlgorithm', but it
- // failed. However, the server had told us it supported
- // 'chosenAlgorithm'. Thus it makes no sense to continue
- // with this key and other signature algorithms. Skip to the
- // next key, if any.
- currentAlgorithm = null;
- }
- }
- }
- if (currentAlgorithm == null) {
- try {
- if (keys == null || !keys.hasNext()) {
- if (log.isDebugEnabled()) {
- log.debug(
- "sendAuthDataRequest({})[{}] no more keys to send", //$NON-NLS-1$
- session, service);
- }
- current = null;
- return false;
- }
- current = keys.next();
- currentAlgorithms.clear();
- chosenAlgorithm = null;
- } catch (Error e) { // Copied from superclass
- throw new RuntimeSshException(e);
- }
- }
- PublicKey key;
- try {
- key = current.getPublicKey();
- } catch (Error e) { // Copied from superclass
- throw new RuntimeSshException(e);
- }
- if (currentAlgorithm == null) {
- String keyType = KeyUtils.getKeyType(key);
- Set<String> aliases = new HashSet<>(
- KeyUtils.getAllEquivalentKeyTypes(keyType));
- aliases.add(keyType);
- List<NamedFactory<Signature>> existingFactories;
- if (current instanceof SignatureFactoriesHolder) {
- existingFactories = ((SignatureFactoriesHolder) current)
- .getSignatureFactories();
- } else {
- existingFactories = getSignatureFactories();
- }
- if (existingFactories != null) {
- if (log.isDebugEnabled()) {
- log.debug(
- "sendAuthDataRequest({})[{}] selecting from PubKeyAcceptedAlgorithms {}", //$NON-NLS-1$
- session, service,
- NamedResource.getNames(existingFactories));
- }
- // Select the factories by name and in order
- existingFactories.forEach(f -> {
- if (aliases.contains(f.getName())) {
- currentAlgorithms.add(f.getName());
- }
- });
- }
- currentAlgorithm = currentAlgorithms.isEmpty() ? keyType
- : currentAlgorithms.poll();
- }
- String name = getName();
- if (log.isDebugEnabled()) {
- log.debug(
- "sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}", //$NON-NLS-1$
- session, service, name, currentAlgorithm,
- KeyUtils.getFingerPrint(key));
- }
-
- chosenAlgorithm = currentAlgorithm;
- Buffer buffer = session
- .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
- buffer.putString(session.getUsername());
- buffer.putString(service);
- buffer.putString(name);
- buffer.putBoolean(false);
- buffer.putString(currentAlgorithm);
- buffer.putPublicKey(key);
- session.writePacket(buffer);
- return true;
- }
-
- @Override
- protected boolean processAuthDataRequest(ClientSession session,
- String service, Buffer buffer) throws Exception {
- String name = getName();
- int cmd = buffer.getUByte();
- if (cmd != SshConstants.SSH_MSG_USERAUTH_PK_OK) {
- throw new IllegalStateException(MessageFormat.format(
- SshdText.get().pubkeyAuthWrongCommand,
- SshConstants.getCommandMessageName(cmd),
- session.getConnectAddress(), session.getServerVersion()));
- }
- PublicKey key;
- try {
- key = current.getPublicKey();
- } catch (Error e) { // Copied from superclass
- throw new RuntimeSshException(e);
- }
- String rspKeyAlgorithm = buffer.getString();
- PublicKey rspKey = buffer.getPublicKey();
- if (log.isDebugEnabled()) {
- log.debug(
- "processAuthDataRequest({})[{}][{}] SSH_MSG_USERAUTH_PK_OK type={}, fingerprint={}", //$NON-NLS-1$
- session, service, name, rspKeyAlgorithm,
- KeyUtils.getFingerPrint(rspKey));
- }
- if (!KeyUtils.compareKeys(rspKey, key)) {
- throw new InvalidKeySpecException(MessageFormat.format(
- SshdText.get().pubkeyAuthWrongKey,
- KeyUtils.getFingerPrint(key),
- KeyUtils.getFingerPrint(rspKey),
- session.getConnectAddress(), session.getServerVersion()));
- }
- if (!chosenAlgorithm.equalsIgnoreCase(rspKeyAlgorithm)) {
- // 'algo' SHOULD be the same as 'chosenAlgorithm', which is the one
- // we sent above. See https://tools.ietf.org/html/rfc4252#page-9 .
- //
- // However, at least Github (SSH-2.0-babeld-383743ad) servers seem
- // to return the key type, not the algorithm name.
- //
- // So we don't check but just log the inconsistency. We sign using
- // 'chosenAlgorithm' in any case, so we don't really care what the
- // server says here.
- log.warn(MessageFormat.format(
- SshdText.get().pubkeyAuthWrongSignatureAlgorithm,
- chosenAlgorithm, rspKeyAlgorithm, session.getConnectAddress(),
- session.getServerVersion()));
- }
- String username = session.getUsername();
- Buffer out = session
- .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
- out.putString(username);
- out.putString(service);
- out.putString(name);
- out.putBoolean(true);
- out.putString(chosenAlgorithm);
- out.putPublicKey(key);
- if (log.isDebugEnabled()) {
- log.debug(
- "processAuthDataRequest({})[{}][{}]: signing with algorithm {}", //$NON-NLS-1$
- session, service, name, chosenAlgorithm);
- }
- appendSignature(session, service, name, username, chosenAlgorithm, key,
- out);
- session.writePacket(out);
- return true;
- }
-
- @Override
- protected void releaseKeys() throws IOException {
- currentAlgorithms.clear();
- current = null;
- chosenAlgorithm = null;
- super.releaseKeys();
- }
- }
|