See previous attempt: https://git.eclipse.org/r/#/c/16674/ Here we preserve as much of JetS3t mode as possible while allowing to use new Java 8+ PBE algorithms such as PBEWithHmacSHA512AndAES_256 Summary of changes: * change pom.xml to control long tests * add WalkEncryptionTest.launch to run long tests * add AmazonS3.Keys to to normalize use of constants * change WalkEncryption to support AES in JetS3t mode * add WalkEncryptionTest to test remote encryption pipeline * add support for CI configuration for live Amazon S3 testing * add log4j based logging for tests in both Eclipse and Maven build To test locally, check out the review branch, then: * create amazon test configuration file * located your home dir: ${user.home} * named jgit-s3-config.properties * file format follows AmazonS3 connection settings file: accesskey = your-amazon-access-key secretkey = your-amazon-secret-key test.bucket = your-bucket-for-testing * finally: * run in Eclipse: WalkEncryptionTest.launch * or * run in Shell: mvn test --define test=WalkEncryptionTest Change-Id: I6f455fd9fb4eac261ca73d0bec6a4e7dae9f2e91 Signed-off-by: Andrei Pozolotin <andrei.pozolotin@gmail.com>tags/v4.2.0.201511101648-m1
@@ -1 +1,2 @@ | |||
/target | |||
/.project |
@@ -4,3 +4,4 @@ source.. = tst/,\ | |||
bin.includes = META-INF/,\ | |||
.,\ | |||
plugin.properties | |||
additional.bundles = org.apache.log4j |
@@ -0,0 +1,20 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType"> | |||
<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/> | |||
<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --define http_proxy=http://proxy:3128"/> | |||
<booleanAttribute key="M2_NON_RECURSIVE" value="false"/> | |||
<booleanAttribute key="M2_OFFLINE" value="false"/> | |||
<stringAttribute key="M2_PROFILES" value=""/> | |||
<listAttribute key="M2_PROPERTIES"/> | |||
<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/> | |||
<booleanAttribute key="M2_SKIP_TESTS" value="false"/> | |||
<intAttribute key="M2_THREADS" value="1"/> | |||
<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/> | |||
<stringAttribute key="M2_USER_SETTINGS" value=""/> | |||
<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/> | |||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> | |||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> | |||
</listAttribute> | |||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/> | |||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/> | |||
</launchConfiguration> |
@@ -0,0 +1,20 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType"> | |||
<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/> | |||
<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --activate-profiles test.long"/> | |||
<booleanAttribute key="M2_NON_RECURSIVE" value="false"/> | |||
<booleanAttribute key="M2_OFFLINE" value="false"/> | |||
<stringAttribute key="M2_PROFILES" value=""/> | |||
<listAttribute key="M2_PROPERTIES"/> | |||
<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/> | |||
<booleanAttribute key="M2_SKIP_TESTS" value="false"/> | |||
<intAttribute key="M2_THREADS" value="1"/> | |||
<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/> | |||
<stringAttribute key="M2_USER_SETTINGS" value=""/> | |||
<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/> | |||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> | |||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> | |||
</listAttribute> | |||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/> | |||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/> | |||
</launchConfiguration> |
@@ -69,6 +69,16 @@ | |||
<scope>test</scope> | |||
</dependency> | |||
<!-- Optional security provider for encryption tests. --> | |||
<!-- See https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9554 --> | |||
<!-- See https://bugs.eclipse.org/bugs/show_bug.cgi?id=467064 --> | |||
<dependency> | |||
<groupId>org.bouncycastle</groupId> | |||
<artifactId>bcprov-jdk15on</artifactId> | |||
<version>1.52</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.hamcrest</groupId> | |||
<artifactId>hamcrest-library</artifactId> | |||
@@ -101,6 +111,24 @@ | |||
</dependency> | |||
</dependencies> | |||
<profiles> | |||
<!-- Profile provides a property which enables long running tests. --> | |||
<profile> | |||
<id>test.long</id> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-surefire-plugin</artifactId> | |||
<configuration> | |||
<argLine>-Djgit.test.long=true</argLine> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</profile> | |||
</profiles> | |||
<build> | |||
<sourceDirectory>src/</sourceDirectory> | |||
<testSourceDirectory>tst/</testSourceDirectory> |
@@ -0,0 +1,48 @@ | |||
# | |||
# See WalkEncryptionTest.java | |||
# | |||
# This file is a template for test configuration file used by WalkEncryptionTest. | |||
# To be active, this file must have the following hard coded name: jgit-s3-config.properties | |||
# To be active, this file must be discovered by WalkEncryptionTest from one of these locations: | |||
# * ${user.home}/jgit-s3-config.properties | |||
# * ${user.dir}/jgit-s3-config.properties | |||
# * ${user.dir}/tst-rsrc/jgit-s3-config.properties | |||
# When this file is missing, tests in WalkEncryptionTest will not run, only report a warning. | |||
# | |||
# | |||
# WalkEncryptionTest requires amazon s3 test bucket setup. | |||
# | |||
# Test bucket setup instructions: | |||
# | |||
# Create IAM user: | |||
# http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html | |||
# * user name: jgit.eclipse.org | |||
# | |||
# Configure IAM user S3 bucket access | |||
# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html | |||
# * attach S3 user policy to user account: jgit-s3-config.policy.user.json | |||
# | |||
# Create S3 bucket: | |||
# http://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html | |||
# * bucket name: jgit.eclipse.org | |||
# | |||
# Configure S3 bucket source address/mask access: | |||
# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html | |||
# * attach bucket policy to the test bucket: jgit-s3-config.policy.bucket.json | |||
# * verify that any required source address/mask is included in the bucket policy: | |||
# * see https://wiki.eclipse.org/Hudson | |||
# * see http://www.tcpiputils.com/browse/ip-address/198.41.30.200 | |||
# * proxy.eclipse.org 198.41.30.0/24 | |||
# * Andrei Pozolotin 67.175.188.187/32 | |||
# | |||
# Configure bucket 1 day expiration in object life cycle management: | |||
# * https://docs.aws.amazon.com/AmazonS3/latest/dev/manage-lifecycle-using-console.html | |||
# | |||
# Test bucket name | |||
test.bucket=jgit.eclipse.org | |||
# IAM credentials for user jgit.eclipse.org | |||
accesskey=AKIAIYWXB4ETREBRMZDQ | |||
secretkey=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 |
@@ -0,0 +1,20 @@ | |||
{ | |||
"Version": "2012-10-17", | |||
"Statement": [ | |||
{ | |||
"Sid": "DenyAllButKnownSourceAddressWithMask", | |||
"Effect": "Deny", | |||
"Principal": "*", | |||
"Action": "s3:*", | |||
"Resource": "arn:aws:s3:::jgit.eclipse.org/*", | |||
"Condition": { | |||
"NotIpAddress": { | |||
"aws:SourceIp": [ | |||
"198.41.30.0/24", | |||
"67.175.188.187/32" | |||
] | |||
} | |||
} | |||
} | |||
] | |||
} |
@@ -0,0 +1,24 @@ | |||
{ | |||
"Version": "2012-10-17", | |||
"Statement": [ | |||
{ | |||
"Sid": "BucketList", | |||
"Effect": "Allow", | |||
"Action": "s3:ListAllMyBuckets", | |||
"Resource": [ | |||
"arn:aws:s3:::jgit.eclipse.org" | |||
] | |||
}, | |||
{ | |||
"Sid": "BucketFullControl", | |||
"Effect": "Allow", | |||
"Action": [ | |||
"s3:*" | |||
], | |||
"Resource": [ | |||
"arn:aws:s3:::jgit.eclipse.org", | |||
"arn:aws:s3:::jgit.eclipse.org/*" | |||
] | |||
} | |||
] | |||
} |
@@ -0,0 +1,9 @@ | |||
# Root logger option | |||
log4j.rootLogger=INFO, stdout | |||
# Direct log messages to stdout | |||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender | |||
log4j.appender.stdout.Target=System.out | |||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout | |||
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n |
@@ -228,6 +228,7 @@ emptyCommit=No changes | |||
emptyPathNotPermitted=Empty path not permitted. | |||
emptyRef=Empty ref: {0} | |||
encryptionError=Encryption error: {0} | |||
encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported. | |||
endOfFileInEscape=End of file in escape | |||
entryNotFoundByPath=Entry not found by path: {0} | |||
enumValueNotSupported2=Invalid value: {0}.{1}={2} |
@@ -287,6 +287,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String emptyPathNotPermitted; | |||
/***/ public String emptyRef; | |||
/***/ public String encryptionError; | |||
/***/ public String encryptionOnlyPBE; | |||
/***/ public String endOfFileInEscape; | |||
/***/ public String entryNotFoundByPath; | |||
/***/ public String enumValueNotSupported2; |
@@ -56,10 +56,10 @@ import java.net.ProxySelector; | |||
import java.net.URL; | |||
import java.net.URLConnection; | |||
import java.security.DigestOutputStream; | |||
import java.security.GeneralSecurityException; | |||
import java.security.InvalidKeyException; | |||
import java.security.MessageDigest; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.spec.InvalidKeySpecException; | |||
import java.text.MessageFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.ArrayList; | |||
@@ -186,6 +186,19 @@ public class AmazonS3 { | |||
/** S3 Bucket Domain. */ | |||
private final String domain; | |||
/** Property names used in amazon connection configuration file. */ | |||
interface Keys { | |||
String ACCESS_KEY = "accesskey"; //$NON-NLS-1$ | |||
String SECRET_KEY = "secretkey"; //$NON-NLS-1$ | |||
String PASSWORD = "password"; //$NON-NLS-1$ | |||
String CRYPTO_ALG = "crypto.algorithm"; //$NON-NLS-1$ | |||
String CRYPTO_VER = "crypto.version"; //$NON-NLS-1$ | |||
String ACL = "acl"; //$NON-NLS-1$ | |||
String DOMAIN = "domain"; //$NON-NLS-1$ | |||
String HTTP_RETRY = "httpclient.retry-max"; //$NON-NLS-1$ | |||
String TMP_DIR = "tmpdir"; //$NON-NLS-1$ | |||
} | |||
/** | |||
* Create a new S3 client for the supplied user information. | |||
* <p> | |||
@@ -219,17 +232,18 @@ public class AmazonS3 { | |||
* | |||
*/ | |||
public AmazonS3(final Properties props) { | |||
domain = props.getProperty("domain", "s3.amazonaws.com"); //$NON-NLS-1$ //$NON-NLS-2$ | |||
publicKey = props.getProperty("accesskey"); //$NON-NLS-1$ | |||
domain = props.getProperty(Keys.DOMAIN, "s3.amazonaws.com"); //$NON-NLS-1$ | |||
publicKey = props.getProperty(Keys.ACCESS_KEY); | |||
if (publicKey == null) | |||
throw new IllegalArgumentException(JGitText.get().missingAccesskey); | |||
final String secret = props.getProperty("secretkey"); //$NON-NLS-1$ | |||
final String secret = props.getProperty(Keys.SECRET_KEY); | |||
if (secret == null) | |||
throw new IllegalArgumentException(JGitText.get().missingSecretkey); | |||
privateKey = new SecretKeySpec(Constants.encodeASCII(secret), HMAC); | |||
final String pacl = props.getProperty("acl", "PRIVATE"); //$NON-NLS-1$ //$NON-NLS-2$ | |||
final String pacl = props.getProperty(Keys.ACL, "PRIVATE"); //$NON-NLS-1$ | |||
if (StringUtils.equalsIgnoreCase("PRIVATE", pacl)) //$NON-NLS-1$ | |||
acl = "private"; //$NON-NLS-1$ | |||
else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl)) //$NON-NLS-1$ | |||
@@ -242,26 +256,24 @@ public class AmazonS3 { | |||
throw new IllegalArgumentException("Invalid acl: " + pacl); //$NON-NLS-1$ | |||
try { | |||
final String cPas = props.getProperty("password"); //$NON-NLS-1$ | |||
final String cPas = props.getProperty(Keys.PASSWORD); | |||
if (cPas != null) { | |||
String cAlg = props.getProperty("crypto.algorithm"); //$NON-NLS-1$ | |||
String cAlg = props.getProperty(Keys.CRYPTO_ALG); | |||
if (cAlg == null) | |||
cAlg = "PBEWithMD5AndDES"; //$NON-NLS-1$ | |||
encryption = new WalkEncryption.ObjectEncryptionV2(cAlg, cPas); | |||
cAlg = WalkEncryption.ObjectEncryptionJetS3tV2.JETS3T_ALGORITHM; | |||
encryption = new WalkEncryption.ObjectEncryptionJetS3tV2(cAlg, cPas); | |||
} else { | |||
encryption = WalkEncryption.NONE; | |||
} | |||
} catch (InvalidKeySpecException e) { | |||
throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); | |||
} catch (NoSuchAlgorithmException e) { | |||
} catch (GeneralSecurityException e) { | |||
throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); | |||
} | |||
maxAttempts = Integer.parseInt(props.getProperty( | |||
"httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$ | |||
maxAttempts = Integer | |||
.parseInt(props.getProperty(Keys.HTTP_RETRY, "3")); //$NON-NLS-1$ | |||
proxySelector = ProxySelector.getDefault(); | |||
String tmp = props.getProperty("tmpdir"); //$NON-NLS-1$ | |||
String tmp = props.getProperty(Keys.TMP_DIR); | |||
tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null; | |||
} | |||
@@ -47,18 +47,16 @@ import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.net.HttpURLConnection; | |||
import java.security.InvalidAlgorithmParameterException; | |||
import java.security.InvalidKeyException; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.spec.InvalidKeySpecException; | |||
import java.security.GeneralSecurityException; | |||
import java.security.spec.AlgorithmParameterSpec; | |||
import java.text.MessageFormat; | |||
import javax.crypto.Cipher; | |||
import javax.crypto.CipherInputStream; | |||
import javax.crypto.CipherOutputStream; | |||
import javax.crypto.NoSuchPaddingException; | |||
import javax.crypto.SecretKey; | |||
import javax.crypto.SecretKeyFactory; | |||
import javax.crypto.spec.IvParameterSpec; | |||
import javax.crypto.spec.PBEKeySpec; | |||
import javax.crypto.spec.PBEParameterSpec; | |||
@@ -77,23 +75,29 @@ abstract class WalkEncryption { | |||
abstract void request(HttpURLConnection u, String prefix); | |||
abstract void validate(HttpURLConnection u, String p) throws IOException; | |||
abstract void validate(HttpURLConnection u, String prefix) throws IOException; | |||
protected void validateImpl(final HttpURLConnection u, final String p, | |||
// TODO mixed ciphers | |||
// consider permitting mixed ciphers to facilitate algorithm migration | |||
// i.e. user keeps the password, but changes the algorithm | |||
// then existing remote entries will still be readable | |||
protected void validateImpl(final HttpURLConnection u, final String prefix, | |||
final String version, final String name) throws IOException { | |||
String v; | |||
v = u.getHeaderField(p + JETS3T_CRYPTO_VER); | |||
v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER); | |||
if (v == null) | |||
v = ""; //$NON-NLS-1$ | |||
if (!version.equals(v)) | |||
throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v)); | |||
v = u.getHeaderField(p + JETS3T_CRYPTO_ALG); | |||
v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG); | |||
if (v == null) | |||
v = ""; //$NON-NLS-1$ | |||
if (!name.equals(v)) | |||
throw new IOException(JGitText.get().unsupportedEncryptionAlgorithm + v); | |||
// Standard names are not case-sensitive. | |||
// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html | |||
if (!name.equalsIgnoreCase(v)) | |||
throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v)); | |||
} | |||
IOException error(final Throwable why) { | |||
@@ -110,9 +114,9 @@ abstract class WalkEncryption { | |||
} | |||
@Override | |||
void validate(final HttpURLConnection u, final String p) | |||
void validate(final HttpURLConnection u, final String prefix) | |||
throws IOException { | |||
validateImpl(u, p, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |||
validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |||
} | |||
@Override | |||
@@ -126,53 +130,132 @@ abstract class WalkEncryption { | |||
} | |||
} | |||
static class ObjectEncryptionV2 extends WalkEncryption { | |||
private static int ITERATION_COUNT = 5000; | |||
// PBEParameterSpec factory for Java (version <= 7). | |||
// Does not support AlgorithmParameterSpec. | |||
static PBEParameterSpec java7PBEParameterSpec(byte[] salt, | |||
int iterationCount) { | |||
return new PBEParameterSpec(salt, iterationCount); | |||
} | |||
// PBEParameterSpec factory for Java (version >= 8). | |||
// Adds support for AlgorithmParameterSpec. | |||
static PBEParameterSpec java8PBEParameterSpec(byte[] salt, | |||
int iterationCount, AlgorithmParameterSpec paramSpec) { | |||
try { | |||
@SuppressWarnings("boxing") | |||
PBEParameterSpec instance = PBEParameterSpec.class | |||
.getConstructor(byte[].class, int.class, | |||
AlgorithmParameterSpec.class) | |||
.newInstance(salt, iterationCount, paramSpec); | |||
return instance; | |||
} catch (Exception e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
// Current runtime version. | |||
// https://docs.oracle.com/javase/7/docs/technotes/guides/versioning/spec/versioning2.html | |||
static double javaVersion() { | |||
return Double.parseDouble(System.getProperty("java.specification.version")); //$NON-NLS-1$ | |||
} | |||
/** | |||
* JetS3t compatibility reference: <a href= | |||
* "https://bitbucket.org/jmurty/jets3t/src/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java"> | |||
* EncryptionUtil.java</a> | |||
* <p> | |||
* Note: EncryptionUtil is inadequate: | |||
* <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which | |||
* "always works", but in JetS3t both encryption and decryption use non-IV | |||
* aware algorithm parameters for all PBE specs, which breaks in case of AES | |||
* <li>that means that only non-IV algorithms will work round trip in | |||
* JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC | |||
* <li>any AES based algorithms such as "PBE...With...And...AES" will not | |||
* work, since they need proper IV setup | |||
*/ | |||
static class ObjectEncryptionJetS3tV2 extends WalkEncryption { | |||
static final String JETS3T_VERSION = "2"; //$NON-NLS-1$ | |||
static final String JETS3T_ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$ | |||
static final int JETS3T_ITERATIONS = 5000; | |||
static final int JETS3T_KEY_SIZE = 32; | |||
static final byte[] JETS3T_SALT = { // | |||
(byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, // | |||
(byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 // | |||
}; | |||
// Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE | |||
static final byte[] ZERO_AES_IV = new byte[16]; | |||
private static byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, | |||
(byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 }; | |||
private final String cryptoVer = JETS3T_VERSION; | |||
private final String algorithmName; | |||
private final String cryptoAlg; | |||
private final SecretKey skey; | |||
private final SecretKey secretKey; | |||
private final PBEParameterSpec aspec; | |||
private final AlgorithmParameterSpec paramSpec; | |||
ObjectEncryptionV2(final String algo, final String key) | |||
throws InvalidKeySpecException, NoSuchAlgorithmException { | |||
algorithmName = algo; | |||
ObjectEncryptionJetS3tV2(final String algo, final String key) | |||
throws GeneralSecurityException { | |||
cryptoAlg = algo; | |||
// Standard names are not case-sensitive. | |||
// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html | |||
String cryptoName = cryptoAlg.toUpperCase(); | |||
if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$ | |||
throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE); | |||
PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), JETS3T_SALT, JETS3T_ITERATIONS, JETS3T_KEY_SIZE); | |||
secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec); | |||
// Detect algorithms which require initialization vector. | |||
boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$ | |||
// PBEParameterSpec algorithm parameters are supported from Java 8. | |||
boolean isJava8 = javaVersion() >= 1.8; | |||
if (useIV && isJava8) { | |||
// Support IV where possible: | |||
// * since JCE provider uses random IV for PBE/AES | |||
// * and there is no place to store dynamic IV in JetS3t V2 | |||
// * we use static IV, and tolerate increased security risk | |||
// TODO back port this change to JetS3t V2 | |||
// See: | |||
// https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java | |||
// http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java | |||
IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV); | |||
paramSpec = java8PBEParameterSpec(JETS3T_SALT, JETS3T_ITERATIONS, paramIV); | |||
} else { | |||
// Strict legacy JetS3t V2 compatibility, with no IV support. | |||
paramSpec = java7PBEParameterSpec(JETS3T_SALT, JETS3T_ITERATIONS); | |||
} | |||
final PBEKeySpec s; | |||
s = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, 32); | |||
skey = SecretKeyFactory.getInstance(algo).generateSecret(s); | |||
aspec = new PBEParameterSpec(salt, ITERATION_COUNT); | |||
} | |||
@Override | |||
void request(final HttpURLConnection u, final String prefix) { | |||
u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, "2"); //$NON-NLS-1$ | |||
u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, algorithmName); | |||
u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, cryptoVer); | |||
u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg); | |||
} | |||
@Override | |||
void validate(final HttpURLConnection u, final String p) | |||
void validate(final HttpURLConnection u, final String prefix) | |||
throws IOException { | |||
validateImpl(u, p, "2", algorithmName); //$NON-NLS-1$ | |||
validateImpl(u, prefix, cryptoVer, cryptoAlg); | |||
} | |||
@Override | |||
OutputStream encrypt(final OutputStream os) throws IOException { | |||
try { | |||
final Cipher c = Cipher.getInstance(algorithmName); | |||
c.init(Cipher.ENCRYPT_MODE, skey, aspec); | |||
return new CipherOutputStream(os, c); | |||
} catch (NoSuchAlgorithmException e) { | |||
throw error(e); | |||
} catch (NoSuchPaddingException e) { | |||
throw error(e); | |||
} catch (InvalidKeyException e) { | |||
throw error(e); | |||
} catch (InvalidAlgorithmParameterException e) { | |||
final Cipher cipher = Cipher.getInstance(cryptoAlg); | |||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); | |||
return new CipherOutputStream(os, cipher); | |||
} catch (GeneralSecurityException e) { | |||
throw error(e); | |||
} | |||
} | |||
@@ -180,16 +263,10 @@ abstract class WalkEncryption { | |||
@Override | |||
InputStream decrypt(final InputStream in) throws IOException { | |||
try { | |||
final Cipher c = Cipher.getInstance(algorithmName); | |||
c.init(Cipher.DECRYPT_MODE, skey, aspec); | |||
return new CipherInputStream(in, c); | |||
} catch (NoSuchAlgorithmException e) { | |||
throw error(e); | |||
} catch (NoSuchPaddingException e) { | |||
throw error(e); | |||
} catch (InvalidKeyException e) { | |||
throw error(e); | |||
} catch (InvalidAlgorithmParameterException e) { | |||
final Cipher cipher = Cipher.getInstance(cryptoAlg); | |||
cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); | |||
return new CipherInputStream(in, cipher); | |||
} catch (GeneralSecurityException e) { | |||
throw error(e); | |||
} | |||
} |