PasswordProviderWrapper.java

  1. /*
  2.  * Copyright (C) 2018, 2020 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.apache.sshd.core.CoreModuleProperties.PASSWORD_PROMPTS;

  12. import java.io.IOException;
  13. import java.net.URISyntaxException;
  14. import java.security.GeneralSecurityException;
  15. import java.util.Arrays;
  16. import java.util.Map;
  17. import java.util.concurrent.ConcurrentHashMap;
  18. import java.util.concurrent.atomic.AtomicInteger;
  19. import java.util.function.Supplier;

  20. import org.apache.sshd.common.AttributeRepository.AttributeKey;
  21. import org.apache.sshd.common.NamedResource;
  22. import org.apache.sshd.common.config.keys.FilePasswordProvider;
  23. import org.apache.sshd.common.session.SessionContext;
  24. import org.eclipse.jgit.annotations.NonNull;
  25. import org.eclipse.jgit.transport.CredentialsProvider;
  26. import org.eclipse.jgit.transport.URIish;
  27. import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;

  28. /**
  29.  * A bridge from sshd's {@link FilePasswordProvider} to our per-session
  30.  * {@link KeyPasswordProvider} API.
  31.  */
  32. public class PasswordProviderWrapper implements FilePasswordProvider {

  33.     private static final AttributeKey<PerSessionState> STATE = new AttributeKey<>();

  34.     private static class PerSessionState {

  35.         Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();

  36.         KeyPasswordProvider delegate;

  37.     }

  38.     private final Supplier<KeyPasswordProvider> factory;

  39.     /**
  40.      * Creates a new {@link PasswordProviderWrapper}.
  41.      *
  42.      * @param factory
  43.      *            to use to create per-session {@link KeyPasswordProvider}s
  44.      */
  45.     public PasswordProviderWrapper(
  46.             @NonNull Supplier<KeyPasswordProvider> factory) {
  47.         this.factory = factory;
  48.     }

  49.     private PerSessionState getState(SessionContext context) {
  50.         PerSessionState state = context.getAttribute(STATE);
  51.         if (state == null) {
  52.             state = new PerSessionState();
  53.             state.delegate = factory.get();
  54.             state.delegate.setAttempts(
  55.                     PASSWORD_PROMPTS.getRequiredDefault().intValue());
  56.             context.setAttribute(STATE, state);
  57.         }
  58.         return state;
  59.     }

  60.     @Override
  61.     public String getPassword(SessionContext session, NamedResource resource,
  62.             int attemptIndex) throws IOException {
  63.         String key = resource.getName();
  64.         PerSessionState state = getState(session);
  65.         int attempt = state.counts
  66.                 .computeIfAbsent(key, k -> new AtomicInteger()).get();
  67.         char[] passphrase = state.delegate.getPassphrase(toUri(key), attempt);
  68.         if (passphrase == null) {
  69.             return null;
  70.         }
  71.         try {
  72.             return new String(passphrase);
  73.         } finally {
  74.             Arrays.fill(passphrase, '\000');
  75.         }
  76.     }

  77.     @Override
  78.     public ResourceDecodeResult handleDecodeAttemptResult(
  79.             SessionContext session, NamedResource resource, int retryIndex,
  80.             String password, Exception err)
  81.             throws IOException, GeneralSecurityException {
  82.         String key = resource.getName();
  83.         PerSessionState state = getState(session);
  84.         AtomicInteger count = state.counts.get(key);
  85.         int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
  86.         ResourceDecodeResult result = null;
  87.         try {
  88.             if (state.delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
  89.                 result = ResourceDecodeResult.RETRY;
  90.             } else {
  91.                 result = ResourceDecodeResult.TERMINATE;
  92.             }
  93.         } finally {
  94.             if (result != ResourceDecodeResult.RETRY) {
  95.                 state.counts.remove(key);
  96.             }
  97.         }
  98.         return result;
  99.     }

  100.     /**
  101.      * Creates a {@link URIish} from a given string. The
  102.      * {@link CredentialsProvider} uses uris as resource identifications.
  103.      *
  104.      * @param resourceKey
  105.      *            to convert
  106.      * @return the uri
  107.      */
  108.     private URIish toUri(String resourceKey) {
  109.         try {
  110.             return new URIish(resourceKey);
  111.         } catch (URISyntaxException e) {
  112.             return new URIish().setPath(resourceKey); // Doesn't check!!
  113.         }
  114.     }

  115. }