package de.persosim.simulator.crypto;
import static org.globaltester.logging.BasicLogger.log;
import static org.globaltester.logging.BasicLogger.logException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.globaltester.cryptoprovider.Crypto;
import de.persosim.simulator.utils.HexString;
public abstract class CryptoSupport {
protected String cipherAlgorithmNameModePadding;
protected String macName;
protected Cipher cipher;
protected Mac mac;
/*--------------------------------------------------------------------------------*/
public CryptoSupport() {
}
public CryptoSupport(String algorithmNameModePadding, String macName) {
if(algorithmNameModePadding == null) {throw new NullPointerException("algorithm must not be null");}
this.setCipherAlgorithmNameModePadding(algorithmNameModePadding);
this.setMacName(macName);
}
/*--------------------------------------------------------------------------------*/
public SecretKeySpec generateSecretKeySpec(byte[] key, String algorithm) {
return new SecretKeySpec(key, algorithm);
}
protected abstract String getCipherAlgorithm();
public String getCipherAlgorithmNameModePadding() {
return cipherAlgorithmNameModePadding;
}
public void setCipherAlgorithmNameModePadding(String cipherAlgorithmNameModePadding) {
String cipherAlgorithmExpected, cipherAlgorithmReceived;
cipherAlgorithmExpected = this.getCipherAlgorithm();
cipherAlgorithmReceived = CryptoUtil.getCipherNameAsString(cipherAlgorithmNameModePadding);
if(!cipherAlgorithmExpected.equals(cipherAlgorithmReceived)) {
throw new IllegalArgumentException("algorithm must be " + cipherAlgorithmExpected);
}
this.cipherAlgorithmNameModePadding = cipherAlgorithmNameModePadding;
try {
this.cipher = Cipher.getInstance(this.cipherAlgorithmNameModePadding, Crypto.getCryptoProvider());
} catch (GeneralSecurityException e) {
logException(getClass(), e);
throw new IllegalArgumentException(e);
}
}
public void setMacName(String macName) {
this.macName = macName;
try {
this.mac = Mac.getInstance(this.macName, Crypto.getCryptoProvider());
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
}
public SecretKeySpec generateSecretKeySpecCipher(byte[] in) {
return this.generateSecretKeySpec(in, this.cipherAlgorithmNameModePadding);
}
public SecretKeySpec generateSecretKeySpecMac(byte[] in) {
return this.generateSecretKeySpec(in, this.macName);
}
public SecretKeySpec generateSecretKeySpec(byte[] in, int offset, int length, String algorithm) {
return this.generateSecretKeySpec(Arrays.copyOfRange(in, offset, offset + length), algorithm);
}
public SecretKeySpec generateSecretKeySpecCipher(byte[] in, int offset, int length) {
return this.generateSecretKeySpec(in, offset, length, this.cipherAlgorithmNameModePadding);
}
public SecretKeySpec generateSecretKeySpecMac(byte[] in, int offset, int length) {
return this.generateSecretKeySpec(in, offset, length, this.macName);
}
public byte[] encrypt(byte[] plainText, Key key, IvParameterSpec ivParams) {
return encrypt(this.cipher, plainText, key, ivParams);
}
public static byte[] encrypt(Cipher cipher, byte[] plainText, Key key, IvParameterSpec ivParams) {
byte[] encryptedNonce;
try {
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
encryptedNonce = cipher.doFinal(plainText);
return encryptedNonce;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("invalid key");
} catch (InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException("invalid ivParams");
} catch (IllegalBlockSizeException e) {
throw new IllegalArgumentException("illegal blocksize");
} catch (BadPaddingException e) {
throw new IllegalArgumentException("bad padding");
}
}
public static byte[] encryptWithIvZero(Cipher cipher, byte[] plainText, Key key) {
return encrypt(cipher, plainText, key, getIvSetToAllZeros(cipher.getBlockSize()));
}
public byte[] encryptWithIvZero(byte[] plainNonce, Key key) {
return this.encrypt(plainNonce, key, this.getIvSetToAllZeros());
}
public static byte[] decrypt(Cipher cipher, byte[] cipherText, Key key, AlgorithmParameterSpec aps) {
byte[] plainText;
try {
if(aps != null) {
cipher.init(Cipher.DECRYPT_MODE, key, aps);
} else{
cipher.init(Cipher.DECRYPT_MODE, key);
}
plainText = cipher.doFinal(cipherText);
return plainText;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("invalid key");
} catch (InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException("invalid iv");
} catch (IllegalBlockSizeException e) {
throw new IllegalArgumentException("illegal blocksize");
} catch (BadPaddingException e) {
throw new IllegalArgumentException("bad padding");
}
}
public static byte[] decryptWithIvZero(Cipher cipher, byte[] cipherText, Key key) {
return decrypt(cipher, cipherText, key, getIvSetToAllZeros(cipher.getBlockSize()));
}
public byte[] decrypt(byte[] cipherText, Key key, AlgorithmParameterSpec aps) {
return decrypt(this.cipher, cipherText, key, aps);
}
public byte[] decryptWithIvZero(byte[] cipherText, Key key) {
return this.decrypt(cipherText, key, this.getIvSetToAllZeros());
}
public byte[] macPlain(byte[] tokenPlain, Key key) {
return macPlain(this.mac, tokenPlain, key);
}
public static byte[] macPlain(Mac mac, byte[] tokenPlain, Key key) {
try {
mac.init(key);
log(CryptoSupport.class, "used mac algorithm is: " + mac.getAlgorithm());
return mac.doFinal(tokenPlain);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
}
public static byte[] mac(Mac mac, byte[] auxiliaryBlock, Cipher cipherEnc, byte[] macInput, Key macKey, int macLength) {
byte[] processedMacInput = new byte[auxiliaryBlock.length + macInput.length];
System.arraycopy(auxiliaryBlock, 0, processedMacInput, 0, auxiliaryBlock.length);
System.arraycopy(macInput, 0, processedMacInput, auxiliaryBlock.length, macInput.length);
log(CryptoSupport.class, "processed mac input is: " + HexString.encode(macInput));
byte [] macResult = CryptoSupport.macPlain(mac, processedMacInput, macKey);
log(CryptoSupport.class, "raw mac is: " + HexString.encode(macResult));
macResult = Arrays.copyOf(macResult, macLength);
log(CryptoSupport.class, "expected mac is : " + HexString.encode(macResult));
return macResult;
}
public byte[] computeInitialBlockFromAuxiliaryData(byte[] auxiliaryBlock, byte[] macInput, Key macKey) {
return computeInitialBlockFromAuxiliaryData(auxiliaryBlock, this.cipher, macInput, macKey);
}
public static byte[] computeInitialBlockFromAuxiliaryData(byte[] auxiliaryBlock, Cipher cipherEnc, byte[] macInput, Key macKey) {
byte[] processedMacInput = Arrays.copyOf(macInput, macInput.length);
byte[] initialBlock = CryptoSupport.encryptWithIvZero(cipherEnc, auxiliaryBlock, macKey);
log(CryptoSupport.class, "initial block is: " + HexString.encode(initialBlock));
for(int i = 0; i < initialBlock.length; i++) {
processedMacInput[i] = (byte) (macInput[i] ^ initialBlock[i]);
}
return processedMacInput;
}
public byte[] macAuthenticationToken(byte[] tokenPlain, Key key) {
return this.macPlain(tokenPlain, key);
}
public int getBlockSize() {
return this.cipher.getBlockSize();
}
public IvParameterSpec getIvSetToAllZeros() {
return getIvSetToAllZeros(this.cipher.getBlockSize());
}
public static IvParameterSpec getIvSetToAllZeros(int blockSizeInBytes) {
byte[] ivMaterial;
ivMaterial = new byte[blockSizeInBytes];
Arrays.fill(ivMaterial, (byte) 0x00);
/* initialization vector for block cipher */
return new IvParameterSpec(ivMaterial);
}
public String getCipherNameModePadding() {
return this.cipherAlgorithmNameModePadding;
}
public String getCipherName() {
return CryptoUtil.getCipherNameAsString(this.cipherAlgorithmNameModePadding);
}
public String getMacName() {
return this.macName;
}
}