blob: add79b35c9bbe8015e29fb518bc4a27885c916e5 [file] [log] [blame]
/*
* Copyright (C) 2022 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 static org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider.getKeyId;
import java.security.KeyPair;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
import org.apache.sshd.client.auth.password.UserAuthPassword;
import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.KeyUtils;
/**
* Provides a log of authentication attempts for a {@link ClientSession}.
*/
public class AuthenticationLogger {
private final List<String> messages = new ArrayList<>();
// We're interested in this log only in the failure case, so we don't need
// to log authentication success.
private final PublicKeyAuthenticationReporter pubkeyLogger = new PublicKeyAuthenticationReporter() {
private boolean hasAttempts;
@Override
public void signalAuthenticationAttempt(ClientSession session,
String service, KeyPair identity, String signature)
throws Exception {
hasAttempts = true;
String message;
if (identity.getPrivate() == null) {
// SSH agent key
message = MessageFormat.format(
SshdText.get().authPubkeyAttemptAgent,
UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
getKeyId(session, identity), signature);
} else {
message = MessageFormat.format(
SshdText.get().authPubkeyAttempt,
UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
getKeyId(session, identity), signature);
}
messages.add(message);
}
@Override
public void signalAuthenticationExhausted(ClientSession session,
String service) throws Exception {
String message;
if (hasAttempts) {
message = MessageFormat.format(
SshdText.get().authPubkeyExhausted,
UserAuthPublicKey.NAME);
} else {
message = MessageFormat.format(SshdText.get().authPubkeyNoKeys,
UserAuthPublicKey.NAME);
}
messages.add(message);
hasAttempts = false;
}
@Override
public void signalAuthenticationFailure(ClientSession session,
String service, KeyPair identity, boolean partial,
List<String> serverMethods) throws Exception {
String message;
if (partial) {
message = MessageFormat.format(
SshdText.get().authPubkeyPartialSuccess,
UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
getKeyId(session, identity), serverMethods);
} else {
message = MessageFormat.format(
SshdText.get().authPubkeyFailure,
UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
getKeyId(session, identity));
}
messages.add(message);
}
};
private final PasswordAuthenticationReporter passwordLogger = new PasswordAuthenticationReporter() {
private int attempts;
@Override
public void signalAuthenticationAttempt(ClientSession session,
String service, String oldPassword, boolean modified,
String newPassword) throws Exception {
attempts++;
String message;
if (modified) {
message = MessageFormat.format(
SshdText.get().authPasswordChangeAttempt,
UserAuthPassword.NAME, Integer.valueOf(attempts));
} else {
message = MessageFormat.format(
SshdText.get().authPasswordAttempt,
UserAuthPassword.NAME, Integer.valueOf(attempts));
}
messages.add(message);
}
@Override
public void signalAuthenticationExhausted(ClientSession session,
String service) throws Exception {
String message;
if (attempts > 0) {
message = MessageFormat.format(
SshdText.get().authPasswordExhausted,
UserAuthPassword.NAME);
} else {
message = MessageFormat.format(
SshdText.get().authPasswordNotTried,
UserAuthPassword.NAME);
}
messages.add(message);
attempts = 0;
}
@Override
public void signalAuthenticationFailure(ClientSession session,
String service, String password, boolean partial,
List<String> serverMethods) throws Exception {
String message;
if (partial) {
message = MessageFormat.format(
SshdText.get().authPasswordPartialSuccess,
UserAuthPassword.NAME, serverMethods);
} else {
message = MessageFormat.format(
SshdText.get().authPasswordFailure,
UserAuthPassword.NAME);
}
messages.add(message);
}
};
private final GssApiWithMicAuthenticationReporter gssLogger = new GssApiWithMicAuthenticationReporter() {
private boolean hasAttempts;
@Override
public void signalAuthenticationAttempt(ClientSession session,
String service, String mechanism) {
hasAttempts = true;
String message = MessageFormat.format(
SshdText.get().authGssApiAttempt,
GssApiWithMicAuthFactory.NAME, mechanism);
messages.add(message);
}
@Override
public void signalAuthenticationExhausted(ClientSession session,
String service) {
String message;
if (hasAttempts) {
message = MessageFormat.format(
SshdText.get().authGssApiExhausted,
GssApiWithMicAuthFactory.NAME);
} else {
message = MessageFormat.format(
SshdText.get().authGssApiNotTried,
GssApiWithMicAuthFactory.NAME);
}
messages.add(message);
hasAttempts = false;
}
@Override
public void signalAuthenticationFailure(ClientSession session,
String service, String mechanism, boolean partial,
List<String> serverMethods) {
String message;
if (partial) {
message = MessageFormat.format(
SshdText.get().authGssApiPartialSuccess,
GssApiWithMicAuthFactory.NAME, mechanism,
serverMethods);
} else {
message = MessageFormat.format(
SshdText.get().authGssApiFailure,
GssApiWithMicAuthFactory.NAME, mechanism);
}
messages.add(message);
}
};
/**
* Creates a new {@link AuthenticationLogger} and configures the
* {@link ClientSession} to report authentication attempts through this
* instance.
*
* @param session
* to configure
*/
public AuthenticationLogger(ClientSession session) {
session.setPublicKeyAuthenticationReporter(pubkeyLogger);
session.setPasswordAuthenticationReporter(passwordLogger);
session.setAttribute(
GssApiWithMicAuthenticationReporter.GSS_AUTHENTICATION_REPORTER,
gssLogger);
// TODO: keyboard-interactive? sshd 2.8.0 has no callback
// interface for it.
}
/**
* Retrieves the log messages for the authentication attempts.
*
* @return the messages as an unmodifiable list
*/
public List<String> getLog() {
return Collections.unmodifiableList(messages);
}
/**
* Drops all previously recorded log messages.
*/
public void clear() {
messages.clear();
}
}