View Javadoc
1   /*
2    * Copyright (C) 2018, 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 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  
20  import org.apache.sshd.common.NamedResource;
21  import org.apache.sshd.common.session.SessionContext;
22  import org.eclipse.jgit.annotations.NonNull;
23  import org.eclipse.jgit.transport.CredentialsProvider;
24  import org.eclipse.jgit.transport.URIish;
25  import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
26  
27  /**
28   * A bridge from sshd's {@link RepeatingFilePasswordProvider} to our
29   * {@link KeyPasswordProvider} API.
30   */
31  public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
32  
33  	private final KeyPasswordProvider delegate;
34  
35  	private Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
36  
37  	/**
38  	 * @param delegate
39  	 */
40  	public PasswordProviderWrapper(@NonNull KeyPasswordProvider delegate) {
41  		this.delegate = delegate;
42  	}
43  
44  	@Override
45  	public void setAttempts(int numberOfPasswordPrompts) {
46  		delegate.setAttempts(numberOfPasswordPrompts);
47  	}
48  
49  	@Override
50  	public int getAttempts() {
51  		return delegate.getAttempts();
52  	}
53  
54  	@Override
55  	public String getPassword(SessionContext session, NamedResource resource,
56  			int attemptIndex) throws IOException {
57  		String key = resource.getName();
58  		int attempt = counts
59  				.computeIfAbsent(key, k -> new AtomicInteger()).get();
60  		char[] passphrase = delegate.getPassphrase(toUri(key), attempt);
61  		if (passphrase == null) {
62  			return null;
63  		}
64  		try {
65  			return new String(passphrase);
66  		} finally {
67  			Arrays.fill(passphrase, '\000');
68  		}
69  	}
70  
71  	@Override
72  	public ResourceDecodeResult handleDecodeAttemptResult(
73  			SessionContext session, NamedResource resource, int retryIndex,
74  			String password, Exception err)
75  			throws IOException, GeneralSecurityException {
76  		String key = resource.getName();
77  		AtomicInteger count = counts.get(key);
78  		int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
79  		ResourceDecodeResult result = null;
80  		try {
81  			if (delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
82  				result = ResourceDecodeResult.RETRY;
83  			} else {
84  				result = ResourceDecodeResult.TERMINATE;
85  			}
86  		} finally {
87  			if (result != ResourceDecodeResult.RETRY) {
88  				counts.remove(key);
89  			}
90  		}
91  		return result;
92  	}
93  
94  	/**
95  	 * Creates a {@link URIish} from a given string. The
96  	 * {@link CredentialsProvider} uses uris as resource identifications.
97  	 *
98  	 * @param resourceKey
99  	 *            to convert
100 	 * @return the uri
101 	 */
102 	private URIish toUri(String resourceKey) {
103 		try {
104 			return new URIish(resourceKey);
105 		} catch (URISyntaxException e) {
106 			return new URIish().setPath(resourceKey); // Doesn't check!!
107 		}
108 	}
109 
110 }