View Javadoc
1   /*
2    * Copyright (C) 2015, Andrei Pozolotin. 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  
11  package org.eclipse.jgit.transport;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListPBE;
15  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans;
16  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete;
17  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.permitLongTests;
18  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.policySetup;
19  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.product;
20  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.proxySetup;
21  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.publicAddress;
22  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.reportPolicy;
23  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.securityProviderName;
24  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.textWrite;
25  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.transferStream;
26  import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.verifyFileContent;
27  import static org.junit.Assert.assertEquals;
28  import static org.junit.Assert.assertFalse;
29  import static org.junit.Assert.assertNotNull;
30  import static org.junit.Assert.assertTrue;
31  import static org.junit.Assume.assumeTrue;
32  
33  import java.io.BufferedReader;
34  import java.io.ByteArrayInputStream;
35  import java.io.ByteArrayOutputStream;
36  import java.io.File;
37  import java.io.FileInputStream;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.io.OutputStream;
42  import java.io.PrintWriter;
43  import java.net.SocketTimeoutException;
44  import java.net.URL;
45  import java.net.URLConnection;
46  import java.net.UnknownHostException;
47  import java.nio.file.Files;
48  import java.security.GeneralSecurityException;
49  import java.security.Provider;
50  import java.security.Security;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.List;
54  import java.util.Locale;
55  import java.util.Properties;
56  import java.util.Set;
57  import java.util.TreeSet;
58  import java.util.UUID;
59  
60  import javax.crypto.SecretKeyFactory;
61  
62  import org.eclipse.jgit.api.Git;
63  import org.eclipse.jgit.lib.StoredConfig;
64  import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
65  import org.eclipse.jgit.util.FileUtils;
66  import org.junit.After;
67  import org.junit.AfterClass;
68  import org.junit.Before;
69  import org.junit.BeforeClass;
70  import org.junit.FixMethodOrder;
71  import org.junit.Test;
72  import org.junit.runner.RunWith;
73  import org.junit.runners.MethodSorters;
74  import org.junit.runners.Parameterized;
75  import org.junit.runners.Parameterized.Parameters;
76  import org.junit.runners.Suite;
77  import org.slf4j.Logger;
78  import org.slf4j.LoggerFactory;
79  
80  /**
81   * Amazon S3 encryption pipeline test.
82   *
83   * See {@link AmazonS3} {@link WalkEncryption}
84   *
85   * Note: CI server must provide amazon credentials (access key, secret key,
86   * bucket name) via one of methods available in {@link Names}.
87   *
88   * Note: long running tests are activated by Maven profile "test.long". There is
89   * also a separate Eclipse m2e launcher for that. See 'pom.xml' and
90   * 'WalkEncryptionTest.launch'.
91   */
92  @RunWith(Suite.class)
93  @Suite.SuiteClasses({ //
94  		WalkEncryptionTest.Required.class, //
95  		WalkEncryptionTest.MinimalSet.class, //
96  		WalkEncryptionTest.TestablePBE.class, //
97  		WalkEncryptionTest.TestableTransformation.class, //
98  })
99  public class WalkEncryptionTest {
100 
101 	/**
102 	 * Logger setup: ${project_loc}/tst-rsrc/log4j.properties
103 	 */
104 	static final Logger logger = LoggerFactory.getLogger(WalkEncryptionTest.class);
105 
106 	/**
107 	 * Property names used in test session.
108 	 */
109 	interface Names {
110 
111 		// Names of discovered test properties.
112 
113 		String TEST_BUCKET = "test.bucket";
114 
115 		// Names of test environment variables for CI.
116 
117 		String ENV_ACCESS_KEY = "JGIT_S3_ACCESS_KEY";
118 
119 		String ENV_SECRET_KEY = "JGIT_S3_SECRET_KEY";
120 
121 		String ENV_BUCKET_NAME = "JGIT_S3_BUCKET_NAME";
122 
123 		// Name of test environment variable file path for CI.
124 
125 		String ENV_CONFIG_FILE = "JGIT_S3_CONFIG_FILE";
126 
127 		// Names of test system properties for CI.
128 
129 		String SYS_ACCESS_KEY = "jgit.s3.access.key";
130 
131 		String SYS_SECRET_KEY = "jgit.s3.secret.key";
132 
133 		String SYS_BUCKET_NAME = "jgit.s3.bucket.name";
134 
135 		// Name of test system property file path for CI.
136 		String SYS_CONFIG_FILE = "jgit.s3.config.file";
137 
138 		// Hard coded name of test properties file for CI.
139 		// File format follows AmazonS3.Keys:
140 		// #
141 		// # Required entries:
142 		// #
143 		// accesskey = your-amazon-access-key # default AmazonS3.Keys
144 		// secretkey = your-amazon-secret-key # default AmazonS3.Keys
145 		// test.bucket = your-bucket-for-testing # custom name, for this test
146 		String CONFIG_FILE = "jgit-s3-config.properties";
147 
148 		// Test properties file in [user home] of CI.
149 		String HOME_CONFIG_FILE = System.getProperty("user.home")
150 				+ File.separator + CONFIG_FILE;
151 
152 		// Test properties file in [project work directory] of CI.
153 		String WORK_CONFIG_FILE = System.getProperty("user.dir")
154 				+ File.separator + CONFIG_FILE;
155 
156 		// Test properties file in [project test source directory] of CI.
157 		String TEST_CONFIG_FILE = System.getProperty("user.dir")
158 				+ File.separator + "tst-rsrc" + File.separator + CONFIG_FILE;
159 
160 	}
161 
162 	/**
163 	 * Find test properties from various sources in order of priority.
164 	 */
165 	static class Props implements WalkEncryptionTest.Names, AmazonS3.Keys {
166 
167 		static boolean haveEnvVar(String name) {
168 			return System.getenv(name) != null;
169 		}
170 
171 		static boolean haveEnvVarFile(String name) {
172 			return haveEnvVar(name) && new File(name).exists();
173 		}
174 
175 		static boolean haveSysProp(String name) {
176 			return System.getProperty(name) != null;
177 		}
178 
179 		static boolean haveSysPropFile(String name) {
180 			return haveSysProp(name) && new File(name).exists();
181 		}
182 
183 		static void loadEnvVar(String source, String target, Properties props) {
184 			props.put(target, System.getenv(source));
185 		}
186 
187 		static void loadSysProp(String source, String target,
188 				Properties props) {
189 			props.put(target, System.getProperty(source));
190 		}
191 
192 		static boolean haveProp(String name, Properties props) {
193 			return props.containsKey(name);
194 		}
195 
196 		static boolean checkTestProps(Properties props) {
197 			return haveProp(ACCESS_KEY, props) && haveProp(SECRET_KEY, props)
198 					&& haveProp(TEST_BUCKET, props);
199 		}
200 
201 		static Properties fromEnvVars() {
202 			if (haveEnvVar(ENV_ACCESS_KEY) && haveEnvVar(ENV_SECRET_KEY)
203 					&& haveEnvVar(ENV_BUCKET_NAME)) {
204 				Properties props = new Properties();
205 				loadEnvVar(ENV_ACCESS_KEY, ACCESS_KEY, props);
206 				loadEnvVar(ENV_SECRET_KEY, SECRET_KEY, props);
207 				loadEnvVar(ENV_BUCKET_NAME, TEST_BUCKET, props);
208 				return props;
209 			}
210 			return null;
211 		}
212 
213 		static Properties fromEnvFile() throws Exception {
214 			if (haveEnvVarFile(ENV_CONFIG_FILE)) {
215 				Properties props = new Properties();
216 				props.load(new FileInputStream(ENV_CONFIG_FILE));
217 				if (checkTestProps(props)) {
218 					return props;
219 				}
220 				throw new Error("Environment config file is incomplete.");
221 			}
222 			return null;
223 		}
224 
225 		static Properties fromSysProps() {
226 			if (haveSysProp(SYS_ACCESS_KEY) && haveSysProp(SYS_SECRET_KEY)
227 					&& haveSysProp(SYS_BUCKET_NAME)) {
228 				Properties props = new Properties();
229 				loadSysProp(SYS_ACCESS_KEY, ACCESS_KEY, props);
230 				loadSysProp(SYS_SECRET_KEY, SECRET_KEY, props);
231 				loadSysProp(SYS_BUCKET_NAME, TEST_BUCKET, props);
232 				return props;
233 			}
234 			return null;
235 		}
236 
237 		static Properties fromSysFile() throws Exception {
238 			if (haveSysPropFile(SYS_CONFIG_FILE)) {
239 				Properties props = new Properties();
240 				props.load(new FileInputStream(SYS_CONFIG_FILE));
241 				if (checkTestProps(props)) {
242 					return props;
243 				}
244 				throw new Error("System props config file is incomplete.");
245 			}
246 			return null;
247 		}
248 
249 		static Properties fromConfigFile(String path) throws Exception {
250 			File file = new File(path);
251 			if (file.exists()) {
252 				Properties props = new Properties();
253 				props.load(new FileInputStream(file));
254 				if (checkTestProps(props)) {
255 					return props;
256 				}
257 				throw new Error("Props config file is incomplete: " + path);
258 			}
259 			return null;
260 		}
261 
262 		/**
263 		 * Find test properties from various sources in order of priority.
264 		 *
265 		 * @return result
266 		 * @throws Exception
267 		 */
268 		static Properties discover() throws Exception {
269 			Properties props;
270 			if ((props = fromEnvVars()) != null) {
271 				logger.debug(
272 						"Using test properties from environment variables.");
273 				return props;
274 			}
275 			if ((props = fromEnvFile()) != null) {
276 				logger.debug(
277 						"Using test properties from environment variable config file.");
278 				return props;
279 			}
280 			if ((props = fromSysProps()) != null) {
281 				logger.debug("Using test properties from system properties.");
282 				return props;
283 			}
284 			if ((props = fromSysFile()) != null) {
285 				logger.debug(
286 						"Using test properties from system property config file.");
287 				return props;
288 			}
289 			if ((props = fromConfigFile(HOME_CONFIG_FILE)) != null) {
290 				logger.debug(
291 						"Using test properties from hard coded ${user.home} file.");
292 				return props;
293 			}
294 			if ((props = fromConfigFile(WORK_CONFIG_FILE)) != null) {
295 				logger.debug(
296 						"Using test properties from hard coded ${user.dir} file.");
297 				return props;
298 			}
299 			if ((props = fromConfigFile(TEST_CONFIG_FILE)) != null) {
300 				logger.debug(
301 						"Using test properties from hard coded ${project.source} file.");
302 				return props;
303 			}
304 			throw new Error("Can not load test properties form any source.");
305 		}
306 
307 	}
308 
309 	/**
310 	 * Collection of test utility methods.
311 	 */
312 	static class Util {
313 
314 		/**
315 		 * Read UTF-8 encoded text file into string.
316 		 *
317 		 * @param file
318 		 * @return result
319 		 * @throws Exception
320 		 */
321 		static String textRead(File file) throws Exception {
322 			return new String(Files.readAllBytes(file.toPath()), UTF_8);
323 		}
324 
325 		/**
326 		 * Write string into UTF-8 encoded file.
327 		 *
328 		 * @param file
329 		 * @param text
330 		 * @throws Exception
331 		 */
332 		static void textWrite(File file, String text) throws Exception {
333 			Files.write(file.toPath(), text.getBytes(UTF_8));
334 		}
335 
336 		static void verifyFileContent(File fileOne, File fileTwo)
337 				throws Exception {
338 			assertTrue(fileOne.length() > 0);
339 			assertTrue(fileTwo.length() > 0);
340 			String textOne = textRead(fileOne);
341 			String textTwo = textRead(fileTwo);
342 			assertEquals(textOne, textTwo);
343 		}
344 
345 		/**
346 		 * Create local folder.
347 		 *
348 		 * @param folder
349 		 * @throws Exception
350 		 */
351 		static void folderCreate(String folder) throws Exception {
352 			File path = new File(folder);
353 			assertTrue(path.mkdirs());
354 		}
355 
356 		/**
357 		 * Delete local folder.
358 		 *
359 		 * @param folder
360 		 * @throws Exception
361 		 */
362 		static void folderDelete(String folder) throws Exception {
363 			File path = new File(folder);
364 			FileUtils.delete(path,
365 					FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
366 		}
367 
368 		/**
369 		 * Discover public address of CI server.
370 		 *
371 		 * @return result
372 		 * @throws Exception
373 		 */
374 		static String publicAddress() throws Exception {
375 			try {
376 				String service = "http://checkip.amazonaws.com";
377 				URL url = new URL(service);
378 				URLConnection c = url.openConnection();
379 				c.setConnectTimeout(500);
380 				c.setReadTimeout(500);
381 				try (BufferedReader reader = new BufferedReader(
382 						new InputStreamReader(c.getInputStream(), UTF_8))) {
383 					return reader.readLine();
384 				}
385 			} catch (UnknownHostException | SocketTimeoutException e) {
386 				return "Can't reach http://checkip.amazonaws.com to"
387 						+ " determine public address";
388 			}
389 		}
390 
391 		/**
392 		 * Discover Password-Based Encryption (PBE) engines providing both
393 		 * [SecretKeyFactory] and [AlgorithmParameters].
394 		 *
395 		 * @return result
396 		 */
397 		// https://www.bouncycastle.org/specifications.html
398 		// https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
399 		static List<String> cryptoCipherListPBE() {
400 			return cryptoCipherList(WalkEncryption.Vals.REGEX_PBE);
401 		}
402 
403 		// TODO returns inconsistent list.
404 		static List<String> cryptoCipherListTrans() {
405 			return cryptoCipherList(WalkEncryption.Vals.REGEX_TRANS);
406 		}
407 
408 		static String securityProviderName(String algorithm) throws Exception {
409 			return SecretKeyFactory.getInstance(algorithm).getProvider()
410 					.getName();
411 		}
412 
413 		static List<String> cryptoCipherList(String regex) {
414 			Set<String> source = Security.getAlgorithms("Cipher");
415 			Set<String> target = new TreeSet<>();
416 			for (String algo : source) {
417 				algo = algo.toUpperCase(Locale.ROOT);
418 				if (algo.matches(regex)) {
419 					target.add(algo);
420 				}
421 			}
422 			return new ArrayList<>(target);
423 		}
424 
425 		/**
426 		 * Stream copy.
427 		 *
428 		 * @param from
429 		 * @param into
430 		 * @return count
431 		 * @throws IOException
432 		 */
433 		static long transferStream(InputStream from, OutputStream into)
434 				throws IOException {
435 			byte[] array = new byte[1 * 1024];
436 			long total = 0;
437 			while (true) {
438 				int count = from.read(array);
439 				if (count == -1) {
440 					break;
441 				}
442 				into.write(array, 0, count);
443 				total += count;
444 			}
445 			return total;
446 		}
447 
448 		/**
449 		 * Setup proxy during CI build.
450 		 *
451 		 * @throws Exception
452 		 */
453 		// https://wiki.eclipse.org/Hudson#Accessing_the_Internet_using_Proxy
454 		// http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html
455 		static void proxySetup() throws Exception {
456 			String keyNoProxy = "no_proxy";
457 			String keyHttpProxy = "http_proxy";
458 			String keyHttpsProxy = "https_proxy";
459 
460 			String no_proxy = System.getProperty(keyNoProxy,
461 					System.getenv(keyNoProxy));
462 			if (no_proxy != null) {
463 				System.setProperty("http.nonProxyHosts", no_proxy);
464 				logger.info("Proxy NOT: " + no_proxy);
465 			}
466 
467 			String http_proxy = System.getProperty(keyHttpProxy,
468 					System.getenv(keyHttpProxy));
469 			if (http_proxy != null) {
470 				URL url = new URL(http_proxy);
471 				System.setProperty("http.proxyHost", url.getHost());
472 				System.setProperty("http.proxyPort", "" + url.getPort());
473 				logger.info("Proxy HTTP: " + http_proxy);
474 			}
475 
476 			String https_proxy = System.getProperty(keyHttpsProxy,
477 					System.getenv(keyHttpsProxy));
478 			if (https_proxy != null) {
479 				URL url = new URL(https_proxy);
480 				System.setProperty("https.proxyHost", url.getHost());
481 				System.setProperty("https.proxyPort", "" + url.getPort());
482 				logger.info("Proxy HTTPS: " + https_proxy);
483 			}
484 
485 			if (no_proxy == null && http_proxy == null && https_proxy == null) {
486 				logger.info("Proxy not used.");
487 			}
488 
489 		}
490 
491 		/**
492 		 * Permit long tests on CI or with manual activation.
493 		 *
494 		 * @return result
495 		 */
496 		static boolean permitLongTests() {
497 			return isBuildCI() || isProfileActive();
498 		}
499 
500 		/**
501 		 * Using Maven profile activation, see pom.xml
502 		 *
503 		 * @return result
504 		 */
505 		static boolean isProfileActive() {
506 			return Boolean.parseBoolean(System.getProperty("jgit.test.long"));
507 		}
508 
509 		/**
510 		 * Detect if build is running on CI.
511 		 *
512 		 * @return result
513 		 */
514 		static boolean isBuildCI() {
515 			return System.getenv("HUDSON_HOME") != null;
516 		}
517 
518 		/**
519 		 * Setup JCE security policy restrictions. Can remove restrictions when
520 		 * restrictions are present, but can not impose them when restrictions
521 		 * are missing.
522 		 *
523 		 * @param restrictedOn
524 		 */
525 		// http://www.docjar.com/html/api/javax/crypto/JceSecurity.java.html
526 		static void policySetup(boolean restrictedOn) {
527 			try {
528 				java.lang.reflect.Field isRestricted = Class
529 						.forName("javax.crypto.JceSecurity")
530 						.getDeclaredField("isRestricted");
531 				isRestricted.setAccessible(true);
532 				isRestricted.set(null, Boolean.valueOf(restrictedOn));
533 			} catch (Throwable e) {
534 				logger.info(
535 						"Could not setup JCE security policy restrictions.");
536 			}
537 		}
538 
539 		static void reportPolicy() {
540 			try {
541 				java.lang.reflect.Field isRestricted = Class
542 						.forName("javax.crypto.JceSecurity")
543 						.getDeclaredField("isRestricted");
544 				isRestricted.setAccessible(true);
545 				logger.info("JCE security policy restricted="
546 						+ isRestricted.get(null));
547 			} catch (Throwable e) {
548 				logger.info(
549 						"Could not report JCE security policy restrictions.");
550 			}
551 		}
552 
553 		static List<Object[]> product(List<String> one, List<String> two) {
554 			List<Object[]> result = new ArrayList<>();
555 			for (String s1 : one) {
556 				for (String s2 : two) {
557 					result.add(new Object[] { s1, s2 });
558 				}
559 			}
560 			return result;
561 		}
562 
563 	}
564 
565 	/**
566 	 * Common base for encryption tests.
567 	 */
568 	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
569 	public abstract static class Base extends SampleDataRepositoryTestCase {
570 
571 		/**
572 		 * S3 URI user used by JGIT to discover connection configuration file.
573 		 */
574 		static final String JGIT_USER = "tester-" + System.currentTimeMillis();
575 
576 		/**
577 		 * S3 content encoding password used for this test session.
578 		 */
579 		static final String JGIT_PASS = "secret-" + System.currentTimeMillis();
580 
581 		/**
582 		 * S3 repository configuration file expected by {@link AmazonS3}.
583 		 */
584 		static final String JGIT_CONF_FILE = System.getProperty("user.home")
585 				+ "/" + JGIT_USER;
586 
587 		/**
588 		 * Name representing remote or local JGIT repository.
589 		 */
590 		static final String JGIT_REPO_DIR = JGIT_USER + ".jgit";
591 
592 		/**
593 		 * Local JGIT repository for this test session.
594 		 */
595 		static final String JGIT_LOCAL_DIR = System.getProperty("user.dir")
596 				+ "/target/" + JGIT_REPO_DIR;
597 
598 		/**
599 		 * Remote JGIT repository for this test session.
600 		 */
601 		static final String JGIT_REMOTE_DIR = JGIT_REPO_DIR;
602 
603 		/**
604 		 * Generate JGIT S3 connection configuration file.
605 		 *
606 		 * @param algorithm
607 		 * @throws Exception
608 		 */
609 		static void configCreate(String algorithm) throws Exception {
610 			Properties props = Props.discover();
611 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
612 			props.put(AmazonS3.Keys.CRYPTO_ALG, algorithm);
613 			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE,
614 					UTF_8.name())) {
615 				props.store(writer, "JGIT S3 connection configuration file.");
616 			}
617 		}
618 
619 		/**
620 		 * Generate JGIT S3 connection configuration file.
621 		 *
622 		 * @param source
623 		 * @throws Exception
624 		 */
625 		static void configCreate(Properties source) throws Exception {
626 			Properties target = Props.discover();
627 			target.putAll(source);
628 			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE,
629 					UTF_8.name())) {
630 				target.store(writer, "JGIT S3 connection configuration file.");
631 			}
632 		}
633 
634 		/**
635 		 * Remove JGIT connection configuration file.
636 		 *
637 		 * @throws Exception
638 		 */
639 		static void configDelete() throws Exception {
640 			File path = new File(JGIT_CONF_FILE);
641 			FileUtils.delete(path, FileUtils.SKIP_MISSING);
642 		}
643 
644 		/**
645 		 * Generate remote URI for the test session.
646 		 *
647 		 * @return result
648 		 * @throws Exception
649 		 */
650 		static String amazonURI() throws Exception {
651 			Properties props = Props.discover();
652 			String bucket = props.getProperty(Names.TEST_BUCKET);
653 			assertNotNull(bucket);
654 			return TransportAmazonS3.S3_SCHEME + "://" + JGIT_USER + "@"
655 					+ bucket + "/" + JGIT_REPO_DIR;
656 		}
657 
658 		/**
659 		 * Create S3 repository folder.
660 		 *
661 		 * @throws Exception
662 		 */
663 		static void remoteCreate() throws Exception {
664 			Properties props = Props.discover();
665 			props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption.
666 			String bucket = props.getProperty(Names.TEST_BUCKET);
667 			AmazonS3 s3 = new AmazonS3(props);
668 			String path = JGIT_REMOTE_DIR + "/";
669 			s3.put(bucket, path, new byte[0]);
670 			logger.debug("remote create: " + JGIT_REMOTE_DIR);
671 		}
672 
673 		/**
674 		 * Delete S3 repository folder.
675 		 *
676 		 * @throws Exception
677 		 */
678 		static void remoteDelete() throws Exception {
679 			Properties props = Props.discover();
680 			props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption.
681 			String bucket = props.getProperty(Names.TEST_BUCKET);
682 			AmazonS3 s3 = new AmazonS3(props);
683 			List<String> list = s3.list(bucket, JGIT_REMOTE_DIR);
684 			for (String path : list) {
685 				path = JGIT_REMOTE_DIR + "/" + path;
686 				s3.delete(bucket, path);
687 			}
688 			logger.debug("remote delete: " + JGIT_REMOTE_DIR);
689 		}
690 
691 		/**
692 		 * Verify if we can create/delete remote file.
693 		 *
694 		 * @throws Exception
695 		 */
696 		static void remoteVerify() throws Exception {
697 			Properties props = Props.discover();
698 			String bucket = props.getProperty(Names.TEST_BUCKET);
699 			AmazonS3 s3 = new AmazonS3(props);
700 			String file = JGIT_USER + "-" + UUID.randomUUID().toString();
701 			String path = JGIT_REMOTE_DIR + "/" + file;
702 			s3.put(bucket, path, file.getBytes(UTF_8));
703 			s3.delete(bucket, path);
704 		}
705 
706 		/**
707 		 * Verify if any security provider published the algorithm.
708 		 *
709 		 * @param algorithm
710 		 * @return result
711 		 */
712 		static boolean isAlgorithmPresent(String algorithm) {
713 			Set<String> cipherSet = Security.getAlgorithms("Cipher");
714 			for (String source : cipherSet) {
715 				// Standard names are not case-sensitive.
716 				// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
717 				String target = algorithm.toUpperCase(Locale.ROOT);
718 				if (source.equalsIgnoreCase(target)) {
719 					return true;
720 				}
721 			}
722 			return false;
723 		}
724 
725 		static boolean isAlgorithmPresent(Properties props) {
726 			String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
727 			String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER,
728 					WalkEncryption.Vals.DEFAULT_VERS);
729 			String cryptoAlgo;
730 			String keyAlgo;
731 			switch (version) {
732 			case WalkEncryption.Vals.DEFAULT_VERS:
733 			case WalkEncryption.JGitV1.VERSION:
734 				cryptoAlgo = profile;
735 				keyAlgo = profile;
736 				break;
737 			case WalkEncryption.JGitV2.VERSION:
738 				cryptoAlgo = props
739 						.getProperty(profile + WalkEncryption.Keys.X_ALGO);
740 				keyAlgo = props
741 						.getProperty(profile + WalkEncryption.Keys.X_KEY_ALGO);
742 				break;
743 			default:
744 				return false;
745 			}
746 			try {
747 				InsecureCipherFactory.create(cryptoAlgo);
748 				SecretKeyFactory.getInstance(keyAlgo);
749 				return true;
750 			} catch (Throwable e) {
751 				return false;
752 			}
753 		}
754 
755 		/**
756 		 * Verify if JRE security policy allows the algorithm.
757 		 *
758 		 * @param algorithm
759 		 * @return result
760 		 */
761 		static boolean isAlgorithmAllowed(String algorithm) {
762 			try {
763 				WalkEncryption crypto = new WalkEncryption.JetS3tV2(
764 						algorithm, JGIT_PASS);
765 				verifyCrypto(crypto);
766 				return true;
767 			} catch (IOException e) {
768 				return false; // Encryption failure.
769 			} catch (GeneralSecurityException e) {
770 				throw new Error(e); // Construction failure.
771 			}
772 		}
773 
774 		static boolean isAlgorithmAllowed(Properties props) {
775 			try {
776 				WalkEncryption.instance(props);
777 				return true;
778 			} catch (GeneralSecurityException e) {
779 				return false;
780 			}
781 		}
782 
783 		/**
784 		 * Verify round trip encryption.
785 		 *
786 		 * @param crypto
787 		 * @throws IOException
788 		 */
789 		static void verifyCrypto(WalkEncryption crypto) throws IOException {
790 			String charset = "UTF-8";
791 			String sourceText = "secret-message Свобода 老子";
792 			String targetText;
793 			byte[] cipherText;
794 			{
795 				byte[] origin = sourceText.getBytes(charset);
796 				ByteArrayOutputStream target = new ByteArrayOutputStream();
797 				try (OutputStream source = crypto.encrypt(target)) {
798 					source.write(origin);
799 					source.flush();
800 				}
801 				cipherText = target.toByteArray();
802 			}
803 			{
804 				InputStream source = new ByteArrayInputStream(cipherText);
805 				InputStream target = crypto.decrypt(source);
806 				ByteArrayOutputStream result = new ByteArrayOutputStream();
807 				transferStream(target, result);
808 				targetText = result.toString(charset);
809 			}
810 			assertEquals(sourceText, targetText);
811 		}
812 
813 		/**
814 		 * Algorithm is testable when it is present and allowed by policy.
815 		 *
816 		 * @param algorithm
817 		 * @return result
818 		 */
819 		static boolean isAlgorithmTestable(String algorithm) {
820 			return isAlgorithmPresent(algorithm)
821 					&& isAlgorithmAllowed(algorithm);
822 		}
823 
824 		static boolean isAlgorithmTestable(Properties props) {
825 			return isAlgorithmPresent(props) && isAlgorithmAllowed(props);
826 		}
827 
828 		/**
829 		 * Log algorithm, provider, testability.
830 		 *
831 		 * @param algorithm
832 		 * @throws Exception
833 		 */
834 		static void reportAlgorithmStatus(String algorithm) throws Exception {
835 			final boolean present = isAlgorithmPresent(algorithm);
836 			final boolean allowed = present && isAlgorithmAllowed(algorithm);
837 			final String provider = present ? securityProviderName(algorithm)
838 					: "N/A";
839 			String status = "Algorithm: " + algorithm + " @ " + provider + "; "
840 					+ "present/allowed : " + present + "/" + allowed;
841 			if (allowed) {
842 				logger.info("Testing " + status);
843 			} else {
844 				logger.warn("Missing " + status);
845 			}
846 		}
847 
848 		static void reportAlgorithmStatus(Properties props) throws Exception {
849 			final boolean present = isAlgorithmPresent(props);
850 			final boolean allowed = present && isAlgorithmAllowed(props);
851 
852 			String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
853 			String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER);
854 
855 			StringBuilder status = new StringBuilder();
856 			status.append(" Version: " + version);
857 			status.append(" Profile: " + profile);
858 			status.append(" Present: " + present);
859 			status.append(" Allowed: " + allowed);
860 
861 			if (allowed) {
862 				logger.info("Testing " + status);
863 			} else {
864 				logger.warn("Missing " + status);
865 			}
866 		}
867 
868 		/**
869 		 * Verify if we can perform remote tests.
870 		 *
871 		 * @return result
872 		 */
873 		static boolean isTestConfigPresent() {
874 			try {
875 				Props.discover();
876 				return true;
877 			} catch (Throwable e) {
878 				return false;
879 			}
880 		}
881 
882 		static void reportTestConfigPresent() {
883 			if (isTestConfigPresent()) {
884 				logger.info("Amazon S3 test configuration is present.");
885 			} else {
886 				logger.error(
887 						"Amazon S3 test configuration is missing, tests will not run.");
888 			}
889 		}
890 
891 		/**
892 		 * Log public address of CI.
893 		 *
894 		 * @throws Exception
895 		 */
896 		static void reportPublicAddress() throws Exception {
897 			logger.info("Public address: " + publicAddress());
898 		}
899 
900 		/**
901 		 * BouncyCastle provider class.
902 		 *
903 		 * Needs extra dependency, see pom.xml
904 		 */
905 		// http://search.maven.org/#artifactdetails%7Corg.bouncycastle%7Cbcprov-jdk15on%7C1.52%7Cjar
906 		static final String PROVIDER_BC = "org.bouncycastle.jce.provider.BouncyCastleProvider";
907 
908 		/**
909 		 * Load BouncyCastle provider if present.
910 		 */
911 		static void loadBouncyCastle() {
912 			try {
913 				Class<?> provider = Class.forName(PROVIDER_BC);
914 				Provider instance = (Provider) provider
915 						.getConstructor(new Class[] {})
916 						.newInstance(new Object[] {});
917 				Security.addProvider(instance);
918 				logger.info("Loaded " + PROVIDER_BC);
919 			} catch (Throwable e) {
920 				logger.warn("Failed to load " + PROVIDER_BC);
921 			}
922 		}
923 
924 		static void reportLongTests() {
925 			if (permitLongTests()) {
926 				logger.info("Long running tests are enabled.");
927 			} else {
928 				logger.warn("Long running tests are disabled.");
929 			}
930 		}
931 
932 		/**
933 		 * Non-PBE algorithm, for error check.
934 		 */
935 		static final String ALGO_ERROR = "PBKDF2WithHmacSHA1";
936 
937 		/**
938 		 * Default JetS3t algorithm present in most JRE.
939 		 */
940 		static final String ALGO_JETS3T = "PBEWithMD5AndDES";
941 
942 		/**
943 		 * Minimal strength AES based algorithm present in most JRE.
944 		 */
945 		static final String ALGO_MINIMAL_AES = "PBEWithHmacSHA1AndAES_128";
946 
947 		/**
948 		 * Selected non-AES algorithm present in BouncyCastle provider.
949 		 */
950 		static final String ALGO_BOUNCY_CASTLE_CBC = "PBEWithSHAAndTwofish-CBC";
951 
952 		//////////////////////////////////////////////////
953 
954 		@BeforeClass
955 		public static void initialize() throws Exception {
956 			Transport.register(TransportAmazonS3.PROTO_S3);
957 			proxySetup();
958 			reportPolicy();
959 			reportLongTests();
960 			reportPublicAddress();
961 			reportTestConfigPresent();
962 			loadBouncyCastle();
963 			if (isTestConfigPresent()) {
964 				remoteCreate();
965 			}
966 		}
967 
968 		@AfterClass
969 		public static void terminate() throws Exception {
970 			configDelete();
971 			folderDelete(JGIT_LOCAL_DIR);
972 			if (isTestConfigPresent()) {
973 				remoteDelete();
974 			}
975 		}
976 
977 		@Before
978 		@Override
979 		public void setUp() throws Exception {
980 			super.setUp();
981 		}
982 
983 		@After
984 		@Override
985 		public void tearDown() throws Exception {
986 			super.tearDown();
987 		}
988 
989 		/**
990 		 * Optional encrypted amazon remote JGIT life cycle test.
991 		 *
992 		 * @param props
993 		 * @throws Exception
994 		 */
995 		void cryptoTestIfCan(Properties props) throws Exception {
996 			reportAlgorithmStatus(props);
997 			assumeTrue(isTestConfigPresent());
998 			assumeTrue(isAlgorithmTestable(props));
999 			cryptoTest(props);
1000 		}
1001 
1002 		/**
1003 		 * Required encrypted amazon remote JGIT life cycle test.
1004 		 *
1005 		 * @param props
1006 		 * @throws Exception
1007 		 */
1008 		void cryptoTest(Properties props) throws Exception {
1009 
1010 			remoteDelete();
1011 			configCreate(props);
1012 			folderDelete(JGIT_LOCAL_DIR);
1013 
1014 			String uri = amazonURI();
1015 
1016 			// Local repositories.
1017 			File dirOne = db.getWorkTree(); // Provided by setup.
1018 			File dirTwo = new File(JGIT_LOCAL_DIR);
1019 
1020 			// Local verification files.
1021 			String nameStatic = "master.txt"; // Provided by setup.
1022 			String nameDynamic = JGIT_USER + "-" + UUID.randomUUID().toString();
1023 
1024 			String remote = "remote";
1025 			RefSpec specs = new RefSpec("refs/heads/master:refs/heads/master");
1026 
1027 			{ // Push into remote from local one.
1028 
1029 				StoredConfig config = db.getConfig();
1030 				RemoteConfig remoteConfig = new RemoteConfig(config, remote);
1031 				remoteConfig.addURI(new URIish(uri));
1032 				remoteConfig.update(config);
1033 				config.save();
1034 
1035 				try (Git git = Git.open(dirOne)) {
1036 					git.checkout().setName("master").call();
1037 					git.push().setRemote(remote).setRefSpecs(specs).call();
1038 				}
1039 
1040 				File fileStatic = new File(dirOne, nameStatic);
1041 				assertTrue("Provided by setup", fileStatic.exists());
1042 
1043 			}
1044 
1045 			{ // Clone from remote into local two.
1046 
1047 				File fileStatic = new File(dirTwo, nameStatic);
1048 				assertFalse("Not Provided by setup", fileStatic.exists());
1049 
1050 				try (Git git = Git.cloneRepository().setURI(uri)
1051 						.setDirectory(dirTwo).call()) {
1052 					assertTrue("Provided by clone", fileStatic.exists());
1053 				}
1054 
1055 			}
1056 
1057 			{ // Verify static file content.
1058 				File fileOne = new File(dirOne, nameStatic);
1059 				File fileTwo = new File(dirTwo, nameStatic);
1060 				verifyFileContent(fileOne, fileTwo);
1061 			}
1062 
1063 			{ // Verify new file commit and push from local one.
1064 
1065 				File fileDynamic = new File(dirOne, nameDynamic);
1066 				assertFalse("Not Provided by setup", fileDynamic.exists());
1067 				FileUtils.createNewFile(fileDynamic);
1068 				textWrite(fileDynamic, nameDynamic);
1069 				assertTrue("Provided by create", fileDynamic.exists());
1070 				assertTrue("Need content to encrypt", fileDynamic.length() > 0);
1071 
1072 				try (Git git = Git.open(dirOne)) {
1073 					git.add().addFilepattern(nameDynamic).call();
1074 					git.commit().setMessage(nameDynamic).call();
1075 					git.push().setRemote(remote).setRefSpecs(specs).call();
1076 				}
1077 
1078 			}
1079 
1080 			{ // Verify new file pull from remote into local two.
1081 
1082 				File fileDynamic = new File(dirTwo, nameDynamic);
1083 				assertFalse("Not Provided by setup", fileDynamic.exists());
1084 
1085 				try (Git git = Git.open(dirTwo)) {
1086 					git.pull().call();
1087 				}
1088 
1089 				assertTrue("Provided by pull", fileDynamic.exists());
1090 			}
1091 
1092 			{ // Verify dynamic file content.
1093 				File fileOne = new File(dirOne, nameDynamic);
1094 				File fileTwo = new File(dirTwo, nameDynamic);
1095 				verifyFileContent(fileOne, fileTwo);
1096 			}
1097 
1098 		}
1099 
1100 	}
1101 
1102 	/**
1103 	 * Verify prerequisites.
1104 	 */
1105 	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
1106 	public static class Required extends Base {
1107 
1108 		@Test
1109 		public void test_A1_ValidURI() throws Exception {
1110 			assumeTrue(isTestConfigPresent());
1111 			URIish uri = new URIish(amazonURI());
1112 			assertTrue("uri=" + uri, TransportAmazonS3.PROTO_S3.canHandle(uri));
1113 		}
1114 
1115 		@Test(expected = Exception.class)
1116 		public void test_A2_CryptoError() throws Exception {
1117 			assumeTrue(isTestConfigPresent());
1118 			Properties props = new Properties();
1119 			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_ERROR);
1120 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
1121 			cryptoTest(props);
1122 		}
1123 
1124 	}
1125 
1126 	/**
1127 	 * Test minimal set of algorithms.
1128 	 */
1129 	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
1130 	public static class MinimalSet extends Base {
1131 
1132 		@Test
1133 		public void test_V0_Java7_JET() throws Exception {
1134 			assumeTrue(isTestConfigPresent());
1135 			Properties props = new Properties();
1136 			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T);
1137 			// Do not set version.
1138 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
1139 			cryptoTestIfCan(props);
1140 		}
1141 
1142 		@Test
1143 		public void test_V1_Java7_GIT() throws Exception {
1144 			assumeTrue(isTestConfigPresent());
1145 			Properties props = new Properties();
1146 			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T);
1147 			props.put(AmazonS3.Keys.CRYPTO_VER, "1");
1148 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
1149 			cryptoTestIfCan(props);
1150 		}
1151 
1152 		@Test
1153 		public void test_V2_Java7_AES() throws Exception {
1154 			assumeTrue(isTestConfigPresent());
1155 			// String profile = "default";
1156 			String profile = "AES/CBC/PKCS5Padding+PBKDF2WithHmacSHA1";
1157 			Properties props = new Properties();
1158 			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
1159 			props.put(AmazonS3.Keys.CRYPTO_VER, "2");
1160 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
1161 			props.put(profile + WalkEncryption.Keys.X_ALGO, "AES/CBC/PKCS5Padding");
1162 			props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBKDF2WithHmacSHA1");
1163 			props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "128");
1164 			props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000");
1165 			props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c");
1166 			cryptoTestIfCan(props);
1167 		}
1168 
1169 		@Test
1170 		public void test_V2_Java8_PBE_AES() throws Exception {
1171 			assumeTrue(isTestConfigPresent());
1172 			String profile = "PBEWithHmacSHA512AndAES_256";
1173 			Properties props = new Properties();
1174 			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
1175 			props.put(AmazonS3.Keys.CRYPTO_VER, "2");
1176 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
1177 			props.put(profile + WalkEncryption.Keys.X_ALGO, "PBEWithHmacSHA512AndAES_256");
1178 			props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBEWithHmacSHA512AndAES_256");
1179 			props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "256");
1180 			props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000");
1181 			props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c");
1182 			policySetup(false);
1183 			cryptoTestIfCan(props);
1184 		}
1185 
1186 	}
1187 
1188 	/**
1189 	 * Test all present and allowed PBE algorithms.
1190 	 */
1191 	// https://github.com/junit-team/junit/wiki/Parameterized-tests
1192 	@RunWith(Parameterized.class)
1193 	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
1194 	public static class TestablePBE extends Base {
1195 
1196 		@Parameters(name = "Profile: {0}   Version: {1}")
1197 		public static Collection<Object[]> argsList() {
1198 			List<String> algorithmList = new ArrayList<>();
1199 			algorithmList.addAll(cryptoCipherListPBE());
1200 
1201 			List<String> versionList = new ArrayList<>();
1202 			versionList.add("0");
1203 			versionList.add("1");
1204 
1205 			return product(algorithmList, versionList);
1206 		}
1207 
1208 		final String profile;
1209 
1210 		final String version;
1211 
1212 		final String password = JGIT_PASS;
1213 
1214 		public TestablePBE(String profile, String version) {
1215 			this.profile = profile;
1216 			this.version = version;
1217 		}
1218 
1219 		@Test
1220 		public void testCrypto() throws Exception {
1221 			assumeTrue(permitLongTests());
1222 			Properties props = new Properties();
1223 			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
1224 			props.put(AmazonS3.Keys.CRYPTO_VER, version);
1225 			props.put(AmazonS3.Keys.PASSWORD, password);
1226 			cryptoTestIfCan(props);
1227 		}
1228 
1229 	}
1230 
1231 	/**
1232 	 * Test all present and allowed transformation algorithms.
1233 	 */
1234 	// https://github.com/junit-team/junit/wiki/Parameterized-tests
1235 	@RunWith(Parameterized.class)
1236 	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
1237 	public static class TestableTransformation extends Base {
1238 
1239 		@Parameters(name = "Profile: {0}   Version: {1}")
1240 		public static Collection<Object[]> argsList() {
1241 			List<String> algorithmList = new ArrayList<>();
1242 			algorithmList.addAll(cryptoCipherListTrans());
1243 
1244 			List<String> versionList = new ArrayList<>();
1245 			versionList.add("1");
1246 
1247 			return product(algorithmList, versionList);
1248 		}
1249 
1250 		final String profile;
1251 
1252 		final String version;
1253 
1254 		final String password = JGIT_PASS;
1255 
1256 		public TestableTransformation(String profile, String version) {
1257 			this.profile = profile;
1258 			this.version = version;
1259 		}
1260 
1261 		@Test
1262 		public void testCrypto() throws Exception {
1263 			assumeTrue(permitLongTests());
1264 			Properties props = new Properties();
1265 			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
1266 			props.put(AmazonS3.Keys.CRYPTO_VER, version);
1267 			props.put(AmazonS3.Keys.PASSWORD, password);
1268 			cryptoTestIfCan(props);
1269 		}
1270 
1271 	}
1272 
1273 }