JGitPublicKeyAuthentication.java

/*
 * 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.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.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 List<String> algorithms = new LinkedList<>();

	JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
		super(factories);
	}

	@Override
	protected boolean sendAuthDataRequest(ClientSession session, String service)
			throws Exception {
		if (current == null) {
			algorithms.clear();
		}
		String currentAlgorithm = null;
		if (current != null && !algorithms.isEmpty()) {
			currentAlgorithm = algorithms.remove(0);
		}
		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);
					}
					return false;
				}
				current = keys.next();
				algorithms.clear();
			} catch (Error e) { // Copied from superclass
				warn("sendAuthDataRequest({})[{}] failed ({}) to get next key: {}", //$NON-NLS-1$
						session, service, e.getClass().getSimpleName(),
						e.getMessage(), e);
				throw new RuntimeSshException(e);
			}
		}
		PublicKey key;
		try {
			key = current.getPublicKey();
		} catch (Error e) { // Copied from superclass
			warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve public key: {}", //$NON-NLS-1$
					session, service, e.getClass().getSimpleName(),
					e.getMessage(), e);
			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) {
				// Select the factories by name and in order
				existingFactories.forEach(f -> {
					if (aliases.contains(f.getName())) {
						algorithms.add(f.getName());
					}
				});
			}
			currentAlgorithm = algorithms.isEmpty() ? keyType
					: algorithms.remove(0);
		}
		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));
		}

		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 void releaseKeys() throws IOException {
		algorithms.clear();
		current = null;
		super.releaseKeys();
	}
}