77import org .junit .jupiter .api .DisplayNameGenerator .ReplaceUnderscores ;
88import org .junit .jupiter .api .Nested ;
99import org .junit .jupiter .api .Test ;
10+ import org .junit .jupiter .api .io .TempDir ;
1011
1112import java .io .ByteArrayInputStream ;
1213import java .io .IOException ;
1314import java .io .InputStream ;
1415import java .net .ConnectException ;
1516import java .nio .file .Path ;
1617import java .nio .file .Paths ;
17- import java .util .Vector ;
18+ import java .security .*;
19+ import java .security .KeyPair ;
20+ import java .util .*;
1821import java .util .concurrent .atomic .AtomicInteger ;
1922import java .util .concurrent .atomic .AtomicReference ;
2023
2124import static com .github .stefanbirkner .fakesftpserver .lambda .FakeSftpServer .withSftpServer ;
2225import static com .github .stefanbirkner .fishbowl .Fishbowl .exceptionThrownBy ;
2326import static com .github .stefanbirkner .fishbowl .Fishbowl .ignoreException ;
2427import static java .nio .charset .StandardCharsets .UTF_8 ;
28+ import static java .nio .file .Files .createTempFile ;
29+ import static java .nio .file .Files .write ;
30+ import static java .util .Arrays .asList ;
2531import static org .apache .commons .io .IOUtils .toByteArray ;
2632import static org .assertj .core .api .Assertions .assertThat ;
2733import static org .assertj .core .api .Assertions .assertThatThrownBy ;
@@ -92,7 +98,7 @@ void a_client_can_connect_to_the_server_at_a_user_specified_port(
9298 @ Nested
9399 class authentication {
94100 @ Nested
95- class server_without_credentials {
101+ class server_without_credentials_or_SSH_keys {
96102 @ Test
97103 void the_server_accepts_connections_with_password (
98104 ) throws Exception {
@@ -107,6 +113,31 @@ void the_server_accepts_connections_with_password(
107113 }
108114 );
109115 }
116+
117+ @ Test
118+ void the_server_accepts_connections_with_an_SSH_key (
119+ @ TempDir Path tempDir
120+ ) throws Exception {
121+ String anyPrivateKey = fileWithArbitraryPrivateKey (tempDir );
122+ withSftpServer (
123+ server -> {
124+ JSCH .addIdentity (anyPrivateKey );
125+ Session session = createSessionForUser (
126+ "dummy user" ,
127+ server .getPort ()
128+ );
129+ session .connect (TIMEOUT );
130+ }
131+ );
132+ }
133+
134+ private String fileWithArbitraryPrivateKey (
135+ Path directory
136+ ) throws NoSuchAlgorithmException , IOException {
137+ KeyPairGenerator keyPairGenerator = KeyPairGenerator .getInstance ("RSA" );
138+ KeyPair keyPair = keyPairGenerator .generateKeyPair ();
139+ return keyFile (directory , keyPair .getPrivate ());
140+ }
110141 }
111142
112143 @ Nested
@@ -164,6 +195,101 @@ void the_last_password_is_effective_if_addUser_is_called_multiple_times(
164195 }
165196 }
166197
198+ @ Nested
199+ class SSH_key {
200+ @ TempDir
201+ Path tempDir ;
202+
203+ @ Test
204+ public void the_server_accepts_connections_with_correct_private_key (
205+ ) throws Exception {
206+ withSftpServer (
207+ server -> {
208+ PrivateKey privateKey = server .addSshKeyForUser ("dummy user" );
209+ JSCH .addIdentity (
210+ keyFile (tempDir , privateKey )
211+ );
212+ Session session = createSessionForUser (
213+ "dummy user" ,
214+ server .getPort ()
215+ );
216+ session .connect (TIMEOUT );
217+ }
218+ );
219+ }
220+
221+ @ Test
222+ public void the_server_rejects_connections_with_wrong_private_key (
223+ ) throws Exception {
224+ withSftpServer (
225+ server -> {
226+ server .addSshKeyForUser ("dummy user" );
227+ PrivateKey privateKey = server .addSshKeyForUser ("another user" );
228+ JSCH .addIdentity (
229+ keyFile (tempDir , privateKey )
230+ );
231+ Session session = createSessionForUser (
232+ "dummy user" ,
233+ server .getPort ()
234+ );
235+ assertThatThrownBy (() -> session .connect (TIMEOUT ))
236+ .isInstanceOf (Exception .class );
237+ }
238+ );
239+ }
240+
241+ @ Test
242+ public void the_server_rejects_connections_for_user_without_private_key (
243+ ) throws Exception {
244+ withSftpServer (
245+ server -> {
246+ // We need to create an SSH key, because servers without
247+ // registered credentials or SSH keys accept every
248+ // connection.
249+ server .addSshKeyForUser ("another user" );
250+ Session session = createSessionForUser (
251+ "dummy user" ,
252+ server .getPort ()
253+ );
254+ assertThatThrownBy (() -> session .connect (TIMEOUT ))
255+ .isInstanceOf (Exception .class );
256+ }
257+ );
258+ }
259+
260+ @ Test
261+ public void the_latest_key_is_effective_if_multiple_keys_are_created_for_a_user (
262+ ) throws Exception {
263+ withSftpServer (
264+ server -> {
265+ server .addSshKeyForUser ("dummy user" );
266+ PrivateKey privateKey = server .addSshKeyForUser ("dummy user" );
267+ JSCH .addIdentity (
268+ keyFile (tempDir , privateKey )
269+ );
270+ Session session = createSessionForUser (
271+ "dummy user" ,
272+ server .getPort ()
273+ );
274+ session .connect (TIMEOUT );
275+ }
276+ );
277+ }
278+ }
279+
280+ private String keyFile (
281+ Path directory ,
282+ PrivateKey key
283+ ) throws IOException {
284+ Path keyFile = createTempFile (directory , "key_" , ".tmp" );
285+ write (keyFile , asList (
286+ "-----BEGIN PRIVATE KEY-----" ,
287+ Base64 .getMimeEncoder ().encodeToString (key .getEncoded ()),
288+ "-----END PRIVATE KEY-----"
289+ ));
290+ return keyFile .toFile ().getAbsolutePath ();
291+ }
292+
167293 private Session createSessionWithCredentials (
168294 FakeSftpServer server ,
169295 String username ,
@@ -964,10 +1090,18 @@ private static Session createSessionWithCredentials(
9641090 String username ,
9651091 String password ,
9661092 int port
1093+ ) throws JSchException {
1094+ Session session = createSessionForUser (username , port );
1095+ session .setPassword (password );
1096+ return session ;
1097+ }
1098+
1099+ private static Session createSessionForUser (
1100+ String username ,
1101+ int port
9671102 ) throws JSchException {
9681103 Session session = JSCH .getSession (username , "127.0.0.1" , port );
9691104 session .setConfig ("StrictHostKeyChecking" , "no" );
970- session .setPassword (password );
9711105 return session ;
9721106 }
9731107
0 commit comments