Skip to content

Commit a75c1cd

Browse files
authored
Merge pull request #953 from KnowageLabs/feature/tomcat-encryption
Create project for Tomcat password encryption and decryption
2 parents 6dfe1ee + cdc8979 commit a75c1cd

4 files changed

Lines changed: 232 additions & 0 deletions

File tree

tomcat-password-encryption/pom.xml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>it.eng.knowage</groupId>
5+
<artifactId>tomcat-password-encryption</artifactId>
6+
<version>9.0.0-SNAPSHOT</version>
7+
<name>Archetype - tomcat-password-encryption</name>
8+
<url>http://maven.apache.org</url>
9+
10+
<properties>
11+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
13+
<maven.compiler.release>17</maven.compiler.release>
14+
</properties>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.apache.tomcat</groupId>
19+
<artifactId>tomcat-jdbc</artifactId>
20+
<version>9.0.83</version>
21+
<scope>compile</scope>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.jasypt</groupId>
25+
<artifactId>jasypt</artifactId>
26+
<version>1.9.3</version>
27+
</dependency>
28+
</dependencies>
29+
<build>
30+
<plugins>
31+
<plugin>
32+
<groupId>org.apache.maven.plugins</groupId>
33+
<artifactId>maven-compiler-plugin</artifactId>
34+
<version>3.11.0</version>
35+
<configuration>
36+
<release>${maven.compiler.release}</release>
37+
<encoding>${project.build.sourceEncoding}</encoding>
38+
</configuration>
39+
</plugin>
40+
<!-- Bundle dependencies (e.g., jasypt) inside one jar -->
41+
<plugin>
42+
<groupId>org.apache.maven.plugins</groupId>
43+
<artifactId>maven-shade-plugin</artifactId>
44+
<version>3.5.0</version>
45+
<executions>
46+
<execution>
47+
<phase>package</phase>
48+
<goals>
49+
<goal>shade</goal>
50+
</goals>
51+
<configuration>
52+
<createDependencyReducedPom>true</createDependencyReducedPom>
53+
<shadedArtifactSet>
54+
<includes>
55+
<include>org.jasypt:jasypt</include>
56+
</includes>
57+
</shadedArtifactSet>
58+
<relocations>
59+
<!-- optional: avoid classpath conflicts by relocating jasypt -->
60+
<relocation>
61+
<pattern>org.jasypt</pattern>
62+
<shadedPattern>shade.org.jasypt</shadedPattern>
63+
</relocation>
64+
</relocations>
65+
</configuration>
66+
</execution>
67+
</executions>
68+
</plugin>
69+
</plugins>
70+
</build>
71+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package it.eng.knowage.tomcatpasswordencryption;
2+
3+
import java.util.Enumeration;
4+
import java.util.Hashtable;
5+
6+
import javax.naming.Context;
7+
import javax.naming.Name;
8+
import javax.naming.RefAddr;
9+
import javax.naming.Reference;
10+
import javax.naming.StringRefAddr;
11+
12+
import it.eng.knowage.tomcatpasswordencryption.helper.EncryptedPasswordUtils;
13+
import org.apache.juli.logging.Log;
14+
import org.apache.juli.logging.LogFactory;
15+
16+
public class KnowageTomcatEncryptedPasswordDatasource extends org.apache.tomcat.jdbc.pool.DataSourceFactory {
17+
18+
private static final Log log = LogFactory.getLog(KnowageTomcatEncryptedPasswordDatasource.class);
19+
20+
@Override
21+
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
22+
try {
23+
if (obj instanceof Reference ref) {
24+
StringRefAddr passwordRefAddr = (StringRefAddr) ref.get(PROP_PASSWORD);
25+
if (passwordRefAddr != null) {
26+
String encryptedPwd = (String) passwordRefAddr.getContent();
27+
String cleartextPwd = decrypt(encryptedPwd);
28+
int index = find(ref);
29+
if (index >= 0) {
30+
ref.remove(index);
31+
ref.add(index, new StringRefAddr(PROP_PASSWORD, cleartextPwd));
32+
}
33+
}
34+
}
35+
} catch (Exception e) {
36+
log.error("Failed to decrypt password. Please check DataSource definition.");
37+
throw e;
38+
}
39+
return super.getObjectInstance(obj, name, nameCtx, environment);
40+
}
41+
42+
private int find(Reference ref) {
43+
Enumeration<RefAddr> enu = ref.getAll();
44+
for (int i = 0; enu.hasMoreElements(); i++) {
45+
RefAddr addr = enu.nextElement();
46+
if (addr.getType().equals(PROP_PASSWORD))
47+
return i;
48+
}
49+
return -1;
50+
}
51+
52+
public static String decrypt(String encryptSource) {
53+
return EncryptedPasswordUtils.decrypt(encryptSource);
54+
}
55+
56+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package it.eng.knowage.tomcatpasswordencryption.helper;
2+
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
3+
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
4+
5+
public class EncryptOnce {
6+
7+
public static void main(String[] args) {
8+
if (args.length == 0) {
9+
System.err.println("Usage: java -Dknowage.enc.password=<KEY> [-Dknowage.enc.algorithm=PBEWithMD5AndDES] "
10+
+ "[-Dknowage.enc.keyObtentionIterations=1000] EncryptOnce <CLEAR_TEXT_PASSWORD>");
11+
System.exit(1);
12+
}
13+
String clear = args[0];
14+
String key = EncryptedPasswordUtils.resolveKey();
15+
if (key == null || key.isEmpty()) {
16+
System.err.println("Missing -Dknowage.enc.password.file");
17+
System.exit(2);
18+
}
19+
20+
SimpleStringPBEConfig cfg = new SimpleStringPBEConfig();
21+
cfg.setPassword(key);
22+
cfg.setPoolSize("1");
23+
cfg.setStringOutputType("base64");
24+
25+
StandardPBEStringEncryptor enc = new StandardPBEStringEncryptor();
26+
enc.setConfig(cfg);
27+
28+
String cipher = enc.encrypt(clear);
29+
System.out.println("#encr#" + cipher);
30+
}
31+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package it.eng.knowage.tomcatpasswordencryption.helper;
2+
3+
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
4+
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
5+
6+
import java.io.IOException;
7+
import java.nio.charset.StandardCharsets;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
11+
public final class EncryptedPasswordUtils {
12+
private static final String ENCRYPTED_PREFIX = "#encr#";
13+
14+
private EncryptedPasswordUtils() {}
15+
16+
public static String decrypt(String value) {
17+
if (value == null || value.isEmpty()) return value;
18+
if (!value.startsWith(ENCRYPTED_PREFIX)) {
19+
return value;
20+
}
21+
String cipherText = value.substring(ENCRYPTED_PREFIX.length());
22+
String password = resolveKey();
23+
if (password == null || password.isEmpty()) {
24+
throw new IllegalStateException("""
25+
Missing decryption key. Provide it via system property knowage.enc.password, " +
26+
"environment variable KNOWAGE_ENC_PASSWORD, or a file at ${catalina.base}/conf/knowageTomcatEncryptedPasswordDatasource " +
27+
"or -Dknowage.enc.password.file=/secure/path
28+
""");
29+
}
30+
31+
SimpleStringPBEConfig cfg = new SimpleStringPBEConfig();
32+
cfg.setPassword(password);
33+
cfg.setPoolSize("1");
34+
cfg.setStringOutputType("base64");
35+
36+
StandardPBEStringEncryptor enc = new StandardPBEStringEncryptor();
37+
enc.setConfig(cfg);
38+
return enc.decrypt(cipherText);
39+
}
40+
41+
public static String resolveKey() {
42+
// Prefer explicit file path via system property
43+
String fileProp = System.getProperty("knowage.enc.password.file");
44+
if (fileProp != null && !fileProp.isEmpty()) {
45+
String fromFile = readFirstLineTrimmed(Path.of(fileProp));
46+
if (fromFile != null && !fromFile.isEmpty()) return fromFile;
47+
}
48+
49+
// Default file under Tomcat conf: ${catalina.base}/conf/passwordEncryptionSecret
50+
String catalinaBase = System.getProperty("catalina.base");
51+
if (catalinaBase != null && !catalinaBase.isEmpty()) {
52+
Path defaultPath = Path.of(catalinaBase, "conf", "knowageTomcatEncryptedPasswordDatasource");
53+
String fromFile = readFirstLineTrimmed(defaultPath);
54+
if (fromFile != null && !fromFile.isEmpty()) return fromFile;
55+
}
56+
57+
return null;
58+
}
59+
60+
private static String readFirstLineTrimmed(Path path) {
61+
try {
62+
if (Files.isRegularFile(path)) {
63+
for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) {
64+
String trimmed = line.trim();
65+
if (!trimmed.isEmpty()) return trimmed;
66+
}
67+
}
68+
} catch (IOException ignored) {
69+
}
70+
return null;
71+
}
72+
73+
74+
}

0 commit comments

Comments
 (0)