From f33c2e62ad54fc55991b4be9db399396190018e4 Mon Sep 17 00:00:00 2001
From: Dan Davis Allows many classes implementing {@link IXMLConfigurable} to load and store an {@link EncryptionKey} in a standard manner through delegation. Simplified encryption and decryption methods using the
- * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you
+ * Simplified encryption and decryption methods using the
+ * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you
* can also think of as a passphrase, or password).
* The "salt" and iteration count used by this class are hard-coded. To have
- * more control and ensure a more secure approach, you should rely on another
+ * more control and ensure a more secure approach, you should rely on another
* implementation or create your own.
*
@@ -44,7 +44,7 @@
* java -cp norconex-commons-lang-[version].jar com.norconex.commons.lang.encrypt.EncryptionUtil
*
*
- * For example, to use a encryption key store in a file to encrypt some text,
+ * For example, to use a encryption key store in a file to encrypt some text,
* add the following arguments to the above command:
* null
if the key does not exist
+ * @return encryption key or null
if the key does not exist
* for the specified type
*/
public String resolve() {
@@ -101,12 +128,12 @@ public String resolve() {
return null;
}
}
-
+
private String fromEnv() {
//TODO allow a flag to optionally throw an exception when null?
return System.getenv(value);
}
-
+
private String fromProperty() {
//TODO allow a flag to optionally throw an exception when null?
return System.getProperty(value);
@@ -128,7 +155,7 @@ private String fromFile() {
"Could not read key file.", e);
}
}
-
+
//Do not use Apache Commons Lang below to avoid any dependency
//when used on command-line with EncryptionUtil.
@Override
@@ -136,6 +163,7 @@ public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((source == null) ? 0 : source.hashCode());
+ result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@@ -154,6 +182,9 @@ public boolean equals(Object obj) {
if (source != other.source) {
return false;
}
+ if (algorithm != other.algorithm) {
+ return false;
+ }
if (value == null) {
if (other.value != null) {
return false;
@@ -165,6 +196,6 @@ public boolean equals(Object obj) {
}
@Override
public String toString() {
- return "EncryptionKey [value=" + value + ", source=" + source + "]";
- }
+ return "EncryptionKey [value=" + value + ", source=" + source + ", algorithm=" + algorithm + "]";
+ }
}
diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
index df321c5e..6d10ae98 100644
--- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
+++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
@@ -29,11 +29,11 @@
import com.norconex.commons.lang.encrypt.EncryptionKey.Source;
/**
- *
@@ -54,12 +54,12 @@
* As of 1.13.0, you can also use the
encrypt.[sh|bat]
and
* decrypt.[sh|bat]
files distributed with this library.
*
Encrypts the given text with the encryption key supplied. If the
- * encryption key is null
or resolves to blank key,
- * the text to encrypt will be returned unmodified.
null
if
- * textToEncrypt
is null
.
- */
- public static String encrypt(
- String textToEncrypt, EncryptionKey encryptionKey) {
- if (textToEncrypt == null) {
- return null;
- }
- if (encryptionKey == null) {
- return textToEncrypt;
- }
- String key = encryptionKey.resolve();
- if (key == null) {
- return textToEncrypt;
- }
-
+
+ private static String encryptLegacy(String textToEncrypt, String key) {
// 8-byte Salt
byte[] salt = {
- (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9,
+ (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9,
(byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56
};
// Iteration count
@@ -158,46 +136,29 @@ public static String encrypt(
ecipher = Cipher.getInstance(secretKey.getAlgorithm());
// Prepare the parameter to the ciphers
- AlgorithmParameterSpec paramSpec =
+ AlgorithmParameterSpec paramSpec =
new PBEParameterSpec(salt, iterationCount);
// Create the ciphers
ecipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
-
+
byte[] utf8 = textToEncrypt.trim().getBytes(StandardCharsets.UTF_8);
byte[] enc = ecipher.doFinal(utf8);
-
+
return DatatypeConverter.printBase64Binary(enc);
} catch (Exception e) {
throw new EncryptionException("Encryption failed.", e);
}
}
-
- /**
- * Decrypts the given encrypted text with the encryption key supplied. - *
- * @param encryptedText text to be decrypted - * @param encryptionKey encryption key which must resolve to the same - * value to encrypt and decrypt the supplied text. - * @return decrypted text ornull
if one of
- * encryptedText
or key
is null
.
- */
- public static String decrypt(
- String encryptedText, EncryptionKey encryptionKey) {
- if (encryptedText == null) {
- return null;
- }
- if (encryptionKey == null) {
- return encryptedText;
- }
- String key = encryptionKey.resolve();
- if (key == null) {
- return encryptedText;
- }
-
+
+ private static String encryptStrong(String textToEncrypt, String key) {
+ return encryptLegacy(textToEncrypt, key);
+ }
+
+ private static String decryptLegacy(String encryptedText, String key) {
// 8-byte Salt
byte[] salt = {
- (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9,
+ (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9,
(byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56
};
// Iteration count
@@ -213,13 +174,13 @@ public static String decrypt(
dcipher = Cipher.getInstance(secretKey.getAlgorithm());
// Prepare the parameter to the ciphers
- AlgorithmParameterSpec paramSpec =
+ AlgorithmParameterSpec paramSpec =
new PBEParameterSpec(salt, iterationCount);
// Create the ciphers
dcipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
-
- byte[] dec =
+
+ byte[] dec =
DatatypeConverter.parseBase64Binary(encryptedText.trim());
byte[] utf8 = dcipher.doFinal(dec);
return new String(utf8, StandardCharsets.UTF_8);
@@ -227,4 +188,77 @@ public static String decrypt(
throw new EncryptionException("Decryption failed.", e);
}
}
+
+ private static String decryptStrong(String encryptedText, String key) {
+ return decryptLegacy(encryptedText, key);
+ }
+
+ private static String decryptAutomatic(String encryptedText, String key) {
+ return decryptLegacy(encryptedText, key);
+
+ }
+
+ /**
+ * Encrypts the given text with the encryption key supplied. If the
+ * encryption key is null
or resolves to blank key,
+ * the text to encrypt will be returned unmodified.
null
if
+ * textToEncrypt
is null
.
+ */
+ public static String encrypt(String textToEncrypt, EncryptionKey encryptionKey) {
+ if (textToEncrypt == null) {
+ return null;
+ }
+ if (encryptionKey == null) {
+ return textToEncrypt;
+ }
+ String key = encryptionKey.resolve();
+ if (key == null) {
+ return textToEncrypt;
+ }
+ switch (encryptionKey.getAlgorithm()) {
+ case LEGACY:
+ return encryptLegacy(textToEncrypt, key);
+ case STRONG:
+ case AUTO:
+ default:
+ return encryptStrong(textToEncrypt, key);
+ }
+ }
+
+ /**
+ * Decrypts the given encrypted text with the encryption key supplied. + *
+ * @param encryptedText text to be decrypted + * @param encryptionKey encryption key which must resolve to the same + * value to encrypt and decrypt the supplied text. + * @return decrypted text ornull
if one of
+ * encryptedText
or key
is null
.
+ */
+ public static String decrypt(
+ String encryptedText, EncryptionKey encryptionKey) {
+ if (encryptedText == null) {
+ return null;
+ }
+ if (encryptionKey == null) {
+ return encryptedText;
+ }
+ String key = encryptionKey.resolve();
+ if (key == null) {
+ return encryptedText;
+ }
+
+ switch (encryptionKey.getAlgorithm()) {
+ case LEGACY:
+ return decryptLegacy(encryptedText, key);
+ case STRONG:
+ return decryptStrong(encryptedText, key);
+ case AUTO:
+ default:
+ return decryptAutomatic(encryptedText, key);
+ }
+ }
}
diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java
index 8d092cdb..3cba4178 100644
--- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java
+++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java
@@ -16,15 +16,37 @@
import org.junit.Assert;
import org.junit.Test;
+import org.junit.Assume;
public class EncryptionUtilTest {
+ public static final String ENVIRONMENT_KEY = "TEST_ENCRYPT_KEY";
+ public static final String PROPERTY_KEY = "test.encrypt.key";
+ public static final String CLEAR_TEXT = "please encrypt this text.";
+
@Test
public void testEncrypt() {
EncryptionKey key = new EncryptionKey("this is my secret key.");
- String text = "please encrypt this text.";
- String encryptedText = EncryptionUtil.encrypt(text, key);
+ String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key);
+ String decryptedText = EncryptionUtil.decrypt(encryptedText, key);
+ Assert.assertEquals(CLEAR_TEXT, decryptedText);
+ }
+
+ @Test
+ public void testEnvironmentEncrypt() {
+ Assume.assumeNotNull(System.getenv(ENVIRONMENT_KEY));
+ EncryptionKey key = new EncryptionKey(ENVIRONMENT_KEY, EncryptionKey.Source.ENVIRONMENT);
+ String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key);
+ String decryptedText = EncryptionUtil.decrypt(encryptedText, key);
+ Assert.assertEquals(CLEAR_TEXT, decryptedText);
+ }
+
+ @Test
+ public void testPropertyEncrypt() {
+ Assume.assumeNotNull(System.getProperty(PROPERTY_KEY));
+ EncryptionKey key = new EncryptionKey(PROPERTY_KEY, EncryptionKey.Source.PROPERTY);
+ String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key);
String decryptedText = EncryptionUtil.decrypt(encryptedText, key);
- Assert.assertEquals(text, decryptedText);
+ Assert.assertEquals(CLEAR_TEXT, decryptedText);
}
}
From ded69030ec90c62a292dc63d54a8edd765d5e435 Mon Sep 17 00:00:00 2001
From: Dan Davis null
if the key does not exist
+ * @return encryption key or null
if the key does not exist
* for the specified type
*/
public String resolve() {
@@ -128,12 +101,12 @@ public String resolve() {
return null;
}
}
-
+
private String fromEnv() {
//TODO allow a flag to optionally throw an exception when null?
return System.getenv(value);
}
-
+
private String fromProperty() {
//TODO allow a flag to optionally throw an exception when null?
return System.getProperty(value);
@@ -155,7 +128,7 @@ private String fromFile() {
"Could not read key file.", e);
}
}
-
+
//Do not use Apache Commons Lang below to avoid any dependency
//when used on command-line with EncryptionUtil.
@Override
@@ -163,7 +136,6 @@ public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((source == null) ? 0 : source.hashCode());
- result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@@ -182,9 +154,6 @@ public boolean equals(Object obj) {
if (source != other.source) {
return false;
}
- if (algorithm != other.algorithm) {
- return false;
- }
if (value == null) {
if (other.value != null) {
return false;
@@ -196,6 +165,6 @@ public boolean equals(Object obj) {
}
@Override
public String toString() {
- return "EncryptionKey [value=" + value + ", source=" + source + ", algorithm=" + algorithm + "]";
- }
+ return "EncryptionKey [value=" + value + ", source=" + source + "]";
+ }
}
diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
index 6d10ae98..df321c5e 100644
--- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
+++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java
@@ -29,11 +29,11 @@
import com.norconex.commons.lang.encrypt.EncryptionKey.Source;
/**
- * Simplified encryption and decryption methods using the - * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you + *
Simplified encryption and decryption methods using the + * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you * can also think of as a passphrase, or password). * The "salt" and iteration count used by this class are hard-coded. To have - * more control and ensure a more secure approach, you should rely on another + * more control and ensure a more secure approach, you should rely on another * implementation or create your own. *
*@@ -44,7 +44,7 @@ * java -cp norconex-commons-lang-[version].jar com.norconex.commons.lang.encrypt.EncryptionUtil * *
- * For example, to use a encryption key store in a file to encrypt some text, + * For example, to use a encryption key store in a file to encrypt some text, * add the following arguments to the above command: *
*@@ -54,12 +54,12 @@ * As of 1.13.0, you can also use the*encrypt.[sh|bat]
and *decrypt.[sh|bat]
files distributed with this library. * - * + * * @author Pascal Essiembre * @since 1.9.0 */ public class EncryptionUtil { - + private EncryptionUtil() { super(); } @@ -72,7 +72,7 @@ public static void main(String[] args) { String typeArg = args[1]; String keyArg = args[2]; String textArg = args[3]; - + Source type = null; if ("-k".equalsIgnoreCase(typeArg)) { type = Source.KEY; @@ -86,7 +86,7 @@ public static void main(String[] args) { System.err.println("Unsupported type of key: " + type); printUsage(); } - + EncryptionKey key = new EncryptionKey(keyArg, type); if ("encrypt".equalsIgnoreCase(cmdArg)) { System.out.println(encrypt(textArg, key)); @@ -116,11 +116,33 @@ private static void printUsage() { out.println(" text text to encrypt or decrypt"); System.exit(-1); } - - private static String encryptLegacy(String textToEncrypt, String key) { + + /** + *Encrypts the given text with the encryption key supplied. If the + * encryption key is
+ * @param textToEncrypt text to be encrypted + * @param encryptionKey encryption key which must resolve to the same + * value to encrypt and decrypt the supplied text. + * @return encrypted text ornull
or resolves to blank key, + * the text to encrypt will be returned unmodified.null
if + *textToEncrypt
isnull
. + */ + public static String encrypt( + String textToEncrypt, EncryptionKey encryptionKey) { + if (textToEncrypt == null) { + return null; + } + if (encryptionKey == null) { + return textToEncrypt; + } + String key = encryptionKey.resolve(); + if (key == null) { + return textToEncrypt; + } + // 8-byte Salt byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 }; // Iteration count @@ -136,29 +158,46 @@ private static String encryptLegacy(String textToEncrypt, String key) { ecipher = Cipher.getInstance(secretKey.getAlgorithm()); // Prepare the parameter to the ciphers - AlgorithmParameterSpec paramSpec = + AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount); // Create the ciphers ecipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); - + byte[] utf8 = textToEncrypt.trim().getBytes(StandardCharsets.UTF_8); byte[] enc = ecipher.doFinal(utf8); - + return DatatypeConverter.printBase64Binary(enc); } catch (Exception e) { throw new EncryptionException("Encryption failed.", e); } } - - private static String encryptStrong(String textToEncrypt, String key) { - return encryptLegacy(textToEncrypt, key); - } - - private static String decryptLegacy(String encryptedText, String key) { + + /** + *Decrypts the given encrypted text with the encryption key supplied. + *
+ * @param encryptedText text to be decrypted + * @param encryptionKey encryption key which must resolve to the same + * value to encrypt and decrypt the supplied text. + * @return decrypted text ornull
if one of + *encryptedText
orkey
isnull
. + */ + public static String decrypt( + String encryptedText, EncryptionKey encryptionKey) { + if (encryptedText == null) { + return null; + } + if (encryptionKey == null) { + return encryptedText; + } + String key = encryptionKey.resolve(); + if (key == null) { + return encryptedText; + } + // 8-byte Salt byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 }; // Iteration count @@ -174,13 +213,13 @@ private static String decryptLegacy(String encryptedText, String key) { dcipher = Cipher.getInstance(secretKey.getAlgorithm()); // Prepare the parameter to the ciphers - AlgorithmParameterSpec paramSpec = + AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount); // Create the ciphers dcipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); - - byte[] dec = + + byte[] dec = DatatypeConverter.parseBase64Binary(encryptedText.trim()); byte[] utf8 = dcipher.doFinal(dec); return new String(utf8, StandardCharsets.UTF_8); @@ -188,77 +227,4 @@ private static String decryptLegacy(String encryptedText, String key) { throw new EncryptionException("Decryption failed.", e); } } - - private static String decryptStrong(String encryptedText, String key) { - return decryptLegacy(encryptedText, key); - } - - private static String decryptAutomatic(String encryptedText, String key) { - return decryptLegacy(encryptedText, key); - - } - - /** - *Encrypts the given text with the encryption key supplied. If the - * encryption key is
- * @param textToEncrypt text to be encrypted - * @param encryptionKey encryption key which must resolve to the same - * value to encrypt and decrypt the supplied text. - * @return encrypted text ornull
or resolves to blank key, - * the text to encrypt will be returned unmodified.null
if - *textToEncrypt
isnull
. - */ - public static String encrypt(String textToEncrypt, EncryptionKey encryptionKey) { - if (textToEncrypt == null) { - return null; - } - if (encryptionKey == null) { - return textToEncrypt; - } - String key = encryptionKey.resolve(); - if (key == null) { - return textToEncrypt; - } - switch (encryptionKey.getAlgorithm()) { - case LEGACY: - return encryptLegacy(textToEncrypt, key); - case STRONG: - case AUTO: - default: - return encryptStrong(textToEncrypt, key); - } - } - - /** - *Decrypts the given encrypted text with the encryption key supplied. - *
- * @param encryptedText text to be decrypted - * @param encryptionKey encryption key which must resolve to the same - * value to encrypt and decrypt the supplied text. - * @return decrypted text ornull
if one of - *encryptedText
orkey
isnull
. - */ - public static String decrypt( - String encryptedText, EncryptionKey encryptionKey) { - if (encryptedText == null) { - return null; - } - if (encryptionKey == null) { - return encryptedText; - } - String key = encryptionKey.resolve(); - if (key == null) { - return encryptedText; - } - - switch (encryptionKey.getAlgorithm()) { - case LEGACY: - return decryptLegacy(encryptedText, key); - case STRONG: - return decryptStrong(encryptedText, key); - case AUTO: - default: - return decryptAutomatic(encryptedText, key); - } - } } diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java index 3cba4178..8d092cdb 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java @@ -16,37 +16,15 @@ import org.junit.Assert; import org.junit.Test; -import org.junit.Assume; public class EncryptionUtilTest { - public static final String ENVIRONMENT_KEY = "TEST_ENCRYPT_KEY"; - public static final String PROPERTY_KEY = "test.encrypt.key"; - public static final String CLEAR_TEXT = "please encrypt this text."; - @Test public void testEncrypt() { EncryptionKey key = new EncryptionKey("this is my secret key."); - String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key); - String decryptedText = EncryptionUtil.decrypt(encryptedText, key); - Assert.assertEquals(CLEAR_TEXT, decryptedText); - } - - @Test - public void testEnvironmentEncrypt() { - Assume.assumeNotNull(System.getenv(ENVIRONMENT_KEY)); - EncryptionKey key = new EncryptionKey(ENVIRONMENT_KEY, EncryptionKey.Source.ENVIRONMENT); - String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key); - String decryptedText = EncryptionUtil.decrypt(encryptedText, key); - Assert.assertEquals(CLEAR_TEXT, decryptedText); - } - - @Test - public void testPropertyEncrypt() { - Assume.assumeNotNull(System.getProperty(PROPERTY_KEY)); - EncryptionKey key = new EncryptionKey(PROPERTY_KEY, EncryptionKey.Source.PROPERTY); - String encryptedText = EncryptionUtil.encrypt(CLEAR_TEXT, key); + String text = "please encrypt this text."; + String encryptedText = EncryptionUtil.encrypt(text, key); String decryptedText = EncryptionUtil.decrypt(encryptedText, key); - Assert.assertEquals(CLEAR_TEXT, decryptedText); + Assert.assertEquals(text, decryptedText); } } From 2f7a645bf42b2562d6653dbb86f5e840c37e1e00 Mon Sep 17 00:00:00 2001 From: Pascal EssiembreDate: Thu, 25 Jan 2018 23:13:37 -0500 Subject: [PATCH 04/15] ConfigurationLoader now sets the Velocity character encoding to UTF-8. Javadoc fixes. --- norconex-commons-lang/pom.xml | 2 +- norconex-commons-lang/src/changes/changes.xml | 6 ++++++ .../java/com/norconex/commons/lang/ClassFinder.java | 6 ++---- .../main/java/com/norconex/commons/lang/Sleeper.java | 3 +-- .../java/com/norconex/commons/lang/StringUtil.java | 10 +++++++++- .../commons/lang/config/ConfigurationLoader.java | 4 +++- .../com/norconex/commons/lang/net/ProxySettings.java | 1 + 7 files changed, 23 insertions(+), 9 deletions(-) diff --git a/norconex-commons-lang/pom.xml b/norconex-commons-lang/pom.xml index 06e2fa1e..552c1c56 100644 --- a/norconex-commons-lang/pom.xml +++ b/norconex-commons-lang/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.norconex.commons norconex-commons-lang -1.14.0 +1.14.1-SNAPSHOT jar Norconex Commons Lang diff --git a/norconex-commons-lang/src/changes/changes.xml b/norconex-commons-lang/src/changes/changes.xml index 00766a0e..1ceff371 100644 --- a/norconex-commons-lang/src/changes/changes.xml +++ b/norconex-commons-lang/src/changes/changes.xml @@ -7,6 +7,12 @@ ++ ++ ConfigurationLoader now sets the Velocity character encoding to UTF-8. + +Can now store and load Properties file as JSON. diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/ClassFinder.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/ClassFinder.java index c779ea8b..85aeaa9d 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/ClassFinder.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/ClassFinder.java @@ -1,4 +1,4 @@ -/* Copyright 2010-2016 Norconex Inc. +/* Copyright 2010-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ * * @author Pascal Essiembre */ -@SuppressWarnings("nls") public final class ClassFinder { private static final Logger LOG = Logger.getLogger(ClassFinder.class); @@ -127,8 +126,7 @@ public static List findSubTypes( return new ArrayList<>(); } if (file.isDirectory()) { - return findSubTypesFromDirectory( - new File(file.getAbsolutePath() + "/"), superClass); + return findSubTypesFromDirectory(file, superClass); } if (file.getName().endsWith(".jar")) { return findSubTypesFromJar(file, superClass); diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/Sleeper.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/Sleeper.java index 877df953..3942eb5b 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/Sleeper.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/Sleeper.java @@ -1,4 +1,4 @@ -/* Copyright 2010-2014 Norconex Inc. +/* Copyright 2010-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ * * @author Pascal Essiembre */ -@SuppressWarnings("nls") public final class Sleeper { /** Number of milliseconds representing 1 second. */ diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java index c4d44940..2dcea953 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java @@ -1,4 +1,4 @@ -/* Copyright 2017 Norconex Inc. +/* Copyright 2017-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,9 +110,11 @@ public static String truncateWithHash( * For this reason, the maxByteLength
argument must be * be large enough for any truncation to occur. * @param text text to truncate + * @param charset character encoding * @param maxByteLength maximum byte length the truncated text must have * @return truncated character byte array, or original text if no * truncation required + * @throws CharacterCodingException */ public static String truncateBytesWithHash(String text, Charset charset, int maxByteLength) @@ -135,10 +137,12 @@ public static String truncateBytesWithHash(String text, * For this reason, themaxByteLength
argument must be * be large enough for any truncation to occur. * @param text text to truncate + * @param charset character encoding * @param maxByteLength maximum byte length the truncated text must have * @param separator string separating truncated text from hash code * @return truncated character byte array, or original text if no * truncation required + * @throws CharacterCodingException */ public static String truncateBytesWithHash(String text, Charset charset, int maxByteLength, String separator) @@ -161,9 +165,11 @@ public static String truncateBytesWithHash(String text, * For this reason, themaxByteLength
argument must be * be large enough for any truncation to occur. * @param bytes byte array of text to truncate + * @param charset character encoding * @param maxByteLength maximum byte length the truncated text must have * @return truncated character byte array, or original text if no * truncation required + * @throws CharacterCodingException */ public static byte[] truncateBytesWithHash( byte[] bytes, Charset charset, int maxByteLength) @@ -184,10 +190,12 @@ public static byte[] truncateBytesWithHash( * For this reason, themaxByteLength
argument must be * be large enough for any truncation to occur. * @param bytes byte array of text to truncate + * @param charset character encoding * @param maxByteLength maximum byte length the truncated text must have * @param separator string separating truncated text from hash code * @return truncated character byte array, or original text if no * truncation required + * @throws CharacterCodingException */ public static byte[] truncateBytesWithHash( byte[] bytes, Charset charset, int maxByteLength, String separator) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java index 5013a422..abff177c 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java @@ -121,7 +121,6 @@ * * @author Pascal Essiembre */ -@SuppressWarnings("nls") public final class ConfigurationLoader { private static final String EXTENSION_PROPERTIES = ".properties"; @@ -139,6 +138,9 @@ public ConfigurationLoader() { velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file"); velocityEngine.setProperty( RuntimeConstants.FILE_RESOURCE_LOADER_PATH, ""); + velocityEngine.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8"); + velocityEngine.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8"); + velocityEngine.setProperty(RuntimeConstants.ENCODING_DEFAULT, "UTF-8"); velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.Log4JLogChute"); velocityEngine.setProperty("runtime.log", ""); diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java index b198fc1d..9922a49b 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java @@ -214,6 +214,7 @@ public void saveToXML(Writer out) throws IOException { /** * Saves assuming we are already in a parent tag. * @param out XML stream writer + * @throws XMLStreamException */ public void saveProxyToXML(XMLStreamWriter out) throws XMLStreamException { EnhancedXMLStreamWriter writer; From c31450cd50d9cc294c8a0ed380fae41625242c93 Mon Sep 17 00:00:00 2001 From: Pascal EssiembreDate: Thu, 25 Jan 2018 23:18:02 -0500 Subject: [PATCH 05/15] Javadoc fixes. --- .../main/java/com/norconex/commons/lang/StringUtil.java | 8 ++++---- .../java/com/norconex/commons/lang/net/ProxySettings.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java index 2dcea953..719d58dc 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/StringUtil.java @@ -114,7 +114,7 @@ public static String truncateWithHash( * @param maxByteLength maximum byte length the truncated text must have * @return truncated character byte array, or original text if no * truncation required - * @throws CharacterCodingException + * @throws CharacterCodingException character coding problem */ public static String truncateBytesWithHash(String text, Charset charset, int maxByteLength) @@ -142,7 +142,7 @@ public static String truncateBytesWithHash(String text, * @param separator string separating truncated text from hash code * @return truncated character byte array, or original text if no * truncation required - * @throws CharacterCodingException + * @throws CharacterCodingException character coding problem */ public static String truncateBytesWithHash(String text, Charset charset, int maxByteLength, String separator) @@ -169,7 +169,7 @@ public static String truncateBytesWithHash(String text, * @param maxByteLength maximum byte length the truncated text must have * @return truncated character byte array, or original text if no * truncation required - * @throws CharacterCodingException + * @throws CharacterCodingException character coding problem */ public static byte[] truncateBytesWithHash( byte[] bytes, Charset charset, int maxByteLength) @@ -195,7 +195,7 @@ public static byte[] truncateBytesWithHash( * @param separator string separating truncated text from hash code * @return truncated character byte array, or original text if no * truncation required - * @throws CharacterCodingException + * @throws CharacterCodingException character coding problem */ public static byte[] truncateBytesWithHash( byte[] bytes, Charset charset, int maxByteLength, String separator) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java index 9922a49b..e4ac64d9 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java @@ -214,7 +214,7 @@ public void saveToXML(Writer out) throws IOException { /** * Saves assuming we are already in a parent tag. * @param out XML stream writer - * @throws XMLStreamException + * @throws XMLStreamException problem saving stream to XML */ public void saveProxyToXML(XMLStreamWriter out) throws XMLStreamException { EnhancedXMLStreamWriter writer; From 88f04a48b548afa6d1f57dd939baf8f52294bc72 Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Thu, 25 Jan 2018 23:29:19 -0500 Subject: [PATCH 06/15] Updated site. --- norconex-commons-lang/src/site/markdown/download.md.vm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/norconex-commons-lang/src/site/markdown/download.md.vm b/norconex-commons-lang/src/site/markdown/download.md.vm index b53c2f5c..9d6d1984 100644 --- a/norconex-commons-lang/src/site/markdown/download.md.vm +++ b/norconex-commons-lang/src/site/markdown/download.md.vm @@ -30,6 +30,10 @@ $h2 Binaries **Older Releases** + * [1.13.1]($nexusPath/1.13.1/norconex-commons-lang-1.13.1.zip) + [[Release Notes](changes-report.html#a1.13.1)] + * [1.13.0]($nexusPath/1.13.0/norconex-commons-lang-1.13.0.zip) + [[Release Notes](changes-report.html#a1.13.0)] * [1.12.3]($nexusPath/1.12.2/norconex-commons-lang-1.12.3.zip) [[Release Notes](changes-report.html#a1.12.3)] * [1.12.2]($nexusPath/1.12.2/norconex-commons-lang-1.12.2.zip) From 6d313455213561539d84864dc1321a5d0c49ce3d Mon Sep 17 00:00:00 2001 From: Dan Davis Date: Fri, 26 Jan 2018 18:09:45 -0500 Subject: [PATCH 07/15] Resolve issue #5 - New test assures that the same clear text is encrypted differently each time - New test assures that a text encrypted with the old algorithm can be decrypted by the new code. --- .../commons/lang/encrypt/EncryptionUtil.java | 184 ++++++++++++------ .../lang/encrypt/EncryptionUtilTest.java | 18 ++ 2 files changed, 140 insertions(+), 62 deletions(-) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java index df321c5e..56a2def8 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java @@ -15,25 +15,40 @@ package com.norconex.commons.lang.encrypt; import java.io.PrintStream; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; +import java.util.Arrays; +import javax.crypto.BadPaddingException; import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +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; +import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import com.norconex.commons.lang.encrypt.EncryptionKey.Source; +import com.norconex.commons.lang.io.ByteArrayOutputStream; /** - * Simplified encryption and decryption methods using the - * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you + *
Simplified encryption and decryption methods using the + * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you * can also think of as a passphrase, or password). * The "salt" and iteration count used by this class are hard-coded. To have - * more control and ensure a more secure approach, you should rely on another + * more control and ensure a more secure approach, you should rely on another * implementation or create your own. *
*@@ -44,7 +59,7 @@ * java -cp norconex-commons-lang-[version].jar com.norconex.commons.lang.encrypt.EncryptionUtil *
- * For example, to use a encryption key store in a file to encrypt some text, + * For example, to use a encryption key store in a file to encrypt some text, * add the following arguments to the above command: *
*@@ -54,12 +69,12 @@ * As of 1.13.0, you can also use theencrypt.[sh|bat]
and *decrypt.[sh|bat]
files distributed with this library. * - * + * * @author Pascal Essiembre * @since 1.9.0 */ public class EncryptionUtil { - + private EncryptionUtil() { super(); } @@ -72,7 +87,7 @@ public static void main(String[] args) { String typeArg = args[1]; String keyArg = args[2]; String textArg = args[3]; - + Source type = null; if ("-k".equalsIgnoreCase(typeArg)) { type = Source.KEY; @@ -86,7 +101,7 @@ public static void main(String[] args) { System.err.println("Unsupported type of key: " + type); printUsage(); } - + EncryptionKey key = new EncryptionKey(keyArg, type); if ("encrypt".equalsIgnoreCase(cmdArg)) { System.out.println(encrypt(textArg, key)); @@ -116,19 +131,26 @@ private static void printUsage() { out.println(" text text to encrypt or decrypt"); System.exit(-1); } - + /** *Encrypts the given text with the encryption key supplied. If the * encryption key is
* @param textToEncrypt text to be encrypted - * @param encryptionKey encryption key which must resolve to the same + * @param encryptionKey encryption key which must resolve to the same * value to encrypt and decrypt the supplied text. - * @return encrypted text ornull
or resolves to blank key, * the text to encrypt will be returned unmodified.null
if + * @return encrypted text ornull
if *textToEncrypt
isnull
. + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException */ public static String encrypt( String textToEncrypt, EncryptionKey encryptionKey) { + // 8-byte Salt + byte[] salt = { + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 + }; if (textToEncrypt == null) { return null; } @@ -139,51 +161,57 @@ public static String encrypt( if (key == null) { return textToEncrypt; } - - // 8-byte Salt - byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, - (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 - }; + // Iteration count - int iterationCount = 19; + int iterationCount = 1000; + int keySize = 128; Cipher ecipher; try { // Create the key KeySpec keySpec = new PBEKeySpec( - key.trim().toCharArray(), salt, iterationCount); - SecretKey secretKey = SecretKeyFactory.getInstance( - "PBEWithMD5AndDES").generateSecret(keySpec); - ecipher = Cipher.getInstance(secretKey.getAlgorithm()); - - // Prepare the parameter to the ciphers - AlgorithmParameterSpec paramSpec = - new PBEParameterSpec(salt, iterationCount); - - // Create the ciphers - ecipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); - + key.trim().toCharArray(), salt, iterationCount, keySize); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKey secretKeyTemp = factory.generateSecret(keySpec); + SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); + + ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + ecipher.init(Cipher.ENCRYPT_MODE, secretKey); + + AlgorithmParameters params = ecipher.getParameters(); + + byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] utf8 = textToEncrypt.trim().getBytes(StandardCharsets.UTF_8); - byte[] enc = ecipher.doFinal(utf8); - - return DatatypeConverter.printBase64Binary(enc); + byte[] cipherBytes = ecipher.doFinal(utf8); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(iv); + bos.write(cipherBytes); + bos.close(); + byte[] cryptMessage = bos.toByteArray(); + + return DatatypeConverter.printBase64Binary(cryptMessage); } catch (Exception e) { throw new EncryptionException("Encryption failed.", e); } } - + /** *Decrypts the given encrypted text with the encryption key supplied. *
* @param encryptedText text to be decrypted - * @param encryptionKey encryption key which must resolve to the same + * @param encryptionKey encryption key which must resolve to the same * value to encrypt and decrypt the supplied text. - * @return decrypted text ornull
if one of + * @return decrypted text ornull
if one of *encryptedText
orkey
isnull
. - */ + */ public static String decrypt( String encryptedText, EncryptionKey encryptionKey) { + // 8-byte Salt + byte[] salt = { + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 + }; if (encryptedText == null) { return null; } @@ -193,38 +221,70 @@ public static String decrypt( String key = encryptionKey.resolve(); if (key == null) { return encryptedText; - } - - // 8-byte Salt - byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, - (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 - }; + } + // Iteration count - int iterationCount = 19; + int iterationCount = 1000; + int keySize = 128; Cipher dcipher; try { + // Separate the encrypted data into the salt and the encrypted message + byte[] cryptMessage = DatatypeConverter.parseBase64Binary(encryptedText.trim()); + byte[] iv = Arrays.copyOf(cryptMessage, 16); + byte[] cryptBytes = Arrays.copyOfRange(cryptMessage, 16, cryptMessage.length); + // Create the key KeySpec keySpec = new PBEKeySpec( - key.trim().toCharArray(), salt, iterationCount); - SecretKey secretKey = SecretKeyFactory.getInstance( - "PBEWithMD5AndDES").generateSecret(keySpec); - dcipher = Cipher.getInstance(secretKey.getAlgorithm()); - - // Prepare the parameter to the ciphers - AlgorithmParameterSpec paramSpec = - new PBEParameterSpec(salt, iterationCount); - - // Create the ciphers - dcipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); - - byte[] dec = - DatatypeConverter.parseBase64Binary(encryptedText.trim()); - byte[] utf8 = dcipher.doFinal(dec); + key.trim().toCharArray(), salt, iterationCount, keySize); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKey secretKeyTemp = factory.generateSecret(keySpec); + SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); + + IvParameterSpec ivParamSpec = new IvParameterSpec(iv); + + dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + dcipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec); + + byte[] utf8 = dcipher.doFinal(cryptBytes); return new String(utf8, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new EncryptionException("Decryption failed.", e); + } catch (Exception original) { + try { + return decryptLegacy(encryptedText, key); + } catch (GeneralSecurityException subsequent) { + throw new EncryptionException("Decryption failed.", original); + } } } + + private static String decryptLegacy(String encryptedText, String key) throws GeneralSecurityException { + // 8-byte Salt + byte[] salt = { + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 + }; + // Iteration count + int iterationCount = 19; + Cipher dcipher; + + // Create the key + KeySpec keySpec = new PBEKeySpec( + key.trim().toCharArray(), salt, iterationCount); + SecretKey secretKey = SecretKeyFactory.getInstance( + "PBEWithMD5AndDES").generateSecret(keySpec); + dcipher = Cipher.getInstance(secretKey.getAlgorithm()); + + // Prepare the parameter to the ciphers + AlgorithmParameterSpec paramSpec = + new PBEParameterSpec(salt, iterationCount); + + // Create the ciphers + dcipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + + byte[] dec = + DatatypeConverter.parseBase64Binary(encryptedText.trim()); + byte[] utf8 = dcipher.doFinal(dec); + return new String(utf8, StandardCharsets.UTF_8); + } + } diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java index 8d092cdb..5fe1d69f 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java @@ -27,4 +27,22 @@ public void testEncrypt() { String decryptedText = EncryptionUtil.decrypt(encryptedText, key); Assert.assertEquals(text, decryptedText); } + + @Test + public void testEncryptTwice() { + EncryptionKey key = new EncryptionKey("this is my secret key."); + String text = "please encrypt this text."; + String encryptedText1 = EncryptionUtil.encrypt(text, key); + String encryptedText2 = EncryptionUtil.encrypt(text, key); + Assert.assertNotEquals(encryptedText1, encryptedText2); + } + + @Test + public void testDecryptLegacy() { + EncryptionKey key = new EncryptionKey("This is an encryption key"); + String expectedClearText = "Please encrypt this text"; + String encryptedText = "aeEFKa0uXMUHT4UyeFtuHjm37NQw3vEaxY03EkkD2qM="; + String actualClearText = EncryptionUtil.decrypt(encryptedText, key); + Assert.assertEquals(expectedClearText, actualClearText); + } } From 4d5ff2d266bd7694ae0336a5d9e3336222fe7827 Mon Sep 17 00:00:00 2001 From: Dan DavisDate: Mon, 29 Jan 2018 14:53:25 -0500 Subject: [PATCH 08/15] Address issue #8 by adding a size to the encyption key - default size is 128, supported without JCE Unlimited Strength and in older Java. - An explicit size can be passed to `EncryptionKey`, and is supported by `PasswordKeyUtil` - `PasswordKeyUtil` now used by `...net.ProxySettings` --- .../commons/lang/config/PasswordKeyUtil.java | 5 +- .../commons/lang/encrypt/EncryptionKey.java | 77 ++++++++++++++----- .../commons/lang/encrypt/EncryptionUtil.java | 4 +- .../commons/lang/net/ProxySettings.java | 46 +++-------- .../lang/encrypt/EncryptionUtilTest.java | 21 +++++ 5 files changed, 94 insertions(+), 59 deletions(-) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java index 60b6ae0d..8cafa866 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java @@ -30,12 +30,13 @@ public static EncryptionKey loadKeyFrom(Reader in, EncryptionKey defaultKey) { public static EncryptionKey loadKeyFrom(XMLConfiguration xml, EncryptionKey defaultKey) { String xmlKey = xml.getString("passwordKey", null); String xmlSource = xml.getString("passwordKeySource", null); + Integer size = xml.getInteger("passwordKeySize", EncryptionKey.DEFAULT_KEY_SIZE); if (StringUtils.isNotBlank(xmlKey)) { EncryptionKey.Source source = null; if (StringUtils.isNotBlank(xmlSource)) { source = EncryptionKey.Source.valueOf(xmlSource.toUpperCase()); } - return new EncryptionKey(xmlKey, source); + return new EncryptionKey(xmlKey, source, size); } return defaultKey; } @@ -54,11 +55,11 @@ public static void saveKeyTo(Writer out, EncryptionKey passwordKey) throws IOExc public static void saveKeyTo(EnhancedXMLStreamWriter writer, EncryptionKey passwordKey) throws XMLStreamException { if (passwordKey != null) { writer.writeElementString("passwordKey", passwordKey.getValue()); + writer.writeElementInteger("passwordKeySize", passwordKey.getSize()); if (passwordKey.getSource() != null) { writer.writeElementString("passwordKeySource", passwordKey.getSource().name().toLowerCase()); } } } - } diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java index e8cc98bc..a6c8c5c6 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java @@ -22,8 +22,8 @@ import java.nio.file.Paths; /** - * Pointer to the an encryption key, or the encryption key itself. An - * encryption key can be seen as equivalent to a secret key, + * Pointer to the an encryption key, or the encryption key itself. An + * encryption key can be seen as equivalent to a secret key, * passphrase or password. * @author Pascal Essiembre * @since 1.9.0 @@ -33,38 +33,66 @@ public class EncryptionKey implements Serializable { private static final long serialVersionUID = 1L; - public enum Source { + public static final int DEFAULT_KEY_SIZE = 128; + + public enum Source { /** Value is the actual key. */ - KEY, + KEY, /** Value is the path to a file containing the key. */ - FILE, + FILE, /** Value is the name of an environment variable containing the key. */ - ENVIRONMENT, + ENVIRONMENT, /** Value is the name of a JVM system property containing the key. */ PROPERTY } private final String value; + private final Integer size; private final Source source; - + /** - * Creates a new reference to an encryption key. The reference can either + * Creates a new reference to an encryption key. The reference can either * be the key itself, or a pointer to a file or environment variable - * containing the key (as defined by the supplied value type). + * containing the key (as defined by the supplied value type). The actual value + * can be any sort of string, and it is converted to an encryption key of length size + * using cryptographic algorithms. If the size is specified, it must be supported by + * your version of Java. + * * @param value the encryption key + * @param size the size in bits of the encryption key * @param source the type of value */ - public EncryptionKey(String value, Source source) { + public EncryptionKey(String value, Source source, int size) { super(); this.value = value; this.source = source; + this.size = size; + } + /** + * Creates a new reference to an encryption key. The reference can either + * be the key itself, or a pointer to a file or environment variable + * containing the key (as defined by the supplied value type). + * @param value the encryption key + * @param source the type of value + */ + public EncryptionKey(String value, Source source) { + this(value, source, DEFAULT_KEY_SIZE); + } + /** + * Creates a new encryption key where the value is the actual key, and the number + * of key bits to generate is the size. + * @param value the encrption key + * @param size the encryption key size in bits + */ + public EncryptionKey(String value, int size) { + this(value, Source.KEY, size); } /** * Creates a new encryption key where the value is the actual key. * @param value the encryption key */ public EncryptionKey(String value) { - this(value, Source.KEY); + this(value, Source.KEY, DEFAULT_KEY_SIZE); } public String getValue() { return value; @@ -72,13 +100,16 @@ public String getValue() { public Source getSource() { return source; } + public int getSize() { + return (size != null ? size : DEFAULT_KEY_SIZE); + } /** - * Locate the key according to its value type and return it. This - * method will always resolve the value each type it is invoked and + * Locate the key according to its value type and return it. This + * method will always resolve the value each type it is invoked and * never caches the key, unless the key value specified at construction * time is the actual key. - * @return encryption key or null
if the key does not exist + * @return encryption key ornull
if the key does not exist * for the specified type */ public String resolve() { @@ -101,12 +132,12 @@ public String resolve() { return null; } } - + private String fromEnv() { //TODO allow a flag to optionally throw an exception when null? return System.getenv(value); } - + private String fromProperty() { //TODO allow a flag to optionally throw an exception when null? return System.getProperty(value); @@ -128,7 +159,7 @@ private String fromFile() { "Could not read key file.", e); } } - + //Do not use Apache Commons Lang below to avoid any dependency //when used on command-line with EncryptionUtil. @Override @@ -137,6 +168,7 @@ public int hashCode() { int result = 1; result = prime * result + ((source == null) ? 0 : source.hashCode()); result = prime * result + ((value == null) ? 0 : value.hashCode()); + result = prime * result + size; return result; } @Override @@ -161,10 +193,17 @@ public boolean equals(Object obj) { } else if (!value.equals(other.value)) { return false; } + if (size == null) { + if (other.size != null) { + return false; + } + } else if (!size.equals(other.size)) { + return false; + } return true; } @Override public String toString() { - return "EncryptionKey [value=" + value + ", source=" + source + "]"; - } + return "EncryptionKey [value=" + value + ", source=" + source + ", size=" + size + "]"; + } } diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java index 56a2def8..8ed8c6ce 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java @@ -164,7 +164,7 @@ public static String encrypt( // Iteration count int iterationCount = 1000; - int keySize = 128; + int keySize = encryptionKey.getSize(); Cipher ecipher; try { @@ -225,7 +225,7 @@ public static String decrypt( // Iteration count int iterationCount = 1000; - int keySize = 128; + int keySize = encryptionKey.getSize(); Cipher dcipher; try { diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java index b198fc1d..06b88b8d 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java @@ -36,6 +36,7 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import com.norconex.commons.lang.config.IXMLConfigurable; +import com.norconex.commons.lang.config.PasswordKeyUtil; import com.norconex.commons.lang.config.XMLConfigurationUtil; import com.norconex.commons.lang.encrypt.EncryptionKey; import com.norconex.commons.lang.encrypt.EncryptionUtil; @@ -43,7 +44,7 @@ /** * Convenience class for implementation requiring proxy settings. - * + * * @author Pascal Essiembre * @since 1.14.0 */ @@ -58,7 +59,7 @@ public class ProxySettings implements IXMLConfigurable, Serializable { private String proxyPassword; private EncryptionKey proxyPasswordKey; private String proxyRealm; - + public ProxySettings() { super(); } @@ -120,7 +121,7 @@ public ProxySettings setProxyRealm(String proxyRealm) { public boolean isSet() { return StringUtils.isNotBlank(proxyHost); } - + public void copyFrom(ProxySettings another) { proxyHost = another.proxyHost; proxyPort = another.proxyPort; @@ -130,7 +131,7 @@ public void copyFrom(ProxySettings another) { proxyPasswordKey = another.proxyPasswordKey; proxyRealm = another.proxyRealm; } - + /** * Creates an Apache {@link HttpHost}. * @return HttpHost ornull
if proxy is not set. @@ -149,7 +150,7 @@ public AuthScope createAuthScope() { } public Credentials createCredentials() { if (isSet() && StringUtils.isNotBlank(proxyUsername)) { - String password = + String password = EncryptionUtil.decrypt(proxyPassword, proxyPasswordKey); return new UsernamePasswordCredentials(proxyUsername, password); } @@ -170,7 +171,7 @@ public CredentialsProvider createCredentialsProvider() { protected String getXmlTag() { return "proxy"; } - + /** * Loads from a {@link #getXmlTag()} tag. * @param in XML reader @@ -189,8 +190,7 @@ public void loadProxyFromXML(XMLConfiguration xml) { proxyScheme = xml.getString("proxyScheme", proxyScheme); proxyUsername = xml.getString("proxyUsername", proxyUsername); proxyPassword = xml.getString("proxyPassword", proxyPassword); - proxyPasswordKey = - loadXMLPasswordKey(xml, "proxyPasswordKey", proxyPasswordKey); + proxyPasswordKey = PasswordKeyUtil.loadKeyFrom(xml, proxyPasswordKey); proxyRealm = xml.getString("proxyRealm", proxyRealm); } /** @@ -227,36 +227,10 @@ public void saveProxyToXML(XMLStreamWriter out) throws XMLStreamException { writer.writeElementString("proxyScheme", proxyScheme); writer.writeElementString("proxyUsername", proxyUsername); writer.writeElementString("proxyPassword", proxyPassword); - saveXMLPasswordKey(writer, "proxyPasswordKey", proxyPasswordKey); + PasswordKeyUtil.saveKeyTo(writer, proxyPasswordKey); writer.writeElementString("proxyRealm", proxyRealm); } - private void saveXMLPasswordKey(EnhancedXMLStreamWriter writer, - String field, EncryptionKey key) throws XMLStreamException { - if (key == null) { - return; - } - writer.writeElementString(field, key.getValue()); - if (key.getSource() != null) { - writer.writeElementString( - field + "Source", key.getSource().name().toLowerCase()); - } - } - - private EncryptionKey loadXMLPasswordKey( - XMLConfiguration xml, String field, EncryptionKey defaultKey) { - String xmlKey = xml.getString(field, null); - String xmlSource = xml.getString(field + "Source", null); - if (StringUtils.isBlank(xmlKey)) { - return defaultKey; - } - EncryptionKey.Source source = null; - if (StringUtils.isNotBlank(xmlSource)) { - source = EncryptionKey.Source.valueOf(xmlSource.toUpperCase()); - } - return new EncryptionKey(xmlKey, source); - } - @Override public boolean equals(final Object other) { if (!(other instanceof ProxySettings)) { @@ -298,5 +272,5 @@ public String toString() { .append("proxyPasswordKey", proxyPasswordKey) .append("proxyRealm", proxyRealm) .toString(); - } + } } diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java index 5fe1d69f..76ccec8c 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java @@ -14,9 +14,15 @@ */ package com.norconex.commons.lang.encrypt; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; + import org.junit.Assert; +import org.junit.Assume; import org.junit.Test; + public class EncryptionUtilTest { @Test @@ -45,4 +51,19 @@ public void testDecryptLegacy() { String actualClearText = EncryptionUtil.decrypt(encryptedText, key); Assert.assertEquals(expectedClearText, actualClearText); } + + @Test + public void testAes256bitEncryptionKey() throws NoSuchAlgorithmException { + + // NOTE: this test should be true on Java 8 u162+ or on Java 9, or on any Java where JCE Unlimited Strength has been applied + Assume.assumeTrue(Cipher.getMaxAllowedKeyLength("AES") >= 256); + + // Create round-trip encryption key + EncryptionKey key = new EncryptionKey("This as an encryption key", 256); + String text = "please encrypt this text"; + String encryptedText = EncryptionUtil.encrypt(text, key); + String decryptedText = EncryptionUtil.decrypt(encryptedText, key); + + Assert.assertEquals(text, decryptedText); + } } From f7835092ffae8a1fb506bbc3fab3d7e2b8ef5df5 Mon Sep 17 00:00:00 2001 From: EC2 Default UserDate: Thu, 1 Feb 2018 03:50:53 +0000 Subject: [PATCH 09/15] Work with the best key derivation that JCE 1.7 provides. - While Java 1.8 includes PBKDF2WithHmacSHA256, this is not available with Java 1.7 To support Java 1.7, we will use PBKDF2WithHmacSHA1. --- .../com/norconex/commons/lang/encrypt/EncryptionUtil.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java index 8ed8c6ce..1225c1e7 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java @@ -171,7 +171,8 @@ public static String encrypt( // Create the key KeySpec keySpec = new PBEKeySpec( key.trim().toCharArray(), salt, iterationCount, keySize); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKey secretKeyTemp = factory.generateSecret(keySpec); SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); @@ -237,7 +238,7 @@ public static String decrypt( // Create the key KeySpec keySpec = new PBEKeySpec( key.trim().toCharArray(), salt, iterationCount, keySize); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); SecretKey secretKeyTemp = factory.generateSecret(keySpec); SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); From 1717d86a8daa2094c934f8b2e1604afbbba4fd07 Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Sat, 3 Feb 2018 14:12:04 -0500 Subject: [PATCH 10/15] Removed unused Velocity config statement. --- .../com/norconex/commons/lang/config/ConfigurationLoader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java index abff177c..9087856d 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java @@ -140,7 +140,6 @@ public ConfigurationLoader() { RuntimeConstants.FILE_RESOURCE_LOADER_PATH, ""); velocityEngine.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8"); velocityEngine.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8"); - velocityEngine.setProperty(RuntimeConstants.ENCODING_DEFAULT, "UTF-8"); velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.Log4JLogChute"); velocityEngine.setProperty("runtime.log", ""); From bec258bff8d8fad419c6178c24b3d35ffa4ea463 Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Sun, 11 Mar 2018 15:59:53 -0400 Subject: [PATCH 11/15] Pull-request #7 integration. --- norconex-commons-lang/pom.xml | 2 +- norconex-commons-lang/src/changes/changes.xml | 9 +- .../commons/lang/config/PasswordKeyUtil.java | 65 -------- .../commons/lang/encrypt/EncryptionKey.java | 23 ++- .../commons/lang/encrypt/EncryptionUtil.java | 92 ++++++----- .../lang/encrypt/EncryptionXMLUtil.java | 149 ++++++++++++++++++ .../commons/lang/net/ProxySettings.java | 30 ++-- .../lang/encrypt/EncryptionUtilTest.java | 5 +- .../commons/lang/net/ProxySettingsTest.java | 4 +- 9 files changed, 240 insertions(+), 139 deletions(-) delete mode 100644 norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java create mode 100644 norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionXMLUtil.java diff --git a/norconex-commons-lang/pom.xml b/norconex-commons-lang/pom.xml index 552c1c56..ef28aa53 100644 --- a/norconex-commons-lang/pom.xml +++ b/norconex-commons-lang/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.norconex.commons norconex-commons-lang -1.14.1-SNAPSHOT +1.15.0-SNAPSHOT jar Norconex Commons Lang diff --git a/norconex-commons-lang/src/changes/changes.xml b/norconex-commons-lang/src/changes/changes.xml index 1ceff371..1ec06972 100644 --- a/norconex-commons-lang/src/changes/changes.xml +++ b/norconex-commons-lang/src/changes/changes.xml @@ -7,7 +7,14 @@ -+ + + New EncryptionXMLUtil class offering methods to facilitate integration + of EncryptionKey with IXMLConfigurable objects (or other XML objects). + ++ EncryptionUtil now uses AES for encryption and supports custom key size. + ConfigurationLoader now sets the Velocity character encoding to UTF-8. diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java deleted file mode 100644 index 8cafa866..00000000 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/config/PasswordKeyUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.norconex.commons.lang.config; - -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; - -import javax.xml.stream.XMLStreamException; - -import org.apache.commons.configuration.XMLConfiguration; -import org.apache.commons.lang3.StringUtils; - -import com.norconex.commons.lang.encrypt.EncryptionKey; -import com.norconex.commons.lang.encrypt.EncryptionUtil; -import com.norconex.commons.lang.xml.EnhancedXMLStreamWriter; - -/** - *Allows many classes implementing {@link IXMLConfigurable} to load and store an {@link EncryptionKey} in a standard manner through delegation.
- * - * @author davisda4 - * @since 1.9.1 - * @see EncryptionUtil - * - */ -public class PasswordKeyUtil { - public static EncryptionKey loadKeyFrom(Reader in, EncryptionKey defaultKey) { - XMLConfiguration xml = XMLConfigurationUtil.newXMLConfiguration(in); - return loadKeyFrom(xml, defaultKey); - } - - public static EncryptionKey loadKeyFrom(XMLConfiguration xml, EncryptionKey defaultKey) { - String xmlKey = xml.getString("passwordKey", null); - String xmlSource = xml.getString("passwordKeySource", null); - Integer size = xml.getInteger("passwordKeySize", EncryptionKey.DEFAULT_KEY_SIZE); - if (StringUtils.isNotBlank(xmlKey)) { - EncryptionKey.Source source = null; - if (StringUtils.isNotBlank(xmlSource)) { - source = EncryptionKey.Source.valueOf(xmlSource.toUpperCase()); - } - return new EncryptionKey(xmlKey, source, size); - } - return defaultKey; - } - - - public static void saveKeyTo(Writer out, EncryptionKey passwordKey) throws IOException { - try { - EnhancedXMLStreamWriter writer = new EnhancedXMLStreamWriter(out); - saveKeyTo(writer, passwordKey); - } catch (XMLStreamException e) { - throw new IOException("Cannot save as XML.", e); - - } - } - - public static void saveKeyTo(EnhancedXMLStreamWriter writer, EncryptionKey passwordKey) throws XMLStreamException { - if (passwordKey != null) { - writer.writeElementString("passwordKey", passwordKey.getValue()); - writer.writeElementInteger("passwordKeySize", passwordKey.getSize()); - if (passwordKey.getSource() != null) { - writer.writeElementString("passwordKeySource", - passwordKey.getSource().name().toLowerCase()); - } - } - } -} diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java index a6c8c5c6..e7f980ae 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionKey.java @@ -1,4 +1,4 @@ -/* Copyright 2015-2016 Norconex Inc. +/* Copyright 2015-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,10 +53,10 @@ public enum Source { /** * Creates a new reference to an encryption key. The reference can either * be the key itself, or a pointer to a file or environment variable - * containing the key (as defined by the supplied value type). The actual value - * can be any sort of string, and it is converted to an encryption key of length size - * using cryptographic algorithms. If the size is specified, it must be supported by - * your version of Java. + * containing the key (as defined by the supplied value type). The actual + * value can be any sort of string, and it is converted to an encryption + * key of length size using cryptographic algorithms. If the size is + * specified, it must be supported by your version of Java. * * @param value the encryption key * @param size the size in bits of the encryption key @@ -79,8 +79,8 @@ public EncryptionKey(String value, Source source) { this(value, source, DEFAULT_KEY_SIZE); } /** - * Creates a new encryption key where the value is the actual key, and the number - * of key bits to generate is the size. + * Creates a new encryption key where the value is the actual key, and the + * number of key bits to generate is the size. * @param value the encrption key * @param size the encryption key size in bits */ @@ -100,6 +100,12 @@ public String getValue() { public Source getSource() { return source; } + /** + * Gets the size in bits of the encryption key. Default is + * {@value #DEFAULT_KEY_SIZE}. + * @return size in bits of the encryption key + * @since 1.15.0 + */ public int getSize() { return (size != null ? size : DEFAULT_KEY_SIZE); } @@ -204,6 +210,7 @@ public boolean equals(Object obj) { } @Override public String toString() { - return "EncryptionKey [value=" + value + ", source=" + source + ", size=" + size + "]"; + return "EncryptionKey [value=" + value + + ", source=" + source + ", size=" + size + "]"; } } diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java index 1225c1e7..710fa2ea 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionUtil.java @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 Norconex Inc. +/* Copyright 2015-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,16 @@ */ package com.norconex.commons.lang.encrypt; +import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; @@ -41,15 +33,17 @@ import javax.xml.bind.DatatypeConverter; import com.norconex.commons.lang.encrypt.EncryptionKey.Source; -import com.norconex.commons.lang.io.ByteArrayOutputStream; /** *Simplified encryption and decryption methods using the - * "PBEWithMD5AndDES" algorithm with a supplied encryption key (which you - * can also think of as a passphrase, or password). - * The "salt" and iteration count used by this class are hard-coded. To have - * more control and ensure a more secure approach, you should rely on another - * implementation or create your own. + * + * Advanced Encryption Standard (AES) (since 1.15.0) with a supplied + * encryption key (which you can also think of as a passphrase, or password). + *
+ *+ * The "salt" and iteration count used by this class are hard-coded. To use + * a different encryption or have more control over its creation, + * you should rely on another implementation or create your own. *
** To use on the command prompt, use the following command to print usage @@ -141,16 +135,9 @@ private static void printUsage() { * value to encrypt and decrypt the supplied text. * @return encrypted text or
null
if *textToEncrypt
isnull
. - * @throws NoSuchAlgorithmException - * @throws UnsupportedEncodingException */ public static String encrypt( String textToEncrypt, EncryptionKey encryptionKey) { - // 8-byte Salt - byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, - (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 - }; if (textToEncrypt == null) { return null; } @@ -162,6 +149,11 @@ public static String encrypt( return textToEncrypt; } + // 8-byte Salt + byte[] salt = { + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 + }; // Iteration count int iterationCount = 1000; int keySize = encryptionKey.getSize(); @@ -171,10 +163,12 @@ public static String encrypt( // Create the key KeySpec keySpec = new PBEKeySpec( key.trim().toCharArray(), salt, iterationCount, keySize); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKeyFactory factory = + SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); SecretKey secretKeyTemp = factory.generateSecret(keySpec); - SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); + SecretKey secretKey = + new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); ecipher.init(Cipher.ENCRYPT_MODE, secretKey); @@ -185,13 +179,11 @@ public static String encrypt( byte[] utf8 = textToEncrypt.trim().getBytes(StandardCharsets.UTF_8); byte[] cipherBytes = ecipher.doFinal(utf8); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - bos.write(iv); - bos.write(cipherBytes); - bos.close(); - byte[] cryptMessage = bos.toByteArray(); - - return DatatypeConverter.printBase64Binary(cryptMessage); + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + bos.write(iv); + bos.write(cipherBytes); + return DatatypeConverter.printBase64Binary(bos.toByteArray()); + } } catch (Exception e) { throw new EncryptionException("Encryption failed.", e); } @@ -208,11 +200,6 @@ public static String encrypt( */ public static String decrypt( String encryptedText, EncryptionKey encryptionKey) { - // 8-byte Salt - byte[] salt = { - (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, - (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 - }; if (encryptedText == null) { return null; } @@ -224,23 +211,33 @@ public static String decrypt( return encryptedText; } + // 8-byte Salt + byte[] salt = { + (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, + (byte)0xC8, (byte)0x16, (byte)0x35, (byte)0x56 + }; // Iteration count int iterationCount = 1000; int keySize = encryptionKey.getSize(); Cipher dcipher; try { - // Separate the encrypted data into the salt and the encrypted message - byte[] cryptMessage = DatatypeConverter.parseBase64Binary(encryptedText.trim()); + // Separate the encrypted data into the salt and the + // encrypted message + byte[] cryptMessage = + DatatypeConverter.parseBase64Binary(encryptedText.trim()); byte[] iv = Arrays.copyOf(cryptMessage, 16); - byte[] cryptBytes = Arrays.copyOfRange(cryptMessage, 16, cryptMessage.length); + byte[] cryptBytes = Arrays.copyOfRange( + cryptMessage, 16, cryptMessage.length); // Create the key KeySpec keySpec = new PBEKeySpec( key.trim().toCharArray(), salt, iterationCount, keySize); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKeyFactory factory = + SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); SecretKey secretKeyTemp = factory.generateSecret(keySpec); - SecretKey secretKey = new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); + SecretKey secretKey = + new SecretKeySpec(secretKeyTemp.getEncoded(), "AES"); IvParameterSpec ivParamSpec = new IvParameterSpec(iv); @@ -251,6 +248,7 @@ public static String decrypt( return new String(utf8, StandardCharsets.UTF_8); } catch (Exception original) { try { + // Support for text encrypted before version 1.15.0. return decryptLegacy(encryptedText, key); } catch (GeneralSecurityException subsequent) { throw new EncryptionException("Decryption failed.", original); @@ -258,7 +256,8 @@ public static String decrypt( } } - private static String decryptLegacy(String encryptedText, String key) throws GeneralSecurityException { + private static String decryptLegacy(String encryptedText, String key) + throws GeneralSecurityException { // 8-byte Salt byte[] salt = { (byte)0xE3, (byte)0x03, (byte)0x9B, (byte)0xA9, @@ -282,10 +281,7 @@ private static String decryptLegacy(String encryptedText, String key) throws Gen // Create the ciphers dcipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); - byte[] dec = - DatatypeConverter.parseBase64Binary(encryptedText.trim()); - byte[] utf8 = dcipher.doFinal(dec); - return new String(utf8, StandardCharsets.UTF_8); + byte[] dec = DatatypeConverter.parseBase64Binary(encryptedText.trim()); + return new String(dcipher.doFinal(dec), StandardCharsets.UTF_8); } - } diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionXMLUtil.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionXMLUtil.java new file mode 100644 index 00000000..fedf8470 --- /dev/null +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/encrypt/EncryptionXMLUtil.java @@ -0,0 +1,149 @@ +/* Copyright 2018 Norconex Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.norconex.commons.lang.encrypt; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.commons.lang3.StringUtils; + +import com.norconex.commons.lang.config.IXMLConfigurable; +import com.norconex.commons.lang.config.XMLConfigurationUtil; +import com.norconex.commons.lang.xml.EnhancedXMLStreamWriter; + +/** + *+ * Utility methods for loading and saving {@link EncryptionKey} with + * {@link IXMLConfigurable} objects or other XML-driven classes. + *
+ * + * @author Pascal Essiembre + * @since 1.15.0 + */ +public class EncryptionXMLUtil { + + private EncryptionXMLUtil() { + super(); + } + + /** + * Convenience method for loading an encryption key from an XML reader. + * @param in an XML reader + * @param tagPrefix prefix of the XML tag names being loaded. + * @param defaultKey default encryption key + * @return encryption key + * @see IXMLConfigurable + */ + public static EncryptionKey loadFromXML( + Reader in, String tagPrefix, EncryptionKey defaultKey) { + XMLConfiguration xml = XMLConfigurationUtil.newXMLConfiguration(in); + return loadFromXML(xml, tagPrefix, defaultKey); + } + /** + * Convenience method for loading an encryption key from an + * {@link XMLConfiguration}. + * @param xml xml configuration + * @param tagPrefix prefix of the XML tag names being loaded. + * @param defaultKey default encryption key + * @return encryption key + * @see IXMLConfigurable + */ + public static EncryptionKey loadFromXML( + XMLConfiguration xml, String tagPrefix, EncryptionKey defaultKey) { + String tagKey = StringUtils.trimToEmpty(tagPrefix); + tagKey = tagKey.length() > 0 ? tagKey + "Key" : "key"; + String tagSource = tagKey + "Source"; + String tagSize = tagKey + "Size"; + + String xmlKey = xml.getString(tagKey, null); + if (StringUtils.isNotBlank(xmlKey)) { + String xmlSource = xml.getString(tagSource, null); + Integer size = xml.getInteger( + tagSize, EncryptionKey.DEFAULT_KEY_SIZE); + EncryptionKey.Source source = null; + if (StringUtils.isNotBlank(xmlSource)) { + source = EncryptionKey.Source.valueOf(xmlSource.toUpperCase()); + } + return new EncryptionKey(xmlKey, source, size); + } + return defaultKey; + } + + /** + * Convenience method for saving an encryption key to an XML writer. + * @param writer a writer + * @param tagPrefix Prefix of the XML tag names being saved. If + *null
, no prefix is used (not recommended unless + * wrapped in a parent tag). + * @param encryptionKey the encryption key to save + * @throws IOException problem saving to XML + * @see IXMLConfigurable + */ + public static void saveToXML( + Writer writer, String tagPrefix, EncryptionKey encryptionKey) + throws IOException { + try { + saveToXML(new EnhancedXMLStreamWriter(writer), + tagPrefix, encryptionKey); + } catch (XMLStreamException e) { + throw new IOException("Cannot save as XML.", e); + + } + } + /** + * Convenience method for saving an encryption key to an + * {@link XMLStreamWriter}. + * @param writer an XML writer + * @param tagPrefix Prefix of the XML tag names being saved. If + *null
, no prefix is used (not recommended unless + * wrapped in a parent tag). + * @param encryptionKey the encryption key to save + * @throws IOException problem saving to XML + * @see IXMLConfigurable + */ + public static void saveToXML(XMLStreamWriter writer, + String tagPrefix, EncryptionKey encryptionKey) throws IOException { + + String tagKey = StringUtils.trimToEmpty(tagPrefix); + tagKey = tagKey.length() > 0 ? tagKey + "Key" : "key"; + String tagSource = tagKey + "Source"; + String tagSize = tagKey + "Size"; + + try { + EnhancedXMLStreamWriter w = null; + if (writer instanceof EnhancedXMLStreamWriter) { + w = (EnhancedXMLStreamWriter) writer; + } else { + w = new EnhancedXMLStreamWriter(writer); + } + + if (encryptionKey != null) { + w.writeElementString(tagKey, encryptionKey.getValue()); + w.writeElementInteger(tagSize, encryptionKey.getSize()); + if (encryptionKey.getSource() != null) { + w.writeElementString(tagSource, + encryptionKey.getSource().name().toLowerCase()); + } + } + } catch (XMLStreamException e) { + throw new IOException("Cannot save as XML.", e); + } + } +} diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java index 3119d6ca..1dac78e0 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/net/ProxySettings.java @@ -1,4 +1,4 @@ -/* Copyright 2017 Norconex Inc. +/* Copyright 2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,10 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import com.norconex.commons.lang.config.IXMLConfigurable; -import com.norconex.commons.lang.config.PasswordKeyUtil; import com.norconex.commons.lang.config.XMLConfigurationUtil; import com.norconex.commons.lang.encrypt.EncryptionKey; import com.norconex.commons.lang.encrypt.EncryptionUtil; +import com.norconex.commons.lang.encrypt.EncryptionXMLUtil; import com.norconex.commons.lang.xml.EnhancedXMLStreamWriter; /** @@ -190,7 +190,8 @@ public void loadProxyFromXML(XMLConfiguration xml) { proxyScheme = xml.getString("proxyScheme", proxyScheme); proxyUsername = xml.getString("proxyUsername", proxyUsername); proxyPassword = xml.getString("proxyPassword", proxyPassword); - proxyPasswordKey = PasswordKeyUtil.loadKeyFrom(xml, proxyPasswordKey); + proxyPasswordKey = EncryptionXMLUtil.loadFromXML( + xml, "proxyPassword", proxyPasswordKey); proxyRealm = xml.getString("proxyRealm", proxyRealm); } /** @@ -214,22 +215,27 @@ public void saveToXML(Writer out) throws IOException { /** * Saves assuming we are already in a parent tag. * @param out XML stream writer - * @throws XMLStreamException problem saving stream to XML + * @throws IOException problem saving stream to XML */ - public void saveProxyToXML(XMLStreamWriter out) throws XMLStreamException { + public void saveProxyToXML(XMLStreamWriter out) throws IOException { EnhancedXMLStreamWriter writer; if (out instanceof EnhancedXMLStreamWriter) { writer = (EnhancedXMLStreamWriter) out; } else { writer = new EnhancedXMLStreamWriter(out); } - writer.writeElementString("proxyHost", proxyHost); - writer.writeElementInteger("proxyPort", proxyPort); - writer.writeElementString("proxyScheme", proxyScheme); - writer.writeElementString("proxyUsername", proxyUsername); - writer.writeElementString("proxyPassword", proxyPassword); - PasswordKeyUtil.saveKeyTo(writer, proxyPasswordKey); - writer.writeElementString("proxyRealm", proxyRealm); + try { + writer.writeElementString("proxyHost", proxyHost); + writer.writeElementInteger("proxyPort", proxyPort); + writer.writeElementString("proxyScheme", proxyScheme); + writer.writeElementString("proxyUsername", proxyUsername); + writer.writeElementString("proxyPassword", proxyPassword); + EncryptionXMLUtil.saveToXML( + writer, "proxyPassword", proxyPasswordKey); + writer.writeElementString("proxyRealm", proxyRealm); + } catch (XMLStreamException e) { + throw new IOException("Cannot save as XML.", e); + } } @Override diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java index 76ccec8c..5cad3001 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/encrypt/EncryptionUtilTest.java @@ -1,4 +1,4 @@ -/* Copyright 2015 Norconex Inc. +/* Copyright 20185 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,8 @@ public void testDecryptLegacy() { @Test public void testAes256bitEncryptionKey() throws NoSuchAlgorithmException { - // NOTE: this test should be true on Java 8 u162+ or on Java 9, or on any Java where JCE Unlimited Strength has been applied + // NOTE: this test should be true on Java 8 u162+ or on Java 9, or on + // any Java where JCE Unlimited Strength has been applied Assume.assumeTrue(Cipher.getMaxAllowedKeyLength("AES") >= 256); // Create round-trip encryption key diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/net/ProxySettingsTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/net/ProxySettingsTest.java index 9a272dd2..19c07921 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/net/ProxySettingsTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/net/ProxySettingsTest.java @@ -1,4 +1,4 @@ -/* Copyright 2017 Norconex Inc. +/* Copyright 2017-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ public void testWriteRead() throws IOException { ProxySettings ps = new ProxySettings(); ps.setProxyHost("myhost"); ps.setProxyPassword("mypassword"); - ps.setProxyPasswordKey(new EncryptionKey("keyvalue", Source.KEY)); + ps.setProxyPasswordKey(new EncryptionKey("keyvalue", Source.KEY, 256)); ps.setProxyPort(99); ps.setProxyRealm("realm"); ps.setProxyScheme("sheme"); From 9c9416e5a42cefd44b443ec14f557393d221961c Mon Sep 17 00:00:00 2001 From: Pascal EssiembreDate: Sat, 2 Jun 2018 21:59:16 -0400 Subject: [PATCH 12/15] URLNormalizer unit test for file: scheme. --- .../commons/lang/url/URLNormalizerTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/URLNormalizerTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/URLNormalizerTest.java index 27d9f0f2..c5b0a687 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/URLNormalizerTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/URLNormalizerTest.java @@ -510,6 +510,29 @@ public void testRemoveSessionIds() { } // /myservlet;jsessionid=1E6FEC0D14D044541DD84D2D013D29ED?_option=XX[b]'[/b];[ // PHPSESSID=f9f2770d591366bc + + // Test for supporting file:// scheme, from here: + // https://github.com/Norconex/commons-lang/issues/11 + @Test + public void testFileScheme() { + + // Encode non-URI characters + s = "file:///etc/some dir/my file.txt"; + t = "file:///etc/some%20dir/my%20file.txt"; + assertEquals(t, n(s).encodeNonURICharacters().toString()); + + s = "file://./dir/another-dir/path"; + t = "file://./dir/another-dir/path"; + assertEquals(t, n(s).encodeNonURICharacters().toString()); + + s = "file://localhost/c:/WINDOWS/éà.txt"; + t = "file://localhost/c:/WINDOWS/%C3%A9%C3%A0.txt"; + assertEquals(t, n(s).encodeNonURICharacters().toString()); + + s = "file:///c:/WINDOWS/file.txt"; + t = "file:///c:/WINDOWS/file.txt"; + assertEquals(t, n(s).encodeNonURICharacters().toString()); + } private URLNormalizer n(String url) { return new URLNormalizer(url); From 41bc87222b9d1444d83bb8ca14b5fb785c8a2447 Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Sun, 3 Jun 2018 19:48:23 -0400 Subject: [PATCH 13/15] Less restrictive detection of protocol in URL string (github #11). --- norconex-commons-lang/src/changes/changes.xml | 7 +++ .../norconex/commons/lang/url/HttpURL.java | 44 ++++++++------ .../commons/lang/url/QueryString.java | 1 + .../commons/lang/url/HttpURLTest.java | 57 +++++++++++++++++++ 4 files changed, 93 insertions(+), 16 deletions(-) diff --git a/norconex-commons-lang/src/changes/changes.xml b/norconex-commons-lang/src/changes/changes.xml index 1ec06972..ed348f73 100644 --- a/norconex-commons-lang/src/changes/changes.xml +++ b/norconex-commons-lang/src/changes/changes.xml @@ -18,6 +18,13 @@ ConfigurationLoader now sets the Velocity character encoding to UTF-8. ++ HttpURL now extract protocols before first colon, no longer requiring + two forward slash. Also more lenient towards relative URLs. + ++ QueryString now strips out fragments when part of a URL. + diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/HttpURL.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/HttpURL.java index b0e2b451..59578864 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/HttpURL.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/HttpURL.java @@ -1,4 +1,4 @@ -/* Copyright 2010-2017 Norconex Inc. +/* Copyright 2010-2018 Norconex Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ * * @author Pascal Essiembre */ -@SuppressWarnings("nls") +//TODO rename MutableURL public class HttpURL implements Serializable { private static final long serialVersionUID = -8886393027925815099L; @@ -52,7 +52,7 @@ public class HttpURL implements Serializable { private QueryString queryString; private String host; - private int port = DEFAULT_HTTP_PORT; + private int port = -1; private String path; private String protocol; private final String encoding; @@ -103,31 +103,38 @@ public HttpURL(String url, String encoding) { } else { this.encoding = encoding; } - if (url.matches("\\w+://.+")) { + + String u = StringUtils.trimToEmpty(url); + if (u.matches("[a-zA-Z][a-zA-Z0-9\\+\\-\\.]*:.*")) { URL urlwrap; try { - urlwrap = new URL(url); + urlwrap = new URL(u); } catch (MalformedURLException e) { - throw new URLException("Could not interpret URL: " + url, e); + throw new URLException("Could not interpret URL: " + u, e); } - protocol = StringUtils.substringBefore(url, ":"); + protocol = StringUtils.substringBefore(u, ":"); host = urlwrap.getHost(); port = urlwrap.getPort(); if (port < 0) { - if (StringUtils.startsWithIgnoreCase(url, PROTOCOL_HTTPS)) { + if (StringUtils.startsWithIgnoreCase(u, PROTOCOL_HTTPS)) { port = DEFAULT_HTTPS_PORT; } else if ( - StringUtils.startsWithIgnoreCase(url, PROTOCOL_HTTP)) { + StringUtils.startsWithIgnoreCase(u, PROTOCOL_HTTP)) { port = DEFAULT_HTTP_PORT; } } path = urlwrap.getPath(); fragment = urlwrap.getRef(); + } else { + path = u.replaceFirst("^(.*?)([\\?\\#])(.*)", "$1"); + if (StringUtils.contains(u, "#")) { + fragment = u.replaceFirst("^(.*?)(\\#)(.*)", "$3"); + } } // Parameters - if (StringUtils.contains(url, "?")) { - queryString = new QueryString(url, encoding); + if (StringUtils.contains(u, "?")) { + queryString = new QueryString(u, encoding); } } @@ -353,16 +360,20 @@ public static String getRoot(String url) { @Override public String toString() { StringBuilder b = new StringBuilder(); - b.append(protocol); - b.append("://"); - b.append(host); - + if (StringUtils.isNotBlank(protocol)) { + b.append(protocol); + b.append("://"); + } + if (StringUtils.isNotBlank(host)) { + b.append(host); + } if (!isPortDefault() && port != -1) { b.append(':'); b.append(port); } if (StringUtils.isNotBlank(path)) { - if (!path.startsWith("/")) { + // If no scheme/host/port, leave the path as is + if (b.length() > 0 && !path.startsWith("/")) { b.append('/'); } b.append(encodePath(path)); @@ -374,6 +385,7 @@ public String toString() { b.append("#"); b.append(encodePath(fragment)); } + return b.toString(); } diff --git a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/QueryString.java b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/QueryString.java index 4571ca62..53b5ec10 100644 --- a/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/QueryString.java +++ b/norconex-commons-lang/src/main/java/com/norconex/commons/lang/url/QueryString.java @@ -94,6 +94,7 @@ public QueryString(String urlWithQueryString, String encoding) { } String paramString = urlWithQueryString; if (StringUtils.contains(paramString, "?")) { + paramString = StringUtils.substringBefore(paramString, "#"); paramString = paramString.replaceAll("(.*?)(\\?)(.*)", "$3"); } String[] paramParts = paramString.split("\\&"); diff --git a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/HttpURLTest.java b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/HttpURLTest.java index a9c0e852..6de133e2 100644 --- a/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/HttpURLTest.java +++ b/norconex-commons-lang/src/test/java/com/norconex/commons/lang/url/HttpURLTest.java @@ -146,4 +146,61 @@ public void testInvalidURL() { t = "http://www.example.com/%22path%22"; assertEquals(t, new HttpURL(s).toString()); } + + @Test + public void testURLWithLeadingTrailingSpaces() { + s = " http://www.example.com/path "; + t = "http://www.example.com/path"; + assertEquals(t, new HttpURL(s).toString()); + } + + @Test + public void testNullOrBlankURLs() { + s = null; + t = ""; + assertEquals(t, new HttpURL(s).toString()); + s = ""; + t = ""; + assertEquals(t, new HttpURL(s).toString()); + s = " "; + t = ""; + assertEquals(t, new HttpURL(s).toString()); + } + + @Test + public void testRelativeURLs() { + s = "./blah"; + t = "./blah"; + assertEquals(t, new HttpURL(s).toString()); + s = "/blah"; + t = "/blah"; + assertEquals(t, new HttpURL(s).toString()); + s = "blah?param=value#frag"; + t = "blah?param=value#frag"; + assertEquals(t, new HttpURL(s).toString()); + } + + @Test + public void testFileProtocol() { + // Encode non-URI characters + s = "file:///etc/some dir/my file.txt"; + t = "file:///etc/some%20dir/my%20file.txt"; + assertEquals(t, new HttpURL(s).toString()); + + s = "file://./dir/another-dir/path"; + t = "file://./dir/another-dir/path"; + assertEquals(t, new HttpURL(s).toString()); + + s = "file://localhost/c:/WINDOWS/éà.txt"; + t = "file://localhost/c:/WINDOWS/%C3%A9%C3%A0.txt"; + assertEquals(t, new HttpURL(s).toString()); + + s = "file:///c:/WINDOWS/file.txt"; + t = "file:///c:/WINDOWS/file.txt"; + assertEquals(t, new HttpURL(s).toString()); + + s = "file:/c:/WINDOWS/file.txt"; + t = "file:///c:/WINDOWS/file.txt"; + assertEquals(t, new HttpURL(s).toString()); + } } \ No newline at end of file From 2fa3a77c018973c44e4fac62e1aa56770681db99 Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Sun, 3 Jun 2018 21:05:20 -0400 Subject: [PATCH 14/15] Preparing for release. --- norconex-commons-lang/pom.xml | 4 ++-- norconex-commons-lang/src/changes/changes.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/norconex-commons-lang/pom.xml b/norconex-commons-lang/pom.xml index ef28aa53..f02b2043 100644 --- a/norconex-commons-lang/pom.xml +++ b/norconex-commons-lang/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.norconex.commons norconex-commons-lang -1.15.0-SNAPSHOT +1.15.0 jar Norconex Commons Lang @@ -27,7 +27,7 @@UTF-8 UTF-8 - 1.14.0 +1.15.0 3.6 1.10 diff --git a/norconex-commons-lang/src/changes/changes.xml b/norconex-commons-lang/src/changes/changes.xml index ed348f73..eb513103 100644 --- a/norconex-commons-lang/src/changes/changes.xml +++ b/norconex-commons-lang/src/changes/changes.xml @@ -7,7 +7,7 @@ -+ New EncryptionXMLUtil class offering methods to facilitate integration of EncryptionKey with IXMLConfigurable objects (or other XML objects). From 9b4076cec48764332fa52a6c4761d7bfea32a70a Mon Sep 17 00:00:00 2001 From: Pascal Essiembre Date: Sun, 3 Jun 2018 21:12:05 -0400 Subject: [PATCH 15/15] Site update. --- norconex-commons-lang/src/site/markdown/download.md.vm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/norconex-commons-lang/src/site/markdown/download.md.vm b/norconex-commons-lang/src/site/markdown/download.md.vm index 9d6d1984..59434825 100644 --- a/norconex-commons-lang/src/site/markdown/download.md.vm +++ b/norconex-commons-lang/src/site/markdown/download.md.vm @@ -30,6 +30,8 @@ $h2 Binaries **Older Releases** + * [1.14.0]($nexusPath/1.14.0/norconex-commons-lang-1.14.0.zip) + [[Release Notes](changes-report.html#a1.14.0)] * [1.13.1]($nexusPath/1.13.1/norconex-commons-lang-1.13.1.zip) [[Release Notes](changes-report.html#a1.13.1)] * [1.13.0]($nexusPath/1.13.0/norconex-commons-lang-1.13.0.zip)