This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 8afcf88b77 Fix #8054: decrypt trustStorePassword in REST transform
init (#8061)
8afcf88b77 is described below
commit 8afcf88b77b645d8f60a0eab4c7a1c3dc9789897
Author: kotwal-itpro <[email protected]>
AuthorDate: Mon Aug 24 02:05:43 2026 -0700
Fix #8054: decrypt trustStorePassword in REST transform init (#8061)
The REST transform's init() reads the trust store password with resolve()
only, missing the Encr.decryptPasswordOptionallyEncrypted(...) call every
other password field in Hop wraps around resolve(). This includes the
httpPassword branch a dozen lines above it in the very same method:
data.realHttpPassword =
Encr.decryptPasswordOptionallyEncrypted(resolve(meta.getHttpPassword()));
...
data.trustStorePassword = resolve(meta.getTrustStorePassword()); // not
decrypted
As a result, an encrypted value that reaches the field through a variable
is passed to the trust store loader verbatim and the SSL context cannot
be built. A password typed straight into the dialog works, because
XmlMetadataUtil already decrypts password=true properties on
deserialization -- the bug only surfaces on the variable path.
Both fields are declared identically in RestMeta (password = true) and
every peer usage decrypts:
- plugins/misc/rest/.../RestConnection.java:566 (same field name)
- plugins/databases/oracle/.../OracleDatabaseMeta.java:561
- plugins/transforms/ldap/.../LdapSslProtocol.java:41
Fix: wrap resolve() in Encr.decryptPasswordOptionallyEncrypted() to
match the peer pattern. One-line production change plus a regression
test that runs init() with an encrypted trust store password behind a
variable, asserting the plaintext lands on RestData (parity with the
pre-existing httpPassword assertion in the same test).
Verified: full plugins/transforms/rest test suite passes (168 tests, 0
failures, 0 errors). ./mvnw spotless:apply clean. Java 21 build.
---
.../apache/hop/pipeline/transforms/rest/Rest.java | 8 +++-
.../transforms/rest/RestInitAndProcessTest.java | 56 ++++++++++++++++++++++
2 files changed, 63 insertions(+), 1 deletion(-)
diff --git
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
index fb9f2c149e..531f68e407 100644
---
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
+++
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
@@ -2254,7 +2254,13 @@ public class Rest extends BaseTransform<RestMeta,
RestData> {
}
data.trustStoreFile = resolve(meta.getTrustStoreFile());
- data.trustStorePassword = resolve(meta.getTrustStorePassword());
+ // Decrypt the resolved trust store password like every other password
field in Hop
+ // (see RestConnection.java, OracleDatabaseMeta.java,
LdapSslProtocol.java, and the
+ // httpPassword branch a dozen lines above). Without this, an encrypted
value that
+ // reaches the field through a variable is passed to the trust store
loader verbatim
+ // and the SSL context cannot be built. See Apache Hop #8054.
+ data.trustStorePassword =
+
Encr.decryptPasswordOptionallyEncrypted(resolve(meta.getTrustStorePassword()));
String applicationType = NVL(meta.getApplicationType(), "");
switch (applicationType) {
diff --git
a/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestInitAndProcessTest.java
b/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestInitAndProcessTest.java
index efc5c441ed..49e710f249 100644
---
a/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestInitAndProcessTest.java
+++
b/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestInitAndProcessTest.java
@@ -343,4 +343,60 @@ class RestInitAndProcessTest {
assertEquals(3000, data.realConnectionTimeout);
assertEquals(7000, data.realReadTimeout);
}
+
+ /**
+ * Regression test for Apache Hop #8054.
+ *
+ * <p>Every password field in Hop is decrypted with {@link
+ * Encr#decryptPasswordOptionallyEncrypted} after being resolved, including
the {@code
+ * httpPassword} field a dozen lines above the trust store in {@link
Rest#init()}. Before this
+ * fix, {@code trustStorePassword} was resolved but not decrypted, so an
encrypted value that
+ * reached the field through a variable was passed to the trust store loader
verbatim and the SSL
+ * context could not be built.
+ *
+ * <p>This test resolves an encrypted trust store password from a variable
and asserts the value
+ * stored on {@link RestData} is the plaintext, matching the treatment
{@code httpPassword} has
+ * always received.
+ */
+ @Test
+ void testInitDecryptsTrustStorePasswordFromVariable() {
+ String plaintext = "trustpass";
+ String encrypted = Encr.encryptPasswordIfNotUsingVariables(plaintext);
+ // Sanity check: the plugin only decrypts values with the standard
"Encrypted " prefix.
+ assertTrue(
+ encrypted.startsWith("Encrypted "),
+ "test setup precondition: encryptPasswordIfNotUsingVariables should
return a prefixed value");
+
+ TransformMeta transformMeta = new TransformMeta();
+ transformMeta.setName("TestRest");
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.setName("TestRest");
+ pipelineMeta.addTransform(transformMeta);
+
+ RestMeta meta = new RestMeta();
+ meta.setMethod(RestMeta.HTTP_METHOD_GET);
+ meta.setUrl("http://example.com");
+ meta.setApplicationType(RestMeta.APPLICATION_TYPE_JSON);
+ meta.setResultField(new ResultField());
+ // Same shape as an httpPassword coming through a variable: the caller
passes the
+ // encrypted value indirectly and expects the transform to decrypt on init.
+ meta.setHttpPassword("${HTTP_PWD}");
+ meta.setTrustStoreFile("/tmp/does-not-need-to-exist.jks");
+ meta.setTrustStorePassword("${TRUST_PWD}");
+
+ RestData data = new RestData();
+
+ Rest rest =
+ new Rest(transformMeta, meta, data, 1, pipelineMeta, spy(new
LocalPipelineEngine()));
+ rest.setMetadataProvider(mock(IHopMetadataProvider.class));
+ rest.setVariable("HTTP_PWD", encrypted);
+ rest.setVariable("TRUST_PWD", encrypted);
+
+ rest.init();
+
+ // httpPassword has always been decrypted here; asserting alongside
trustStorePassword
+ // makes the parity with the pre-existing branch explicit.
+ assertEquals(plaintext, data.realHttpPassword);
+ assertEquals(plaintext, data.trustStorePassword);
+ }
}