View Javadoc
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 }