This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit c5a998f919fa93aa0b2156249d30f5395e78b8fb
Author: Richard Zowalla <[email protected]>
AuthorDate: Tue Oct 13 08:48:35 2026 +0200

    Warn that the Static3DES password cipher only obfuscates (#3076)
    
    Its key ships with TomEE, so ciphered values must not be treated as secret.
    
    (cherry picked from commit ad159c656647808333e38d907e122ce2cab5aa61)
---
 .../openejb/cipher/StaticDESPasswordCipher.java    | 33 +++++++-
 .../cipher/StaticDESPasswordCipherTest.java        | 97 ++++++++++++++++++++++
 docs/datasource-password-encryption.adoc           |  7 ++
 3 files changed, 135 insertions(+), 2 deletions(-)

diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/cipher/StaticDESPasswordCipher.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/cipher/StaticDESPasswordCipher.java
index 1741e4fa5b..6894cbec16 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/cipher/StaticDESPasswordCipher.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/cipher/StaticDESPasswordCipher.java
@@ -19,16 +19,30 @@ package org.apache.openejb.cipher;
 
 import org.apache.openejb.OpenEJBRuntimeException;
 import org.apache.openejb.util.Base64;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
 
 import javax.crypto.Cipher;
 import javax.crypto.spec.SecretKeySpec;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * This {@link org.apache.openejb.cipher.PasswordCipher} implementation uses a 
the Triple-DES encryption
- * algorithm.
+ * This {@link org.apache.openejb.cipher.PasswordCipher} implementation uses 
the Triple-DES
+ * algorithm with a <b>static key</b> embedded in this class.
+ * <p>
+ * The key is identical in every distribution and publicly available, so 
values produced by
+ * this cipher are only obfuscated, not encrypted: anyone who obtains a 
ciphered value (for
+ * instance from a configuration file in a source repository or a backup) can 
decode it.
+ * Do not rely on it to keep passwords confidential. Use a {@link 
PasswordCipher} backed by a
+ * secret that is kept outside the configuration (custom implementation or a 
{@code cdi:}
+ * cipher, see {@link CdiPasswordCipher}) instead.
+ * <p>
+ * A warning is logged once the first time this cipher is used.
  */
 public class StaticDESPasswordCipher implements PasswordCipher {
 
+    private static final AtomicBoolean WARNED = new AtomicBoolean();
+
     private static final byte[] _3desData = {
         (byte) 0x76, (byte) 0x6F, (byte) 0xBA, (byte) 0x39, (byte) 0x31,
         (byte) 0x2F, (byte) 0x0D, (byte) 0x4A, (byte) 0xA3, (byte) 0x90,
@@ -52,6 +66,8 @@ public class StaticDESPasswordCipher implements 
PasswordCipher {
             throw new IllegalArgumentException("plainPassword cannot be null 
nor empty.");
         }
 
+        warnOnce();
+
         final byte[] plaintext = plainPassword.getBytes();
         try {
             // Get a 3DES Cipher object
@@ -77,6 +93,8 @@ public class StaticDESPasswordCipher implements 
PasswordCipher {
             throw new IllegalArgumentException("encodedPassword cannot be null 
nor empty.");
         }
 
+        warnOnce();
+
         try {
             final byte[] cipherText = Base64.decodeBase64(
                 String.valueOf(encodedPassword).getBytes());
@@ -94,4 +112,15 @@ public class StaticDESPasswordCipher implements 
PasswordCipher {
         }
     }
 
+    private static void warnOnce() {
+        if (WARNED.compareAndSet(false, true)) {
+            Logger.getInstance(LogCategory.OPENEJB, 
StaticDESPasswordCipher.class)
+                .warning("The Static3DES password cipher uses a static key 
shipped with TomEE: ciphered values "
+                    + "are only obfuscated, not protected. Use a 
PasswordCipher relying on a secret key instead.");
+        }
+    }
+
+    static void resetWarning() { // for tests
+        WARNED.set(false);
+    }
 }
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/cipher/StaticDESPasswordCipherTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/cipher/StaticDESPasswordCipherTest.java
new file mode 100644
index 0000000000..2d6eeb2591
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/cipher/StaticDESPasswordCipherTest.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.cipher;
+
+import org.apache.openejb.util.LogCategory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class StaticDESPasswordCipherTest {
+    private static final String MARKER = "StaticDESPasswordCipherTest-done";
+
+    private final List<String> warnings = new CopyOnWriteArrayList<>();
+    private final CountDownLatch done = new CountDownLatch(1);
+    private final Handler handler = new Handler() {
+        @Override
+        public void publish(final LogRecord record) {
+            final String message = record.getMessage();
+            if (message == null) {
+                return;
+            }
+            if (message.contains("Static3DES")) {
+                warnings.add(message);
+            } else if (message.contains(MARKER)) {
+                done.countDown();
+            }
+        }
+
+        @Override
+        public void flush() {
+            // no-op
+        }
+
+        @Override
+        public void close() {
+            // no-op
+        }
+    };
+
+    @Before
+    public void captureLogs() {
+        StaticDESPasswordCipher.resetWarning();
+        Logger.getLogger(LogCategory.OPENEJB.getName()).addHandler(handler);
+    }
+
+    @After
+    public void releaseLogs() {
+        Logger.getLogger(LogCategory.OPENEJB.getName()).removeHandler(handler);
+    }
+
+    @Test
+    public void existingValuesStillDecrypt() {
+        assertEquals("Passw0rd", new 
StaticDESPasswordCipher().decrypt("xMH5uM1V9vQzVUv5LG7YLA==".toCharArray()));
+        assertEquals("xMH5uM1V9vQzVUv5LG7YLA==", new String(new 
StaticDESPasswordCipher().encrypt("Passw0rd")));
+    }
+
+    @Test
+    public void warnsOnlyOnce() throws InterruptedException {
+        final StaticDESPasswordCipher cipher = new StaticDESPasswordCipher();
+        for (int i = 0; i < 3; i++) {
+            assertEquals("secret", cipher.decrypt(cipher.encrypt("secret")));
+        }
+        new 
StaticDESPasswordCipher().decrypt("xMH5uM1V9vQzVUv5LG7YLA==".toCharArray());
+
+        // logging can be asynchronous, wait until everything logged before 
the marker is published
+        org.apache.openejb.util.Logger.getInstance(LogCategory.OPENEJB, 
StaticDESPasswordCipherTest.class).warning(MARKER);
+        assertTrue(done.await(1, TimeUnit.MINUTES));
+
+        assertEquals(1, warnings.size());
+        assertTrue(warnings.get(0).contains("obfuscated"));
+    }
+}
diff --git a/docs/datasource-password-encryption.adoc 
b/docs/datasource-password-encryption.adoc
index 776cefa545..e229a88292 100644
--- a/docs/datasource-password-encryption.adoc
+++ b/docs/datasource-password-encryption.adoc
@@ -45,6 +45,13 @@ how relying on an HSM for example._ The easiest way to do it 
is to
 implement the _org.apache.openejb.resource.jdbc.cipher.PasswordCipher_
 interface.
 
+WARNING: The Static3DES key is the same in every distribution and is
+publicly available, so a Static3DES value is only obfuscated: anyone who
+gets hold of it (a configuration file in a repository, a backup, ...) can
+decode it. Use a `PasswordCipher` relying on a secret kept outside of the
+configuration if the password must stay confidential. TomEE logs a
+warning the first time Static3DES is used.
+
 [source,xml]
 ----
 <Resource id="MySQL Database" type="DataSource">

Reply via email to