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">
