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