SEBSERV-335 fixed decoding for ASK

This commit is contained in:
anhefti 2023-03-02 09:09:19 +01:00
parent 686d4fa7ea
commit b65b411e1d
5 changed files with 331 additions and 11 deletions

View file

@ -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);

View file

@ -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<CharSequence> 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");
});
}
}

View file

@ -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<String, String> actionAttributes) {
// no additional check here
return null;
}
@Override
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
// TODO Auto-generated method stub
return null;
}
}

View file

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

View file

@ -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<CharSequence> 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<CharSequence> 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<CharSequence> 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;
}
}