You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PasswordProviderWrapper.java 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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. }