View Javadoc
1   /*
2    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.io.OutputStream;
49  import java.net.HttpURLConnection;
50  import java.security.AlgorithmParameters;
51  import java.security.GeneralSecurityException;
52  import java.security.spec.AlgorithmParameterSpec;
53  import java.security.spec.KeySpec;
54  import java.text.MessageFormat;
55  import java.util.Properties;
56  import java.util.regex.Matcher;
57  import java.util.regex.Pattern;
58  
59  import javax.crypto.Cipher;
60  import javax.crypto.CipherInputStream;
61  import javax.crypto.CipherOutputStream;
62  import javax.crypto.SecretKey;
63  import javax.crypto.SecretKeyFactory;
64  import javax.crypto.spec.IvParameterSpec;
65  import javax.crypto.spec.PBEKeySpec;
66  import javax.crypto.spec.PBEParameterSpec;
67  import javax.crypto.spec.SecretKeySpec;
68  import javax.xml.bind.DatatypeConverter;
69  
70  import org.eclipse.jgit.internal.JGitText;
71  import org.eclipse.jgit.util.Base64;
72  
73  abstract class WalkEncryption {
74  	static final WalkEncryption NONE = new NoEncryption();
75  
76  	static final String JETS3T_CRYPTO_VER = "jets3t-crypto-ver"; //$NON-NLS-1$
77  
78  	static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$
79  
80  	// Note: encrypt -> request state machine, step 1.
81  	abstract OutputStream encrypt(OutputStream output) throws IOException;
82  
83  	// Note: encrypt -> request state machine, step 2.
84  	abstract void request(HttpURLConnection conn, String prefix) throws IOException;
85  
86  	// Note: validate -> decrypt state machine, step 1.
87  	abstract void validate(HttpURLConnection conn, String prefix) throws IOException;
88  
89  	// Note: validate -> decrypt state machine, step 2.
90  	abstract InputStream decrypt(InputStream input) throws IOException;
91  
92  
93  	// TODO mixed ciphers
94  	// consider permitting mixed ciphers to facilitate algorithm migration
95  	// i.e. user keeps the password, but changes the algorithm
96  	// then existing remote entries will still be readable
97  	protected void validateImpl(final HttpURLConnection u, final String prefix,
98  			final String version, final String name) throws IOException {
99  		String v;
100 
101 		v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER);
102 		if (v == null)
103 			v = ""; //$NON-NLS-1$
104 		if (!version.equals(v))
105 			throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v));
106 
107 		v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG);
108 		if (v == null)
109 			v = ""; //$NON-NLS-1$
110 		// Standard names are not case-sensitive.
111 		// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
112 		if (!name.equalsIgnoreCase(v))
113 			throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v));
114 	}
115 
116 	IOException error(final Throwable why) {
117 		final IOException e;
118 		e = new IOException(MessageFormat.format(JGitText.get().encryptionError, why.getMessage()));
119 		e.initCause(why);
120 		return e;
121 	}
122 
123 	private static class NoEncryption extends WalkEncryption {
124 		@Override
125 		void request(HttpURLConnection u, String prefix) {
126 			// Don't store any request properties.
127 		}
128 
129 		@Override
130 		void validate(final HttpURLConnection u, final String prefix)
131 				throws IOException {
132 			validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
133 		}
134 
135 		@Override
136 		InputStream decrypt(InputStream in) {
137 			return in;
138 		}
139 
140 		@Override
141 		OutputStream encrypt(OutputStream os) {
142 			return os;
143 		}
144 	}
145 
146 	// PBEParameterSpec factory for Java (version <= 7).
147 	// Does not support AlgorithmParameterSpec.
148 	static PBEParameterSpec java7PBEParameterSpec(byte[] salt,
149 			int iterationCount) {
150 		return new PBEParameterSpec(salt, iterationCount);
151 	}
152 
153 	// PBEParameterSpec factory for Java (version >= 8).
154 	// Adds support for AlgorithmParameterSpec.
155 	static PBEParameterSpec java8PBEParameterSpec(byte[] salt,
156 			int iterationCount, AlgorithmParameterSpec paramSpec) {
157 		try {
158 			@SuppressWarnings("boxing")
159 			PBEParameterSpec instance = PBEParameterSpec.class
160 					.getConstructor(byte[].class, int.class,
161 							AlgorithmParameterSpec.class)
162 					.newInstance(salt, iterationCount, paramSpec);
163 			return instance;
164 		} catch (Exception e) {
165 			throw new RuntimeException(e);
166 		}
167 	}
168 
169 	// Current runtime version.
170 	// https://docs.oracle.com/javase/7/docs/technotes/guides/versioning/spec/versioning2.html
171 	static double javaVersion() {
172 		return Double.parseDouble(System.getProperty("java.specification.version")); //$NON-NLS-1$
173 	}
174 
175 	/**
176 	 * JetS3t compatibility reference: <a href=
177 	 * "https://bitbucket.org/jmurty/jets3t/src/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java">
178 	 * EncryptionUtil.java</a>
179 	 * <p>
180 	 * Note: EncryptionUtil is inadequate:
181 	 * <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which
182 	 * "always works", but in JetS3t both encryption and decryption use non-IV
183 	 * aware algorithm parameters for all PBE specs, which breaks in case of AES
184 	 * <li>that means that only non-IV algorithms will work round trip in
185 	 * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC
186 	 * <li>any AES based algorithms such as "PBE...With...And...AES" will not
187 	 * work, since they need proper IV setup
188 	 */
189 	static class JetS3tV2 extends WalkEncryption {
190 
191 		static final String VERSION = "2"; //$NON-NLS-1$
192 
193 		static final String ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$
194 
195 		static final int ITERATIONS = 5000;
196 
197 		static final int KEY_SIZE = 32;
198 
199 		static final byte[] SALT = { //
200 				(byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, //
201 				(byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 //
202 		};
203 
204 		// Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE
205 		static final byte[] ZERO_AES_IV = new byte[16];
206 
207 		private static final String cryptoVer = VERSION;
208 
209 		private final String cryptoAlg;
210 
211 		private final SecretKey secretKey;
212 
213 		private final AlgorithmParameterSpec paramSpec;
214 
215 		JetS3tV2(final String algo, final String key)
216 				throws GeneralSecurityException {
217 			cryptoAlg = algo;
218 
219 			// Verify if cipher is present.
220 			Cipher cipher = Cipher.getInstance(cryptoAlg);
221 
222 			// Standard names are not case-sensitive.
223 			// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
224 			String cryptoName = cryptoAlg.toUpperCase();
225 
226 			if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$
227 				throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE);
228 
229 			PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), SALT, ITERATIONS, KEY_SIZE);
230 			secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec);
231 
232 			// Detect algorithms which require initialization vector.
233 			boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$
234 
235 			// PBEParameterSpec algorithm parameters are supported from Java 8.
236 			boolean isJava8 = javaVersion() >= 1.8;
237 
238 			if (useIV && isJava8) {
239 				// Support IV where possible:
240 				// * since JCE provider uses random IV for PBE/AES
241 				// * and there is no place to store dynamic IV in JetS3t V2
242 				// * we use static IV, and tolerate increased security risk
243 				// TODO back port this change to JetS3t V2
244 				// See:
245 				// https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java
246 				// http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java
247 				IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV);
248 				paramSpec = java8PBEParameterSpec(SALT, ITERATIONS, paramIV);
249 			} else {
250 				// Strict legacy JetS3t V2 compatibility, with no IV support.
251 				paramSpec = java7PBEParameterSpec(SALT, ITERATIONS);
252 			}
253 
254 			// Verify if cipher + key are allowed by policy.
255 			cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
256 			cipher.doFinal();
257 
258 		}
259 
260 		@Override
261 		void request(final HttpURLConnection u, final String prefix) {
262 			u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, cryptoVer);
263 			u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg);
264 		}
265 
266 		@Override
267 		void validate(final HttpURLConnection u, final String prefix)
268 				throws IOException {
269 			validateImpl(u, prefix, cryptoVer, cryptoAlg);
270 		}
271 
272 		@Override
273 		OutputStream encrypt(final OutputStream os) throws IOException {
274 			try {
275 				final Cipher cipher = Cipher.getInstance(cryptoAlg);
276 				cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
277 				return new CipherOutputStream(os, cipher);
278 			} catch (GeneralSecurityException e) {
279 				throw error(e);
280 			}
281 		}
282 
283 		@Override
284 		InputStream decrypt(final InputStream in) throws IOException {
285 			try {
286 				final Cipher cipher = Cipher.getInstance(cryptoAlg);
287 				cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
288 				return new CipherInputStream(in, cipher);
289 			} catch (GeneralSecurityException e) {
290 				throw error(e);
291 			}
292 		}
293 	}
294 
295 	/** Encryption property names. */
296 	interface Keys {
297 		// Remote S3 meta: V1 algorithm name or V2 profile name.
298 		String JGIT_PROFILE = "jgit-crypto-profile"; //$NON-NLS-1$
299 
300 		// Remote S3 meta: JGit encryption implementation version.
301 		String JGIT_VERSION = "jgit-crypto-version"; //$NON-NLS-1$
302 
303 		// Remote S3 meta: base-64 encoded cipher algorithm parameters.
304 		String JGIT_CONTEXT = "jgit-crypto-context"; //$NON-NLS-1$
305 
306 		// Amazon S3 connection configuration file profile property suffixes:
307 		String X_ALGO = ".algo"; //$NON-NLS-1$
308 		String X_KEY_ALGO = ".key.algo"; //$NON-NLS-1$
309 		String X_KEY_SIZE = ".key.size"; //$NON-NLS-1$
310 		String X_KEY_ITER = ".key.iter"; //$NON-NLS-1$
311 		String X_KEY_SALT = ".key.salt"; //$NON-NLS-1$
312 	}
313 
314 	/** Encryption constants and defaults. */
315 	interface Vals {
316 		// Compatibility defaults.
317 		String DEFAULT_VERS = "0"; //$NON-NLS-1$
318 		String DEFAULT_ALGO = JetS3tV2.ALGORITHM;
319 		String DEFAULT_KEY_ALGO = JetS3tV2.ALGORITHM;
320 		String DEFAULT_KEY_SIZE = Integer.toString(JetS3tV2.KEY_SIZE);
321 		String DEFAULT_KEY_ITER = Integer.toString(JetS3tV2.ITERATIONS);
322 		String DEFAULT_KEY_SALT = DatatypeConverter.printHexBinary(JetS3tV2.SALT);
323 
324 		String EMPTY = ""; //$NON-NLS-1$
325 
326 		// Match white space.
327 		String REGEX_WS = "\\s+"; //$NON-NLS-1$
328 
329 		// Match PBE ciphers, i.e: PBEWithMD5AndDES
330 		String REGEX_PBE = "(PBE).*(WITH).+(AND).+"; //$NON-NLS-1$
331 
332 		// Match transformation ciphers, i.e: AES/CBC/PKCS5Padding
333 		String REGEX_TRANS = "(.+)/(.+)/(.+)"; //$NON-NLS-1$
334 	}
335 
336 	static GeneralSecurityException securityError(String message) {
337 		return new GeneralSecurityException(
338 				MessageFormat.format(JGitText.get().encryptionError, message));
339 	}
340 
341 	/**
342 	 * Base implementation of JGit symmetric encryption. Supports V2 properties
343 	 * format.
344 	 */
345 	static abstract class SymmetricEncryption extends WalkEncryption
346 			implements Keys, Vals {
347 
348 		/** Encryption profile, root name of group of related properties. */
349 		final String profile;
350 
351 		/** Encryption version, reflects actual implementation class. */
352 		final String version;
353 
354 		/** Full cipher algorithm name. */
355 		final String cipherAlgo;
356 
357 		/** Cipher algorithm name for parameters lookup. */
358 		final String paramsAlgo;
359 
360 		/** Generated secret key. */
361 		final SecretKey secretKey;
362 
363 		SymmetricEncryption(Properties props) throws GeneralSecurityException {
364 
365 			profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
366 			version = props.getProperty(AmazonS3.Keys.CRYPTO_VER);
367 			String pass = props.getProperty(AmazonS3.Keys.PASSWORD);
368 
369 			cipherAlgo = props.getProperty(profile + X_ALGO, DEFAULT_ALGO);
370 
371 			String keyAlgo = props.getProperty(profile + X_KEY_ALGO, DEFAULT_KEY_ALGO);
372 			String keySize = props.getProperty(profile + X_KEY_SIZE, DEFAULT_KEY_SIZE);
373 			String keyIter = props.getProperty(profile + X_KEY_ITER, DEFAULT_KEY_ITER);
374 			String keySalt = props.getProperty(profile + X_KEY_SALT, DEFAULT_KEY_SALT);
375 
376 			// Verify if cipher is present.
377 			Cipher cipher = Cipher.getInstance(cipherAlgo);
378 
379 			// Verify if key factory is present.
380 			SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo);
381 
382 			final int size;
383 			try {
384 				size = Integer.parseInt(keySize);
385 			} catch (Exception e) {
386 				throw securityError(X_KEY_SIZE + EMPTY + keySize);
387 			}
388 
389 			final int iter;
390 			try {
391 				iter = Integer.parseInt(keyIter);
392 			} catch (Exception e) {
393 				throw securityError(X_KEY_ITER + EMPTY + keyIter);
394 			}
395 
396 			final byte[] salt;
397 			try {
398 				salt = DatatypeConverter
399 						.parseHexBinary(keySalt.replaceAll(REGEX_WS, EMPTY));
400 			} catch (Exception e) {
401 				throw securityError(X_KEY_SALT + EMPTY + keySalt);
402 			}
403 
404 			KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iter, size);
405 
406 			SecretKey keyBase = factory.generateSecret(keySpec);
407 
408 			String name = cipherAlgo.toUpperCase();
409 			Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name);
410 			Matcher matcherTrans = Pattern.compile(REGEX_TRANS).matcher(name);
411 			if (matcherPBE.matches()) {
412 				paramsAlgo = cipherAlgo;
413 				secretKey = keyBase;
414 			} else if (matcherTrans.find()) {
415 				paramsAlgo = matcherTrans.group(1);
416 				secretKey = new SecretKeySpec(keyBase.getEncoded(), paramsAlgo);
417 			} else {
418 				throw new GeneralSecurityException(MessageFormat.format(
419 						JGitText.get().unsupportedEncryptionAlgorithm,
420 						cipherAlgo));
421 			}
422 
423 			// Verify if cipher + key are allowed by policy.
424 			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
425 			cipher.doFinal();
426 
427 		}
428 
429 		// Shared state encrypt -> request.
430 		volatile String context;
431 
432 		@Override
433 		OutputStream encrypt(OutputStream output) throws IOException {
434 			try {
435 				Cipher cipher = Cipher.getInstance(cipherAlgo);
436 				cipher.init(Cipher.ENCRYPT_MODE, secretKey);
437 				AlgorithmParameters params = cipher.getParameters();
438 				if (params == null) {
439 					context = EMPTY;
440 				} else {
441 					context = Base64.encodeBytes(params.getEncoded());
442 				}
443 				return new CipherOutputStream(output, cipher);
444 			} catch (Exception e) {
445 				throw error(e);
446 			}
447 		}
448 
449 		@Override
450 		void request(HttpURLConnection conn, String prefix) throws IOException {
451 			conn.setRequestProperty(prefix + JGIT_PROFILE, profile);
452 			conn.setRequestProperty(prefix + JGIT_VERSION, version);
453 			conn.setRequestProperty(prefix + JGIT_CONTEXT, context);
454 			// No cleanup:
455 			// single encrypt can be followed by several request
456 			// from the AmazonS3.putImpl() multiple retry attempts
457 			// context = null; // Cleanup encrypt -> request transition.
458 			// TODO re-factor AmazonS3.putImpl to be more transaction-like
459 		}
460 
461 		// Shared state validate -> decrypt.
462 		volatile Cipher decryptCipher;
463 
464 		@Override
465 		void validate(HttpURLConnection conn, String prefix)
466 				throws IOException {
467 			String prof = conn.getHeaderField(prefix + JGIT_PROFILE);
468 			String vers = conn.getHeaderField(prefix + JGIT_VERSION);
469 			String cont = conn.getHeaderField(prefix + JGIT_CONTEXT);
470 
471 			if (prof == null) {
472 				throw new IOException(MessageFormat
473 						.format(JGitText.get().encryptionError, JGIT_PROFILE));
474 			}
475 			if (vers == null) {
476 				throw new IOException(MessageFormat
477 						.format(JGitText.get().encryptionError, JGIT_VERSION));
478 			}
479 			if (cont == null) {
480 				throw new IOException(MessageFormat
481 						.format(JGitText.get().encryptionError, JGIT_CONTEXT));
482 			}
483 			if (!profile.equals(prof)) {
484 				throw new IOException(MessageFormat.format(
485 						JGitText.get().unsupportedEncryptionAlgorithm, prof));
486 			}
487 			if (!version.equals(vers)) {
488 				throw new IOException(MessageFormat.format(
489 						JGitText.get().unsupportedEncryptionVersion, vers));
490 			}
491 			try {
492 				decryptCipher = Cipher.getInstance(cipherAlgo);
493 				if (cont.isEmpty()) {
494 					decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
495 				} else {
496 					AlgorithmParameters params = AlgorithmParameters
497 							.getInstance(paramsAlgo);
498 					params.init(Base64.decode(cont));
499 					decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, params);
500 				}
501 			} catch (Exception e) {
502 				throw error(e);
503 			}
504 		}
505 
506 		@Override
507 		InputStream decrypt(InputStream input) throws IOException {
508 			try {
509 				return new CipherInputStream(input, decryptCipher);
510 			} finally {
511 				decryptCipher = null; // Cleanup validate -> decrypt transition.
512 			}
513 		}
514 	}
515 
516 	/**
517 	 * Provides JetS3t-like encryption with AES support. Uses V1 connection file
518 	 * format. For reference, see: 'jgit-s3-connection-v-1.properties'.
519 	 */
520 	static class JGitV1 extends SymmetricEncryption {
521 
522 		static final String VERSION = "1"; //$NON-NLS-1$
523 
524 		// Re-map connection properties V1 -> V2.
525 		static Properties wrap(String algo, String pass) {
526 			Properties props = new Properties();
527 			props.put(AmazonS3.Keys.CRYPTO_ALG, algo);
528 			props.put(AmazonS3.Keys.CRYPTO_VER, VERSION);
529 			props.put(AmazonS3.Keys.PASSWORD, pass);
530 			props.put(algo + Keys.X_ALGO, algo);
531 			props.put(algo + Keys.X_KEY_ALGO, algo);
532 			props.put(algo + Keys.X_KEY_ITER, DEFAULT_KEY_ITER);
533 			props.put(algo + Keys.X_KEY_SIZE, DEFAULT_KEY_SIZE);
534 			props.put(algo + Keys.X_KEY_SALT, DEFAULT_KEY_SALT);
535 			return props;
536 		}
537 
538 		JGitV1(String algo, String pass)
539 				throws GeneralSecurityException {
540 			super(wrap(algo, pass));
541 			String name = cipherAlgo.toUpperCase();
542 			Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name);
543 			if (!matcherPBE.matches())
544 				throw new GeneralSecurityException(
545 						JGitText.get().encryptionOnlyPBE);
546 		}
547 
548 	}
549 
550 	/**
551 	 * Supports both PBE and non-PBE algorithms. Uses V2 connection file format.
552 	 * For reference, see: 'jgit-s3-connection-v-2.properties'.
553 	 */
554 	static class JGitV2 extends SymmetricEncryption {
555 
556 		static final String VERSION = "2"; //$NON-NLS-1$
557 
558 		JGitV2(Properties props)
559 				throws GeneralSecurityException {
560 			super(props);
561 		}
562 	}
563 
564 	/**
565 	 * Encryption factory.
566 	 *
567 	 * @param props
568 	 * @return instance
569 	 * @throws GeneralSecurityException
570 	 */
571 	static WalkEncryption instance(Properties props)
572 			throws GeneralSecurityException {
573 
574 		String algo = props.getProperty(AmazonS3.Keys.CRYPTO_ALG, Vals.DEFAULT_ALGO);
575 		String vers = props.getProperty(AmazonS3.Keys.CRYPTO_VER, Vals.DEFAULT_VERS);
576 		String pass = props.getProperty(AmazonS3.Keys.PASSWORD);
577 
578 		if (pass == null) // Disable encryption.
579 			return WalkEncryption.NONE;
580 
581 		switch (vers) {
582 		case Vals.DEFAULT_VERS:
583 			return new JetS3tV2(algo, pass);
584 		case JGitV1.VERSION:
585 			return new JGitV1(algo, pass);
586 		case JGitV2.VERSION:
587 			return new JGitV2(props);
588 		default:
589 			throw new GeneralSecurityException(MessageFormat.format(
590 					JGitText.get().unsupportedEncryptionVersion, vers));
591 		}
592 	}
593 }