]> source.dussan.org Git - jgit.git/blob
2cd066984275613d0f52e08927d57747baad3d03
[jgit.git] /
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
12 import static org.apache.sshd.core.CoreModuleProperties.PASSWORD_PROMPTS;
13
14 import java.io.IOException;
15 import java.net.URISyntaxException;
16 import java.security.GeneralSecurityException;
17 import java.util.Arrays;
18 import java.util.Map;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.atomic.AtomicInteger;
21 import java.util.function.Supplier;
22
23 import org.apache.sshd.common.AttributeRepository.AttributeKey;
24 import org.apache.sshd.common.NamedResource;
25 import org.apache.sshd.common.config.keys.FilePasswordProvider;
26 import org.apache.sshd.common.session.SessionContext;
27 import org.eclipse.jgit.annotations.NonNull;
28 import org.eclipse.jgit.transport.CredentialsProvider;
29 import org.eclipse.jgit.transport.URIish;
30 import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
31
32 /**
33  * A bridge from sshd's {@link FilePasswordProvider} to our per-session
34  * {@link KeyPasswordProvider} API.
35  */
36 public class PasswordProviderWrapper implements FilePasswordProvider {
37
38         private static final AttributeKey<PerSessionState> STATE = new AttributeKey<>();
39
40         private static class PerSessionState {
41
42                 Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
43
44                 KeyPasswordProvider delegate;
45
46         }
47
48         private final Supplier<KeyPasswordProvider> factory;
49
50         /**
51          * Creates a new {@link PasswordProviderWrapper}.
52          *
53          * @param factory
54          *            to use to create per-session {@link KeyPasswordProvider}s
55          */
56         public PasswordProviderWrapper(
57                         @NonNull Supplier<KeyPasswordProvider> factory) {
58                 this.factory = factory;
59         }
60
61         private PerSessionState getState(SessionContext context) {
62                 PerSessionState state = context.getAttribute(STATE);
63                 if (state == null) {
64                         state = new PerSessionState();
65                         state.delegate = factory.get();
66                         state.delegate.setAttempts(
67                                         PASSWORD_PROMPTS.getRequiredDefault().intValue());
68                         context.setAttribute(STATE, state);
69                 }
70                 return state;
71         }
72
73         @Override
74         public String getPassword(SessionContext session, NamedResource resource,
75                         int attemptIndex) throws IOException {
76                 String key = resource.getName();
77                 PerSessionState state = getState(session);
78                 int attempt = state.counts
79                                 .computeIfAbsent(key, k -> new AtomicInteger()).get();
80                 char[] passphrase = state.delegate.getPassphrase(toUri(key), attempt);
81                 if (passphrase == null) {
82                         return null;
83                 }
84                 try {
85                         return new String(passphrase);
86                 } finally {
87                         Arrays.fill(passphrase, '\000');
88                 }
89         }
90
91         @Override
92         public ResourceDecodeResult handleDecodeAttemptResult(
93                         SessionContext session, NamedResource resource, int retryIndex,
94                         String password, Exception err)
95                         throws IOException, GeneralSecurityException {
96                 String key = resource.getName();
97                 PerSessionState state = getState(session);
98                 AtomicInteger count = state.counts.get(key);
99                 int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
100                 ResourceDecodeResult result = null;
101                 try {
102                         if (state.delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
103                                 result = ResourceDecodeResult.RETRY;
104                         } else {
105                                 result = ResourceDecodeResult.TERMINATE;
106                         }
107                 } finally {
108                         if (result != ResourceDecodeResult.RETRY) {
109                                 state.counts.remove(key);
110                         }
111                 }
112                 return result;
113         }
114
115         /**
116          * Creates a {@link URIish} from a given string. The
117          * {@link CredentialsProvider} uses uris as resource identifications.
118          *
119          * @param resourceKey
120          *            to convert
121          * @return the uri
122          */
123         private URIish toUri(String resourceKey) {
124                 try {
125                         return new URIish(resourceKey);
126                 } catch (URISyntaxException e) {
127                         return new URIish().setPath(resourceKey); // Doesn't check!!
128                 }
129         }
130
131 }