diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index bc74ef3c..fc8c70f2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -21,6 +21,7 @@ public final class API { public enum BatchActionType { EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE), EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE), + EXAM_CONFIG_DELETE(EntityType.CONFIGURATION_NODE), ARCHIVE_EXAM(EntityType.EXAM), DELETE_EXAM(EntityType.EXAM); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java index 6c33a2f6..952a2b8c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java @@ -8,19 +8,30 @@ package ch.ethz.seb.sebserver.gbl.util; +import static org.springframework.security.crypto.util.EncodingUtils.subArray; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.PrivateKey; import java.security.cert.Certificate; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi; import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.BCPKCS12KeyStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; +import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.encrypt.Encryptors; +import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.stereotype.Service; @@ -144,4 +155,32 @@ public class Cryptor { }); } + public static Result decryptASK( + final CharSequence cipherText, + final CharSequence secret, + final CharSequence salt) { + + return Result.tryCatch(() -> { + + final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding"; + final Cipher cipher = Cipher.getInstance(AES_GCM_ALGORITHM); + final BytesKeyGenerator ivGen = KeyGenerators.secureRandom(16); + final PBEKeySpec keySpec = new PBEKeySpec(Utils.toCharArray(secret), Hex.decode(salt), 1024, 256); + final SecretKey secretKey = new SecretKeySpec( + SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(keySpec).getEncoded(), + "AES"); + + final byte[] plainCipherText = Hex.decode(cipherText); + + // decrypt + final byte[] iv = subArray(plainCipherText, 0, ivGen.getKeyLength()); + final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + final byte[] decrypted = cipher.doFinal(subArray(plainCipherText, iv.length, plainCipherText.length)); + + return new String(decrypted, "UTF-8"); + + }); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamConfig.java new file mode 100644 index 00000000..ec271e6b --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl; + +import java.util.Map; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.model.BatchAction; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; + +@Lazy +@Component +@WebServiceProfile +public class DeleteExamConfig implements BatchActionExec { + + private final ConfigurationNodeDAO configurationNodeDAO; + private final ExamConfigurationMapDAO examConfigurationMapDAO; + private final AuthorizationService authorization; + private final UserActivityLogDAO userActivityLogDAO; + + public DeleteExamConfig( + final ConfigurationNodeDAO configurationNodeDAO, + final ExamConfigurationMapDAO examConfigurationMapDAO, + final AuthorizationService authorization, + final UserActivityLogDAO userActivityLogDAO) { + + this.configurationNodeDAO = configurationNodeDAO; + this.examConfigurationMapDAO = examConfigurationMapDAO; + this.authorization = authorization; + this.userActivityLogDAO = userActivityLogDAO; + } + + @Override + public BatchActionType actionType() { + return BatchActionType.EXAM_CONFIG_DELETE; + } + + @Override + public APIMessage checkConsistency(final Map actionAttributes) { + // no additional check here + return null; + } + + @Override + public Result doSingleAction(final String modelId, final BatchAction batchAction) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/institution/impl/SecurityKeyServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/institution/impl/SecurityKeyServiceImpl.java index 1112ccda..bc486505 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/institution/impl/SecurityKeyServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/institution/impl/SecurityKeyServiceImpl.java @@ -178,17 +178,18 @@ public class SecurityKeyServiceImpl implements SecurityKeyService { // TODO if certificate encryption is available check if exam has defined certificate for decryption return Cryptor - .decrypt(appSignatureKey + salt, connectionToken) - .onErrorDo(error -> { - - log.warn( - "Failed to decrypt ASK with added salt value. Try to decrypt without added salt. Error: {}", - error.getMessage()); - - return Cryptor - .decrypt(appSignatureKey, connectionToken) - .getOrThrow(); - }) + .decryptASK(appSignatureKey, connectionToken, salt) +// .decrypt(appSignatureKey + salt, connectionToken) +// .onErrorDo(error -> { +// +// log.warn( +// "Failed to decrypt ASK with added salt value. Try to decrypt without added salt. Error: {}", +// error.getMessage()); +// +// return Cryptor +// .decrypt(appSignatureKey, connectionToken) +// .getOrThrow(); +// }) .map(signature -> createSignatureHash(signature)); } diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/util/CryptorTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/util/CryptorTest.java index 0ab480c9..1d965928 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/util/CryptorTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/util/CryptorTest.java @@ -11,9 +11,28 @@ package ch.ethz.seb.sebserver.gbl.util; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.security.crypto.util.EncodingUtils.subArray; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; import org.junit.Test; import org.springframework.core.env.Environment; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.codec.Utf8; +import org.springframework.security.crypto.encrypt.Encryptors; +import org.springframework.security.crypto.keygen.BytesKeyGenerator; +import org.springframework.security.crypto.keygen.KeyGenerators; public class CryptorTest { @@ -59,4 +78,197 @@ public class CryptorTest { assertEquals(clientSecret, decrypted); } + @Test + public void askEncryptDecryptStd() throws Exception { + + final String ask = "AppSignatureKey_+23"; + final String askHex = new String(Hex.encode(Utf8.encode(ask))); + + final String password = "somePassword"; + final String salt = "someSalt"; + final String saltHex = new String(Hex.encode(Utf8.encode(salt))); + + final String encrypted = Encryptors.delux(password, saltHex).encrypt(ask); + + final String decrypt = Encryptors.delux(password, saltHex).decrypt(encrypted); + + assertEquals("AppSignatureKey_+23", decrypt); + } + + @Test + public void askEncrypt() throws Exception { + + final String ask = "AppSignatureKey_+23"; + final String password = "somePassword"; + final String salt = "someSalt"; + + final byte[] ASKBin = Utf8.encode(ask); + final String passwordHex = toHex(password); + final String saltHex = toHex(salt); + + assertEquals(password, fromHex(passwordHex)); + assertEquals(salt, fromHex(saltHex)); + + final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding"; + final Cipher cipher = newCipher(AES_GCM_ALGORITHM); + final BytesKeyGenerator ivGen = KeyGenerators.secureRandom(16); + final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(saltHex), 1024, 256); + final SecretKey secretKey = new SecretKeySpec(newSecretKey("PBKDF2WithHmacSHA1", keySpec).getEncoded(), "AES"); + + // encrypt + final byte[] iv = ivGen.generateKey(); + final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); + final byte[] encrypted = doFinal(cipher, ASKBin); + final byte[] encryptedFinal = concatenate(iv, encrypted); + final String encryptedHex = new String(Hex.encode(encryptedFinal)); + + //assertEquals("", encryptedHex); + + final byte[] encryptedBin = Hex.decode(encryptedHex); + + // decrypt + final byte[] ivFromCipher = subArray(encryptedBin, 0, ivGen.getKeyLength()); + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + final byte[] decrypted = doFinal(cipher, encrypted(encryptedBin, ivFromCipher.length)); + + final String decryptedString = new String(decrypted, "UTF-8"); + assertEquals("AppSignatureKey_+23", decryptedString); + + } + + @Test + public void askDecrypt() throws Exception { + + final String ASKHex = + "ad36faae29807600a242466999f76f53fa62e5c285d109f1be049b71f92807343dfdb27bf56cb2ad974bad95cf50a9b56bc920"; + final String password = "somePassword"; + final String salt = "someSalt"; + + final byte[] ASKBin = Hex.decode(ASKHex); + final String passwordHex = toHex(password); + final String saltHex = toHex(salt); + + final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding"; + final Cipher cipher = newCipher(AES_GCM_ALGORITHM); + final BytesKeyGenerator ivGen = KeyGenerators.secureRandom(16); + final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(saltHex), 1024, 256); + final SecretKey secretKey = new SecretKeySpec(newSecretKey("PBKDF2WithHmacSHA1", keySpec).getEncoded(), "AES"); + + // decrypt + final byte[] iv = subArray(ASKBin, 0, ivGen.getKeyLength()); + final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + final byte[] decrypted = doFinal(cipher, encrypted(ASKBin, iv.length)); + + final String decryptedString = new String(decrypted, "UTF-8"); + + assertEquals("AppSignatureKey_+23", decryptedString); + } + + @Test + public void testCryptor_decryptASK() throws Exception { + final String ASKHex = + "ad36faae29807600a242466999f76f53fa62e5c285d109f1be049b71f92807343dfdb27bf56cb2ad974bad95cf50a9b56bc920"; + final String password = "somePassword"; + final String salt = "someSalt"; + final String saltHex = toHex(salt); + + final Result decryptASK = Cryptor.decryptASK(ASKHex, password, saltHex); + assertEquals("AppSignatureKey_+23", decryptASK.get()); + } + + @Test + public void testCryptor_decryptASK_Dany() throws Exception { + final String ASKHex = + "011101ADFE1263B4768397D209159023b25286d33f423ecb8be0964dad8986ebf81a93bd0dcb45236d320363"; + final String password = "password"; + final String saltHex = "abcdef1234"; + + final byte[] saltByteArray = Hex.decode(saltHex); + final String saltUTF8 = new String(saltByteArray, "UTF-8"); + + final Result decryptASK = Cryptor.decryptASK(ASKHex, password, saltHex); + if (decryptASK.hasError()) { + decryptASK.getError().printStackTrace(); + } + assertEquals("Hello World!", decryptASK.get()); + } + + @Test + public void testCryptor_decryptASK_Origin() throws Exception { + final String ASKHex = + "011101ADFE1263B4768397D209159023b25286d33f423ecb8be0964dad8986ebf81a93bd0dcb45236d320363"; + final String password = "password"; + final String saltHex = "abcdef1234"; + + final String decryptASK = Encryptors.delux(password, saltHex) + .decrypt(ASKHex); + //final Result decryptASK = Cryptor.decrypt(ASKHex + saltHex, password); + + final byte[] saltByteArray = Hex.decode(saltHex); + final String saltUTF8 = new String(saltByteArray, "UTF-8"); + + assertEquals("Hello World!", decryptASK); + } + + private byte[] encrypted(final byte[] encryptedBytes, final int ivLength) { + return subArray(encryptedBytes, ivLength, encryptedBytes.length); + } + + private String toHex(final String in) { + return String.valueOf(Hex.encode(in.getBytes())); + } + + private String fromHex(final String in) throws Exception { + return new String(Hex.decode(in), "UTF-8"); + } + + public static SecretKey newSecretKey(final String algorithm, final PBEKeySpec keySpec) { + try { + final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm); + return factory.generateSecret(keySpec); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Not a valid encryption algorithm", e); + } catch (final InvalidKeySpecException e) { + throw new IllegalArgumentException("Not a valid secret key", e); + } + } + + public static Cipher newCipher(final String algorithm) { + try { + return Cipher.getInstance(algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Not a valid encryption algorithm", e); + } catch (final NoSuchPaddingException e) { + throw new IllegalStateException("Should not happen", e); + } + } + + public static byte[] doFinal(final Cipher cipher, final byte[] input) { + try { + return cipher.doFinal(input); + } catch (final IllegalBlockSizeException e) { + throw new IllegalStateException( + "Unable to invoke Cipher due to illegal block size", e); + } catch (final BadPaddingException e) { + throw new IllegalStateException("Unable to invoke Cipher due to bad padding", + e); + } + } + + public static byte[] concatenate(final byte[]... arrays) { + int length = 0; + for (final byte[] array : arrays) { + length += array.length; + } + final byte[] newArray = new byte[length]; + int destPos = 0; + for (final byte[] array : arrays) { + System.arraycopy(array, 0, newArray, destPos, array.length); + destPos += array.length; + } + return newArray; + } + }