AuthenticationLogger.java

  1. /*
  2.  * Copyright (C) 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.internal.transport.sshd;

  11. import static org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider.getKeyId;

  12. import java.security.KeyPair;
  13. import java.text.MessageFormat;
  14. import java.util.ArrayList;
  15. import java.util.Collections;
  16. import java.util.List;

  17. import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
  18. import org.apache.sshd.client.auth.password.UserAuthPassword;
  19. import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
  20. import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
  21. import org.apache.sshd.client.session.ClientSession;
  22. import org.apache.sshd.common.config.keys.KeyUtils;

  23. /**
  24.  * Provides a log of authentication attempts for a {@link ClientSession}.
  25.  */
  26. public class AuthenticationLogger {

  27.     private final List<String> messages = new ArrayList<>();

  28.     // We're interested in this log only in the failure case, so we don't need
  29.     // to log authentication success.

  30.     private final PublicKeyAuthenticationReporter pubkeyLogger = new PublicKeyAuthenticationReporter() {

  31.         private boolean hasAttempts;

  32.         @Override
  33.         public void signalAuthenticationAttempt(ClientSession session,
  34.                 String service, KeyPair identity, String signature)
  35.                 throws Exception {
  36.             hasAttempts = true;
  37.             String message;
  38.             if (identity.getPrivate() == null) {
  39.                 // SSH agent key
  40.                 message = MessageFormat.format(
  41.                         SshdText.get().authPubkeyAttemptAgent,
  42.                         UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
  43.                         getKeyId(session, identity), signature);
  44.             } else {
  45.                 message = MessageFormat.format(
  46.                         SshdText.get().authPubkeyAttempt,
  47.                         UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
  48.                         getKeyId(session, identity), signature);
  49.             }
  50.             messages.add(message);
  51.         }

  52.         @Override
  53.         public void signalAuthenticationExhausted(ClientSession session,
  54.                 String service) throws Exception {
  55.             String message;
  56.             if (hasAttempts) {
  57.                 message = MessageFormat.format(
  58.                         SshdText.get().authPubkeyExhausted,
  59.                         UserAuthPublicKey.NAME);
  60.             } else {
  61.                 message = MessageFormat.format(SshdText.get().authPubkeyNoKeys,
  62.                         UserAuthPublicKey.NAME);
  63.             }
  64.             messages.add(message);
  65.             hasAttempts = false;
  66.         }

  67.         @Override
  68.         public void signalAuthenticationFailure(ClientSession session,
  69.                 String service, KeyPair identity, boolean partial,
  70.                 List<String> serverMethods) throws Exception {
  71.             String message;
  72.             if (partial) {
  73.                 message = MessageFormat.format(
  74.                         SshdText.get().authPubkeyPartialSuccess,
  75.                         UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
  76.                         getKeyId(session, identity), serverMethods);
  77.             } else {
  78.                 message = MessageFormat.format(
  79.                         SshdText.get().authPubkeyFailure,
  80.                         UserAuthPublicKey.NAME, KeyUtils.getKeyType(identity),
  81.                         getKeyId(session, identity));
  82.             }
  83.             messages.add(message);
  84.         }
  85.     };

  86.     private final PasswordAuthenticationReporter passwordLogger = new PasswordAuthenticationReporter() {

  87.         private int attempts;

  88.         @Override
  89.         public void signalAuthenticationAttempt(ClientSession session,
  90.                 String service, String oldPassword, boolean modified,
  91.                 String newPassword) throws Exception {
  92.             attempts++;
  93.             String message;
  94.             if (modified) {
  95.                 message = MessageFormat.format(
  96.                         SshdText.get().authPasswordChangeAttempt,
  97.                         UserAuthPassword.NAME, Integer.valueOf(attempts));
  98.             } else {
  99.                 message = MessageFormat.format(
  100.                         SshdText.get().authPasswordAttempt,
  101.                         UserAuthPassword.NAME, Integer.valueOf(attempts));
  102.             }
  103.             messages.add(message);
  104.         }

  105.         @Override
  106.         public void signalAuthenticationExhausted(ClientSession session,
  107.                 String service) throws Exception {
  108.             String message;
  109.             if (attempts > 0) {
  110.                 message = MessageFormat.format(
  111.                         SshdText.get().authPasswordExhausted,
  112.                         UserAuthPassword.NAME);
  113.             } else {
  114.                 message = MessageFormat.format(
  115.                         SshdText.get().authPasswordNotTried,
  116.                         UserAuthPassword.NAME);
  117.             }
  118.             messages.add(message);
  119.             attempts = 0;
  120.         }

  121.         @Override
  122.         public void signalAuthenticationFailure(ClientSession session,
  123.                 String service, String password, boolean partial,
  124.                 List<String> serverMethods) throws Exception {
  125.             String message;
  126.             if (partial) {
  127.                 message = MessageFormat.format(
  128.                         SshdText.get().authPasswordPartialSuccess,
  129.                         UserAuthPassword.NAME, serverMethods);
  130.             } else {
  131.                 message = MessageFormat.format(
  132.                         SshdText.get().authPasswordFailure,
  133.                         UserAuthPassword.NAME);
  134.             }
  135.             messages.add(message);
  136.         }
  137.     };

  138.     private final GssApiWithMicAuthenticationReporter gssLogger = new GssApiWithMicAuthenticationReporter() {

  139.         private boolean hasAttempts;

  140.         @Override
  141.         public void signalAuthenticationAttempt(ClientSession session,
  142.                 String service, String mechanism) {
  143.             hasAttempts = true;
  144.             String message = MessageFormat.format(
  145.                     SshdText.get().authGssApiAttempt,
  146.                     GssApiWithMicAuthFactory.NAME, mechanism);
  147.             messages.add(message);
  148.         }

  149.         @Override
  150.         public void signalAuthenticationExhausted(ClientSession session,
  151.                 String service) {
  152.             String message;
  153.             if (hasAttempts) {
  154.                 message = MessageFormat.format(
  155.                         SshdText.get().authGssApiExhausted,
  156.                         GssApiWithMicAuthFactory.NAME);
  157.             } else {
  158.                 message = MessageFormat.format(
  159.                         SshdText.get().authGssApiNotTried,
  160.                         GssApiWithMicAuthFactory.NAME);
  161.             }
  162.             messages.add(message);
  163.             hasAttempts = false;
  164.         }

  165.         @Override
  166.         public void signalAuthenticationFailure(ClientSession session,
  167.                 String service, String mechanism, boolean partial,
  168.                 List<String> serverMethods) {
  169.             String message;
  170.             if (partial) {
  171.                 message = MessageFormat.format(
  172.                         SshdText.get().authGssApiPartialSuccess,
  173.                         GssApiWithMicAuthFactory.NAME, mechanism,
  174.                         serverMethods);
  175.             } else {
  176.                 message = MessageFormat.format(
  177.                         SshdText.get().authGssApiFailure,
  178.                         GssApiWithMicAuthFactory.NAME, mechanism);
  179.             }
  180.             messages.add(message);
  181.         }
  182.     };

  183.     /**
  184.      * Creates a new {@link AuthenticationLogger} and configures the
  185.      * {@link ClientSession} to report authentication attempts through this
  186.      * instance.
  187.      *
  188.      * @param session
  189.      *            to configure
  190.      */
  191.     public AuthenticationLogger(ClientSession session) {
  192.         session.setPublicKeyAuthenticationReporter(pubkeyLogger);
  193.         session.setPasswordAuthenticationReporter(passwordLogger);
  194.         session.setAttribute(
  195.                 GssApiWithMicAuthenticationReporter.GSS_AUTHENTICATION_REPORTER,
  196.                 gssLogger);
  197.         // TODO: keyboard-interactive? sshd 2.8.0 has no callback
  198.         // interface for it.
  199.     }

  200.     /**
  201.      * Retrieves the log messages for the authentication attempts.
  202.      *
  203.      * @return the messages as an unmodifiable list
  204.      */
  205.     public List<String> getLog() {
  206.         return Collections.unmodifiableList(messages);
  207.     }

  208.     /**
  209.      * Drops all previously recorded log messages.
  210.      */
  211.     public void clear() {
  212.         messages.clear();
  213.     }
  214. }