diff --git a/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.cmd b/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.cmd new file mode 100644 index 00000000000..313f741e27e --- /dev/null +++ b/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.cmd @@ -0,0 +1,47 @@ +@REM +@REM Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. +@REM +@REM This program and the accompanying materials are made available under the +@REM terms of the Eclipse Public License v. 2.0 which is available at +@REM http://www.eclipse.org/legal/epl-2.0, +@REM or the Eclipse Distribution License v. 1.0 which is available at +@REM http://www.eclipse.org/org/documents/edl-v10.php. +@REM +@REM SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +@REM + +@REM Usage is `passwordUpdate.sh|.cmd -ip ` +@REM This application internally decrypt old encrypted password used by some previous version EclipseLink and encrypt it by latest algorithm. + +@echo off +setlocal +call "%~dp0../../bin/setenv.cmd" + +@REM User may increase Java memory setting(s) if desired: +set JVM_ARGS=-Xmx256m + +REM Please do not change any of the following lines: +set _FIXPATH= +call :fixpath "%~dp0" +set THIS=%_FIXPATH:~1% + +set CLASSPATH=%CLASSPATH%;%THIS%..\..\jlib\eclipselink.jar + +set PASSWORD_UPDATE_ARGS=%* + +%JAVA_HOME%\bin\java.exe %JVM_ARGS% -cp %CLASSPATH% org.eclipse.persistence.tools.security.JCEEncryptorCmd %PASSWORD_UPDATE_ARGS% + +endlocal +goto :EOF + +:fixpath +if not %1.==. ( + for /f "tokens=1* delims=;" %%a in (%1) do ( + call :shortfilename "%%a" & call :fixpath "%%b" + ) +) +goto :EOF + +:shortfilename +for %%i in (%1) do set _FIXPATH=%_FIXPATH%;%%~fsi +goto :EOF diff --git a/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.sh b/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.sh new file mode 100755 index 00000000000..ed56166c34a --- /dev/null +++ b/bundles/eclipselink/src/main/scripts/utils/security/passwordUpdate.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, +# or the Eclipse Distribution License v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +#Usage is `passwordUpdate.sh|.cmd -ip ` +#This application internally decrypt old encrypted password used by some previous version EclipseLink and encrypt it by latest algorithm. + +. `dirname $0`/../../bin/setenv.sh + +# User may increase Java memory setting(s) if desired: +JVM_ARGS=-Xmx256m + +# Please do not change any of the following lines: +CLASSPATH=`dirname $0`/../../jlib/eclipselink.jar: + +PASSWORD_UPDATE_ARGS="$@" + +${JAVA_HOME}/bin/java ${JVM_ARGS} -cp ${CLASSPATH} \ + org.eclipse.persistence.tools.security.JCEEncryptorCmd ${PASSWORD_UPDATE_ARGS} diff --git a/foundation/eclipselink.core.test/src/test/java/org/eclipse/persistence/testing/tests/junit/security/SecurableBackwardsCompatibilityTest.java b/foundation/eclipselink.core.test/src/test/java/org/eclipse/persistence/testing/tests/junit/security/SecurableBackwardsCompatibilityTest.java index 274292eda14..7d663d49d78 100644 --- a/foundation/eclipselink.core.test/src/test/java/org/eclipse/persistence/testing/tests/junit/security/SecurableBackwardsCompatibilityTest.java +++ b/foundation/eclipselink.core.test/src/test/java/org/eclipse/persistence/testing/tests/junit/security/SecurableBackwardsCompatibilityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,12 +21,14 @@ import javax.crypto.CipherOutputStream; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.security.JCEEncryptor; import org.eclipse.persistence.internal.security.Securable; +import org.eclipse.persistence.tools.security.JCEEncryptorCmd; import org.junit.Assert; import org.junit.Test; @@ -35,22 +37,54 @@ * @author dminsky */ public class SecurableBackwardsCompatibilityTest { - + /** * Test the decryption of a String encrypted with DES ECB. */ @Test public void testStringDecryption_DES_ECB() throws Exception { String plainTextString = "welcome123_des_ecb"; - + String testString = encryptString_DES_ECB(plainTextString); Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); - + + JCEEncryptorCmd jceEncryptorCmd = new JCEEncryptorCmd(); + String decryptedString = jceEncryptorCmd.decryptPassword(testString); + Assert.assertEquals("Strings should match.", plainTextString, decryptedString); + } + + /** + * Test the decryption of a String encrypted with AES GCM. + */ + @Test + public void testStringDecryption_AES_GCM() throws Exception { + String plainTextString = "welcome123_aes_gcm"; + Securable securable = new JCEEncryptor(); + String testString = securable.encryptPassword(plainTextString); + Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); + String decryptedString = securable.decryptPassword(testString); Assert.assertEquals("Strings should match.", plainTextString, decryptedString); } - + + /** + * Test the decryption of a String encrypted with AES GCM via JCEEncryptorCmd. + */ + @Test + public void testStringDecryption_AES_GCM_via_jceEncryptorCmd() throws Exception { + String plainTextString = "welcome123_aes_gcm"; + + Securable securable = new JCEEncryptor(); + String testString = securable.encryptPassword(plainTextString); + Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); + + JCEEncryptorCmd jceEncryptorCmd = new JCEEncryptorCmd(); + String decryptedString = jceEncryptorCmd.decryptPassword(testString); + + Assert.assertEquals("Strings should match.", plainTextString, decryptedString); + } + /** * Test the decryption of a String encrypted with AES CBC. */ @@ -58,41 +92,51 @@ public void testStringDecryption_DES_ECB() throws Exception { public void testStringDecryption_AES_CBC() throws Exception { String plainTextString = "welcome123_aes_cbc"; - Securable securable = new JCEEncryptor(); - String testString = securable.encryptPassword(plainTextString); + String testString = encryptString_AES_CBC(plainTextString); Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); - - String decryptedString = securable.decryptPassword(testString); + + JCEEncryptorCmd jceEncryptorCmd = new JCEEncryptorCmd(); + String decryptedString = jceEncryptorCmd.decryptPassword(testString); Assert.assertEquals("Strings should match.", plainTextString, decryptedString); } - + /** * Test the decryption of a String encrypted with AES ECB. */ @Test public void testStringDecryption_AES_ECB() throws Exception { String plainTextString = "welcome123_aes_ecb"; - + String testString = encryptString_AES_ECB(plainTextString); Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); - - Securable securable = new JCEEncryptor(); - String decryptedString = securable.decryptPassword(testString); + + JCEEncryptorCmd jceEncryptorCmd = new JCEEncryptorCmd(); + String decryptedString = jceEncryptorCmd.decryptPassword(testString); Assert.assertEquals("Strings should match.", plainTextString, decryptedString); } - + /** * Test the decryption/processing of a plaintext String. */ @Test public void testStringDecryption_PlainText() throws Exception { String plainTextString = "welcome123_plaintext"; - + Securable securable = new JCEEncryptor(); String decryptedString = securable.decryptPassword(plainTextString); Assert.assertEquals("Passwords should match.", plainTextString, decryptedString); } - + + /** + * Test the decryption/processing of an empty String "". + */ + @Test + public void testEmptyStringParameterDecryption() throws Exception { + Securable securable = new JCEEncryptor(); + String returnValue = securable.decryptPassword(""); + Assert.assertEquals("Empty string \"\" should be returned when decrypting a \"\" (empty string) value", "", returnValue); + } + /** * Test the decryption/processing of a null parameter. */ @@ -102,7 +146,7 @@ public void testNullParameterDecryption() throws Exception { String returnValue = securable.decryptPassword(null); Assert.assertNull("Null should be returned when decrypting a null value", returnValue); } - + /** * Test the encryption of a null parameter. */ @@ -117,17 +161,36 @@ public void testNullParameterEncryption() throws Exception { } Assert.assertNotNull("A ValidationException should be thrown when encrypting a null value", expectedException); } - + + /** + * Test the decryption of a String encrypted with AES ECB and let JCEEncryptor throw Exception + */ + @Test + public void testStringDecryptionDeprecatedAlgorithmWithException() throws Exception { + ValidationException expectedException = ValidationException.errorDecryptingPasswordOldAlgorithm(null); + String plainTextString = "welcome123_aes_ecb"; + + String testString = encryptString_AES_ECB(plainTextString); + Assert.assertFalse("Strings should not match.", plainTextString.equals(testString)); + + Securable securable = new JCEEncryptor(); + try { + String decryptedString = securable.decryptPassword(testString); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains(expectedException.getMessage())); + } + } + /* * Internal test utility: * Return a DES ECB encrypted version of the String parameter, using the legacy encryption code. */ private String encryptString_DES_ECB(String aString) throws Exception { final byte[] bytes = Helper.buildBytesFromHexString("E60B80C7AEC78038"); - + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(bytes))); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); CipherOutputStream cos = new CipherOutputStream(baos, cipher); ObjectOutputStream oos = new ObjectOutputStream(cos); @@ -137,17 +200,31 @@ private String encryptString_DES_ECB(String aString) throws Exception { return Helper.buildHexStringFromBytes(baos.toByteArray()); } - + + /* + * Internal test utility: + * Return an AES CBC encrypted version of the String parameter, using the legacy encryption code. + */ + private String encryptString_AES_CBC(String aString) throws Exception { + final byte[] bytes = Helper.buildBytesFromHexString("2DB7354A48F1CA7B48ACA247540FC923"); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + IvParameterSpec iv = getIvSpec(); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(bytes, "AES"), iv); + + return Helper.buildHexStringFromBytes(cipher.doFinal(aString.getBytes("UTF-8"))); + } + /* * Internal test utility: * Return an AES ECB encrypted version of the String parameter, using the legacy encryption code. */ private String encryptString_AES_ECB(String aString) throws Exception { final byte[] bytes = Helper.buildBytesFromHexString("3E7CFEF156E712906E1F603B59463C67"); - + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(bytes, "AES")); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); CipherOutputStream cos = new CipherOutputStream(baos, cipher); ObjectOutputStream oos = new ObjectOutputStream(cos); @@ -157,5 +234,13 @@ private String encryptString_AES_ECB(String aString) throws Exception { return Helper.buildHexStringFromBytes(baos.toByteArray()); } - + + private static IvParameterSpec getIvSpec() { + byte[] b = new byte[] { + (byte) -26, (byte) 124, (byte) -99, (byte) 32, + (byte) -37, (byte) -58, (byte) -93, (byte) 100, + (byte) 126, (byte) -55, (byte) -21, (byte) 48, + (byte) -86, (byte) 97, (byte) 12, (byte) 113}; + return new IvParameterSpec(b); + } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/module-info.java b/foundation/org.eclipse.persistence.core/src/main/java/module-info.java index bdb3097411d..b5c025fbff7 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/module-info.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -100,6 +100,7 @@ exports org.eclipse.persistence.platform.server.wls; exports org.eclipse.persistence.platform.xml; exports org.eclipse.persistence.queries; + exports org.eclipse.persistence.security; exports org.eclipse.persistence.sequencing; exports org.eclipse.persistence.services; exports org.eclipse.persistence.services.glassfish; diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/ValidationException.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/ValidationException.java index 68ab1ab02f7..d03fd991990 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/ValidationException.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/ValidationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -150,6 +150,7 @@ public class ValidationException extends EclipseLinkException { public static final int ERROR_DECRYPTING_PASSWORD = 7107; public static final int NOT_SUPPORTED_FOR_DATASOURCE = 7108; public static final int PROJECT_LOGIN_IS_NULL = 7109; + public static final int ENCRYPTION_OLD_ALGORITHM = 7360; // for flashback: public static final int HISTORICAL_SESSION_ONLY_SUPPORTED_ON_ORACLE = 7110; @@ -905,6 +906,13 @@ public static ValidationException errorDecryptingPassword(Exception exception) { return validationException; } + public static ValidationException errorDecryptingPasswordOldAlgorithm(Exception exception) { + Object[] args = { }; + ValidationException validationException = new ValidationException(ExceptionMessageGenerator.buildMessage(ValidationException.class, ENCRYPTION_OLD_ALGORITHM, args), exception); + validationException.setErrorCode(ENCRYPTION_OLD_ALGORITHM); + return validationException; + } + public static ValidationException invalidEncryptionClass(String className, Throwable throwableError) { Object[] args = { className }; ValidationException validationException = new ValidationException(ExceptionMessageGenerator.buildMessage(ValidationException.class, INVALID_ENCRYPTION_CLASS, args)); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/i18n/ValidationExceptionResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/i18n/ValidationExceptionResource.java index 2ce9d44bc94..32a6cd86c65 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/i18n/ValidationExceptionResource.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/exceptions/i18n/ValidationExceptionResource.java @@ -139,6 +139,7 @@ public final class ValidationExceptionResource extends ListResourceBundle { { "7105", "Error encountered converting encrypting class: [{0}]" }, { "7106", "Error encountered during string encryption." }, { "7107", "Error encountered during string decryption." }, + { "7360", "Database password was encrypted by deprecated algorithm.\nIt's recommended to re-encrypt it by `passwordUpdate.sh` from eclipselink.zip bundle"}, { "7108", "This operation is not supported for non-relational platforms." }, { "7109", "The login in the project used to create the session is null, it must be a valid login." }, { "7110", "At present HistoricalSession only works with Oracle 9R2 or later databases, as it uses Oracle''s Flashback feature." }, diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java index bb8770a5179..fa1afe6d8aa 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java @@ -476,7 +476,11 @@ public class LoggingLocalizationResource extends ListResourceBundle { { "validate_object_space", "validate object space." }, { "stack_of_visited_objects_that_refer_to_the_corrupt_object", "stack of visited objects that refer to the corrupt object: {0}" }, { "corrupt_object_referenced_through_mapping", "The following corrupt object is referenced through mapping: {0}" }, - { "corrupt_object", "corrupt object: {0}" } + { "corrupt_object", "corrupt object: {0}" }, + + { "encryptor_script_usage", "Usage is `passwordUpdate.sh|.cmd -ip `"}, + { "encryptor_script_description", "This application internally decrypt old encrypted password used by some previous version EclipseLink and encrypt it by latest algorithm."}, + { "encryptor_script_output", "Re-encrypted password is: {0}"} }; /** diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/security/JCEEncryptor.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/security/JCEEncryptor.java index 154ea952742..6397e556321 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/security/JCEEncryptor.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/security/JCEEncryptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,42 +14,33 @@ // Oracle - initial API and implementation from Oracle TopLink package org.eclipse.persistence.internal.security; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; +import org.eclipse.persistence.exceptions.ConversionException; +import org.eclipse.persistence.exceptions.ValidationException; +import org.eclipse.persistence.internal.helper.Helper; import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; import javax.crypto.IllegalBlockSizeException; import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; - -import org.eclipse.persistence.exceptions.ConversionException; -import org.eclipse.persistence.exceptions.ValidationException; -import org.eclipse.persistence.internal.helper.Helper; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; /** * EclipseLink reference implementation for password encryption. * * @author Guy Pelletier */ -public class JCEEncryptor implements Securable { - - // Legacy DES ECB cipher used for backwards compatibility decryption only. - private static final String DES_ECB = "DES/ECB/PKCS5Padding"; - private final Cipher decryptCipherDES_ECB; +public final class JCEEncryptor implements org.eclipse.persistence.security.Securable { - // Legacy AES ECB cipher used for backwards compatibility decryption only. - private static final String AES_ECB = "AES/ECB/PKCS5Padding"; - private final Cipher decryptCipherAES_ECB; + // All encryption is done through the AES GCM cipher. + private static final byte IV_GCM_LENGTH = 16; + private static final String AES_GCM = "AES/GCM/NoPadding"; + private final Cipher encryptCipherAES_GCM; + private final Cipher decryptCipherAES_GCM; - // All encryption is done through the AES CBC cipher. - private static final String AES_CBC = "AES/CBC/PKCS5Padding"; - private final Cipher encryptCipherAES_CBC; - private final Cipher decryptCipherAES_CBC; public JCEEncryptor() throws Exception { /** @@ -63,19 +54,9 @@ public JCEEncryptor() throws Exception { * * Confusing??? Well, don't move this code before talking to Guy first! */ - decryptCipherDES_ECB = Cipher.getInstance(DES_ECB); - decryptCipherDES_ECB.init(Cipher.DECRYPT_MODE, Synergizer.getDESMultitasker()); - - decryptCipherAES_ECB = Cipher.getInstance(AES_ECB); - decryptCipherAES_ECB.init(Cipher.DECRYPT_MODE, Synergizer.getAESMultitasker()); + encryptCipherAES_GCM = Cipher.getInstance(AES_GCM); - SecretKey sk = Synergizer.getAESCBCMultitasker(); - IvParameterSpec iv = Synergizer.getIvSpec(); - encryptCipherAES_CBC = Cipher.getInstance(AES_CBC); - encryptCipherAES_CBC.init(Cipher.ENCRYPT_MODE, sk, iv); - - decryptCipherAES_CBC = Cipher.getInstance(AES_CBC); - decryptCipherAES_CBC.init(Cipher.DECRYPT_MODE, sk, iv); + decryptCipherAES_GCM = Cipher.getInstance(AES_GCM); } /** @@ -84,7 +65,14 @@ public JCEEncryptor() throws Exception { @Override public synchronized String encryptPassword(String password) { try { - return Helper.buildHexStringFromBytes(encryptCipherAES_CBC.doFinal(password.getBytes("UTF-8"))); + byte[] ivGCM = Synergizer.getIvGCM(); + AlgorithmParameterSpec parameterSpecGCM = new GCMParameterSpec(128, ivGCM); + SecretKey skGCM = Synergizer.getAESGCMMultitasker(); + encryptCipherAES_GCM.init(Cipher.ENCRYPT_MODE, skGCM, parameterSpecGCM); + byte[] bytePassword = encryptCipherAES_GCM.doFinal(password.getBytes("UTF-8")); + byte[] result = Arrays.copyOf(ivGCM, IV_GCM_LENGTH + bytePassword.length); + System.arraycopy(bytePassword, 0, result, IV_GCM_LENGTH, bytePassword.length); + return Helper.buildHexStringFromBytes(result); } catch (Exception e) { throw ValidationException.errorEncryptingPassword(e); } @@ -96,58 +84,31 @@ public synchronized String encryptPassword(String password) { */ @Override public synchronized String decryptPassword(String encryptedPswd) { - if (encryptedPswd == null) { + if (encryptedPswd == null) { return null; } String password = null; - byte[] bytePassword = new byte[0]; - + byte[] input = null; + byte[] bytePassword = null; + try { - bytePassword = Helper.buildBytesFromHexString(encryptedPswd); - // try AES/CBC first - password = new String(decryptCipherAES_CBC.doFinal(bytePassword), "UTF-8"); - } catch (ConversionException | IllegalBlockSizeException ce) { + input = Helper.buildBytesFromHexString(encryptedPswd); + SecretKey skGCM = Synergizer.getAESGCMMultitasker(); + byte[] ivGCM = new byte[IV_GCM_LENGTH]; + System.arraycopy(input, 0, ivGCM, 0, IV_GCM_LENGTH); + AlgorithmParameterSpec parameterSpecGCM = new GCMParameterSpec(128, ivGCM); + bytePassword = new byte[input.length - IV_GCM_LENGTH]; + System.arraycopy(input, IV_GCM_LENGTH, bytePassword, 0, input.length - IV_GCM_LENGTH); + decryptCipherAES_GCM.init(Cipher.DECRYPT_MODE, skGCM, parameterSpecGCM); + // try AES/GCM first + password = new String(decryptCipherAES_GCM.doFinal(bytePassword), "UTF-8"); + } catch (ArrayIndexOutOfBoundsException | ConversionException | IllegalBlockSizeException ce) { // buildBytesFromHexString failed, assume clear text password = encryptedPswd; - } catch (Exception e) { - ObjectInputStream oisAes = null; - try { - // try AES/ECB second - oisAes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherAES_ECB)); - password = (String)oisAes.readObject(); - } catch (Exception f) { - ObjectInputStream oisDes = null; - try { - // try DES/ECB third - oisDes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherDES_ECB)); - password = (String)oisDes.readObject(); - } catch (ArrayIndexOutOfBoundsException g) { - // JCE 1.2.1 couldn't decrypt it, assume clear text - password = encryptedPswd; - } catch (Exception h) { - if (h.getCause() instanceof IllegalBlockSizeException) { - // JCE couldn't decrypt it, assume clear text - password = encryptedPswd; - } else { - throw ValidationException.errorDecryptingPassword(h); - } - } finally { - if (oisDes != null) { - try { - oisDes.close(); - } catch (IOException e2) {} - } - } - } finally { - if (oisAes != null) { - try { - oisAes.close(); - } catch (IOException e1) {} - } - } + } catch (Exception u) { + throw ValidationException.errorDecryptingPasswordOldAlgorithm(u); } - return password; } @@ -155,26 +116,21 @@ public synchronized String decryptPassword(String encryptedPswd) { * Returns multitaskers for the ciphers. :-) */ private static class Synergizer { - private static SecretKey getDESMultitasker() throws Exception { - SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); - return factory.generateSecret(new DESKeySpec(Helper.buildBytesFromHexString("E60B80C7AEC78038"))); - } - private static SecretKey getAESMultitasker() throws Exception { - return new SecretKeySpec(Helper.buildBytesFromHexString("3E7CFEF156E712906E1F603B59463C67"), "AES"); + private static SecretKey getAESGCMMultitasker() throws Exception { + return new SecretKeySpec(Helper.buildBytesFromHexString("64EF2D9B738ACA254A48F14754030FC2"), "AES"); } - private static SecretKey getAESCBCMultitasker() throws Exception { - return new SecretKeySpec(Helper.buildBytesFromHexString("2DB7354A48F1CA7B48ACA247540FC923"), "AES"); - } - - private static IvParameterSpec getIvSpec() { - byte[] b = new byte[] { - (byte) -26, (byte) 124, (byte) -99, (byte) 32, - (byte) -37, (byte) -58, (byte) -93, (byte) 100, - (byte) 126, (byte) -55, (byte) -21, (byte) 48, - (byte) -86, (byte) 97, (byte) 12, (byte) 113}; - return new IvParameterSpec(b); + private static byte[] getIvGCM() { + byte[] ivGCM = new byte[IV_GCM_LENGTH]; + SecureRandom random = null; + try { + random = SecureRandom.getInstanceStrong(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + random.nextBytes(ivGCM); + return ivGCM; } } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/xml/XMLComparer.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/xml/XMLComparer.java index a2083744d26..332f5e5f94d 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/xml/XMLComparer.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/xml/XMLComparer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,6 +33,9 @@ public class XMLComparer { private boolean ignoreOrder; + //Useful e.g. for password element where is content encrypted by AES/GCM/NoPadding where is randomly generated input vector for every encrypt(.) call. + //Control document and test document is not same in this part. + private String ignoreElementName = null; public XMLComparer() { super(); @@ -143,6 +146,9 @@ private boolean isDocumentTypeEqual(DocumentType control, DocumentType test) { } private boolean isElementEqual(Element control, Element test) { + if (ignoreElementName != null && isStringEqual(ignoreElementName, test.getTagName())) { + return true; + } if (!isStringEqual(control.getNamespaceURI(), test.getNamespaceURI())) { return false; } @@ -253,4 +259,12 @@ public boolean isIgnoreOrder() { public void setIgnoreOrder(boolean ignoreOrder) { this.ignoreOrder = ignoreOrder; } + + public String getIgnoreElementName() { + return ignoreElementName; + } + + public void setIgnoreElementName(String ignoreElementName) { + this.ignoreElementName = ignoreElementName; + } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/security/Securable.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/security/Securable.java new file mode 100644 index 00000000000..22eb88af4bc --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/security/Securable.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// Oracle - initial API and implementation from Oracle TopLink +package org.eclipse.persistence.security; + +/** + * EclipseLink encryption interface + */ +public interface Securable extends org.eclipse.persistence.internal.security.Securable { + + @Override + String encryptPassword(String pswd); + + @Override + String decryptPassword(String encryptedPswd); +} diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/security/JCEEncryptorCmd.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/security/JCEEncryptorCmd.java new file mode 100644 index 00000000000..3a8f59ca346 --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/security/JCEEncryptorCmd.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// Oracle - initial API and implementation +package org.eclipse.persistence.tools.security; + +import org.eclipse.persistence.exceptions.ValidationException; +import org.eclipse.persistence.internal.helper.Helper; +import org.eclipse.persistence.internal.localization.LoggingLocalization; +import org.eclipse.persistence.internal.security.JCEEncryptor; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; + +public final class JCEEncryptorCmd { + + private JCEEncryptor jceEncryptor = new JCEEncryptor(); + + // Legacy DES ECB cipher used for backwards compatibility decryption only. + private static final String DES_ECB = "DES/ECB/PKCS5Padding"; + private final Cipher decryptCipherDES_ECB; + + // Legacy AES ECB cipher used for backwards compatibility decryption only. + private static final String AES_ECB = "AES/ECB/PKCS5Padding"; + private final Cipher decryptCipherAES_ECB; + + // Legacy AES CBC cipher used for backwards compatibility decryption only. + private static final String AES_CBC = "AES/CBC/PKCS5Padding"; + private final Cipher decryptCipherAES_CBC; + + public JCEEncryptorCmd() throws Exception { + decryptCipherDES_ECB = Cipher.getInstance(DES_ECB); + decryptCipherDES_ECB.init(Cipher.DECRYPT_MODE, JCEEncryptorCmd.Synergizer.getDESMultitasker()); + + decryptCipherAES_ECB = Cipher.getInstance(AES_ECB); + decryptCipherAES_ECB.init(Cipher.DECRYPT_MODE, JCEEncryptorCmd.Synergizer.getAESMultitasker()); + + SecretKey sk = JCEEncryptorCmd.Synergizer.getAESCBCMultitasker(); + IvParameterSpec iv = JCEEncryptorCmd.Synergizer.getIvSpec(); + decryptCipherAES_CBC = Cipher.getInstance(AES_CBC); + decryptCipherAES_CBC.init(Cipher.DECRYPT_MODE, sk, iv); + } + + public static void main(String[] args) throws Exception { + JCEEncryptorCmd encryptorCmd = new JCEEncryptorCmd(); + encryptorCmd.start(args); + } + + private void start(String[] args) throws Exception { + if (args.length < 2 || !args[0].equals("-ip")) { + System.out.println(LoggingLocalization.buildMessage("encryptor_script_usage", null) + + "\n" + LoggingLocalization.buildMessage("encryptor_script_description", null)); + } else { + System.out.println(LoggingLocalization.buildMessage("encryptor_script_output", new Object[]{jceEncryptor.encryptPassword(decryptPassword(args[1]))})); + } + } + + public String decryptPassword(String encryptedPswd) throws Exception { + String password = null; + byte[] bytePassword = null; + // try default AES/GCM algorithm first + try { + password = jceEncryptor.decryptPassword(encryptedPswd); + } catch (Exception u) { + try { + // try AES/CBC second + bytePassword = Helper.buildBytesFromHexString(encryptedPswd); + password = new String(decryptCipherAES_CBC.doFinal(bytePassword), "UTF-8"); + } catch (Exception w) { + ObjectInputStream oisAes = null; + try { + // try AES/ECB third + oisAes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherAES_ECB)); + password = (String) oisAes.readObject(); + } catch (Exception x) { + ObjectInputStream oisDes = null; + try { + // try DES/ECB fourth + oisDes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherDES_ECB)); + password = (String) oisDes.readObject(); + } catch (ArrayIndexOutOfBoundsException y) { + // JCE 1.2.1 couldn't decrypt it, assume clear text + password = encryptedPswd; + } catch (Exception z) { + if (z.getCause() instanceof IllegalBlockSizeException) { + // JCE couldn't decrypt it, assume clear text + password = encryptedPswd; + } else { + throw ValidationException.errorDecryptingPassword(z); + } + } finally { + if (oisDes != null) { + try { + oisDes.close(); + } catch (IOException e2) { + } + } + } + } finally { + if (oisAes != null) { + try { + oisAes.close(); + } catch (IOException e1) { + } + } + } + } + } + + return password; + } + + /** + * Returns multitaskers for the ciphers. :-) + */ + private static class Synergizer { + + private static SecretKey getDESMultitasker() throws Exception { + SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); + return factory.generateSecret(new DESKeySpec(Helper.buildBytesFromHexString("E60B80C7AEC78038"))); + } + + private static SecretKey getAESMultitasker() throws Exception { + return new SecretKeySpec(Helper.buildBytesFromHexString("3E7CFEF156E712906E1F603B59463C67"), "AES"); + } + + private static SecretKey getAESCBCMultitasker() throws Exception { + return new SecretKeySpec(Helper.buildBytesFromHexString("2DB7354A48F1CA7B48ACA247540FC923"), "AES"); + } + + private static IvParameterSpec getIvSpec() { + byte[] b = new byte[]{ + (byte) -26, (byte) 124, (byte) -99, (byte) 32, + (byte) -37, (byte) -58, (byte) -93, (byte) 100, + (byte) 126, (byte) -55, (byte) -21, (byte) 48, + (byte) -86, (byte) 97, (byte) 12, (byte) 113}; + return new IvParameterSpec(b); + } + } +} diff --git a/utils/org.eclipse.persistence.dbws.builder/src/it/java/dbws/testing/loglevelvalidation/LogLevelValidationTestSuite.java b/utils/org.eclipse.persistence.dbws.builder/src/it/java/dbws/testing/loglevelvalidation/LogLevelValidationTestSuite.java index 141eef13236..10ff1862187 100644 --- a/utils/org.eclipse.persistence.dbws.builder/src/it/java/dbws/testing/loglevelvalidation/LogLevelValidationTestSuite.java +++ b/utils/org.eclipse.persistence.dbws.builder/src/it/java/dbws/testing/loglevelvalidation/LogLevelValidationTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,6 +17,8 @@ //javase imports import java.io.StringReader; import java.lang.reflect.Field; + +import org.eclipse.persistence.platform.xml.XMLComparer; import org.w3c.dom.Document; //java eXtension imports @@ -225,6 +227,8 @@ static String buildSessionsXML() { * Positive test. */ public void testInvalidLogLevel() { + XMLComparer comparer = new XMLComparer(); + comparer.setIgnoreElementName("password"); Document testDoc = xmlParser.parse(new StringReader(DBWS_SESSION_STREAM.toString())); removeEmptyTextNodes(testDoc); Document controlDoc = xmlParser.parse(new StringReader(SESSIONS_XML));