| /* |
| * 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(); |
| } |
| } |