View Javadoc
1   /*
2    * Copyright (C) 2019, 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.transport.sshd;
11  
12  import static org.junit.Assert.assertNotNull;
13  
14  import java.io.File;
15  import java.io.IOException;
16  import java.io.UncheckedIOException;
17  import java.net.InetSocketAddress;
18  import java.nio.file.Files;
19  import java.security.GeneralSecurityException;
20  import java.security.KeyPair;
21  import java.security.KeyPairGenerator;
22  import java.security.PublicKey;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.sshd.common.config.keys.KeyUtils;
29  import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
30  import org.apache.sshd.common.session.SessionContext;
31  import org.apache.sshd.common.util.net.SshdSocketAddress;
32  import org.eclipse.jgit.junit.ssh.SshTestHarness;
33  import org.eclipse.jgit.lib.Constants;
34  import org.eclipse.jgit.transport.CredentialsProvider;
35  import org.eclipse.jgit.transport.SshSessionFactory;
36  import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
37  import org.eclipse.jgit.util.FS;
38  import org.junit.After;
39  import org.junit.Test;
40  
41  /**
42   * Test for using the SshdSessionFactory without files in ~/.ssh but with an
43   * in-memory setup.
44   */
45  public class NoFilesSshTest extends SshTestHarness {
46  
47  	private PublicKey testServerKey;
48  
49  	private KeyPair testUserKey;
50  
51  	@Override
52  	protected SshSessionFactory createSessionFactory() {
53  		SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(),
54  				null) {
55  
56  			@Override
57  			protected File getSshConfig(File dir) {
58  				return null;
59  			}
60  
61  			@Override
62  			protected ServerKeyDatabase getServerKeyDatabase(File homeDir,
63  					File dir) {
64  				return new ServerKeyDatabase() {
65  
66  					@Override
67  					public List<PublicKey> lookup(String connectAddress,
68  							InetSocketAddress remoteAddress,
69  							Configuration config) {
70  						return Collections.singletonList(testServerKey);
71  					}
72  
73  					@Override
74  					public boolean accept(String connectAddress,
75  							InetSocketAddress remoteAddress,
76  							PublicKey serverKey, Configuration config,
77  							CredentialsProvider provider) {
78  						return KeyUtils.compareKeys(serverKey, testServerKey);
79  					}
80  
81  				};
82  			}
83  
84  			@Override
85  			protected ConnectorFactory getConnectorFactory() {
86  				// No ssh-agent in tests
87  				return null;
88  			}
89  
90  			@Override
91  			protected Iterable<KeyPair> getDefaultKeys(File dir) {
92  				// This would work for this simple test case:
93  				// return Collections.singletonList(testUserKey);
94  				// But let's see if we can check the host and username that's used.
95  				// For that, we need access to the sshd SessionContext:
96  				return new KeyAuthenticator();
97  			}
98  
99  			@Override
100 			protected String getDefaultPreferredAuthentications() {
101 				return "publickey";
102 			}
103 		};
104 
105 		// The home directory is mocked at this point!
106 		result.setHomeDirectory(FS.DETECTED.userHome());
107 		result.setSshDirectory(sshDir);
108 		return result;
109 	}
110 
111 	private class KeyAuthenticator implements KeyIdentityProvider, Iterable<KeyPair> {
112 
113 		@Override
114 		public Iterator<KeyPair> iterator() {
115 			// Should not be called. The use of the Iterable interface in
116 			// SshdSessionFactory.getDefaultKeys() made sense in sshd 2.0.0,
117 			// but sshd 2.2.0 added the SessionContext, which although good
118 			// (without it we couldn't check here) breaks the Iterable analogy.
119 			// But we're stuck now with that interface for getDefaultKeys, and
120 			// so this override throwing an exception is unfortunately needed.
121 			throw new UnsupportedOperationException();
122 		}
123 
124 		@Override
125 		public Iterable<KeyPair> loadKeys(SessionContext session)
126 				throws IOException, GeneralSecurityException {
127 			if (!TEST_USER.equals(session.getUsername())) {
128 				return Collections.emptyList();
129 			}
130 			SshdSocketAddress remoteAddress = SshdSocketAddress
131 					.toSshdSocketAddress(session.getRemoteAddress());
132 			switch (remoteAddress.getHostName()) {
133 			case "localhost":
134 			case "127.0.0.1":
135 				return Collections.singletonList(testUserKey);
136 			default:
137 				return Collections.emptyList();
138 			}
139 		}
140 	}
141 
142 	@After
143 	public void cleanUp() {
144 		testServerKey = null;
145 		testUserKey = null;
146 	}
147 
148 	@Override
149 	protected void installConfig(String... config) {
150 		File configFile = new File(sshDir, Constants.CONFIG);
151 		if (config != null) {
152 			try {
153 				Files.write(configFile.toPath(), Arrays.asList(config));
154 			} catch (IOException e) {
155 				throw new UncheckedIOException(e);
156 			}
157 		}
158 	}
159 
160 	@Test
161 	public void testCloneWithBuiltInKeys() throws Exception {
162 		// This test should fail unless our in-memory setup is taken: no
163 		// known_hosts file, a config that specifies a non-existing key,
164 		// and the test is using a newly generated KeyPairs anyway.
165 		KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
166 		generator.initialize(2048);
167 		testUserKey = generator.generateKeyPair();
168 		KeyPair hostKey = generator.generateKeyPair();
169 		server.addHostKey(hostKey, true);
170 		testServerKey = hostKey.getPublic();
171 		assertNotNull(testServerKey);
172 		assertNotNull(testUserKey);
173 		server.setTestUserPublicKey(testUserKey.getPublic());
174 		cloneWith(
175 				"ssh://" + TEST_USER + "@localhost:" + testPort
176 						+ "/doesntmatter",
177 				new File(getTemporaryDirectory(), "cloned"), null, //
178 				"Host localhost", //
179 				"IdentityFile "
180 						+ new File(sshDir, "does_not_exist").getAbsolutePath());
181 	}
182 
183 }