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);
+  }
 }

Reply via email to