package org.apache.kerberos.kerb.crypto;
import org.apache.kerberos.kerb.crypto.enc.EncryptProvider;
import org.apache.kerberos.kerb.KrbException;
import java.util.Arrays;
/**
* Based on MIT krb5 cmac.c
*/
public class Cmac {
private static byte[] constRb = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x87
};
public static byte[] cmac(EncryptProvider encProvider, byte[] key,
byte[] data, int outputSize) throws KrbException {
return cmac(encProvider, key, data, 0, data.length, outputSize);
}
public static byte[] cmac(EncryptProvider encProvider, byte[] key, byte[] data,
int start, int len, int outputSize) throws KrbException {
byte[] hash = Cmac.cmac(encProvider, key, data, start, len);
if (hash.length > outputSize) {
byte[] output = new byte[outputSize];
System.arraycopy(hash, 0, output, 0, outputSize);
return output;
} else {
return hash;
}
}
public static byte[] cmac(EncryptProvider encProvider,
byte[] key, byte[] data) throws KrbException {
return cmac(encProvider, key, data, 0, data.length);
}
public static byte[] cmac(EncryptProvider encProvider,
byte[] key, byte[] data, int start, int len) throws KrbException {
int blockSize = encProvider.blockSize();
byte[] Y = new byte[blockSize];
byte[] mLast = new byte[blockSize];
byte[] padded = new byte[blockSize];
byte[] K1 = new byte[blockSize];
byte[] K2 = new byte[blockSize];
// step 1
makeSubkey(encProvider, key, K1, K2);
// step 2
int n = (len + blockSize - 1) / blockSize;
// step 3
boolean lastIsComplete;
if (n == 0) {
n = 1;
lastIsComplete = false;
} else {
lastIsComplete = ((len % blockSize) == 0);
}
// Step 6 (all but last block)
byte[] cipherState = new byte[blockSize];
byte[] cipher = new byte[blockSize];
for (int i = 0; i < n - 1; i++) {
System.arraycopy(data, i * blockSize, cipher, 0, blockSize);
encryptBlock(encProvider, key, cipherState, cipher);
System.arraycopy(cipher, 0, cipherState, 0, blockSize);
}
// step 5
System.arraycopy(cipher, 0, Y, 0, blockSize);
// step 4
int lastPos = (n - 1) * blockSize;
int lastLen = lastIsComplete ? blockSize : len % blockSize;
byte[] lastBlock = new byte[lastLen];
System.arraycopy(data, lastPos, lastBlock, 0, lastLen);
if (lastIsComplete) {
Util.xor(lastBlock, K1, mLast);
} else {
padding(lastBlock, padded);
Util.xor(padded, K2, mLast);
}
// Step 6 (last block)
encryptBlock(encProvider, key, cipherState, mLast);
return mLast;
}
// Generate subkeys K1 and K2 as described in RFC 4493 figure 2.2.
private static void makeSubkey(EncryptProvider encProvider,
byte[] key, byte[] K1, byte[] K2) throws KrbException {
// L := encrypt(K, const_Zero)
byte[] L = new byte[K1.length];
Arrays.fill(L, (byte) 0);
encryptBlock(encProvider, key, null, L);
// K1 := (MSB(L) == 0) ? L << 1 : (L << 1) XOR const_Rb
if ((L[0] & 0x80) == 0) {
leftShiftByOne(L, K1);
} else {
byte[] tmp = new byte[K1.length];
leftShiftByOne(L, tmp);
Util.xor(tmp, constRb, K1);
}
// K2 := (MSB(K1) == 0) ? K1 << 1 : (K1 << 1) XOR const_Rb
if ((K1[0] & 0x80) == 0) {
leftShiftByOne(K1, K2);
} else {
byte[] tmp = new byte[K1.length];
leftShiftByOne(K1, tmp);
Util.xor(tmp, constRb, K2);
}
}
private static void encryptBlock(EncryptProvider encProvider,
byte[] key, byte[] cipherState, byte[] block) throws KrbException {
if (cipherState == null) {
cipherState = new byte[encProvider.blockSize()];
}
if (encProvider.supportCbcMac()) {
encProvider.cbcMac(key, cipherState, block);
} else {
encProvider.encrypt(key, cipherState, block);
}
}
private static void leftShiftByOne(byte[] input, byte[] output) {
byte overflow = 0;
for (int i = input.length - 1; i >= 0; i--) {
output[i] = (byte) (input[i] << 1);
output[i] |= overflow;
overflow = (byte) ((input[i] & 0x80) != 0 ? 1 : 0);
}
}
// Padding out data with a 1 bit followed by 0 bits, placing the result in pad
private static void padding(byte[] data, byte[] padded) {
int len = data.length;
// original last block
System.arraycopy(data, 0, padded, 0, len);
padded[len] = (byte) 0x80;
for (int i = len + 1; i < padded.length; i++) {
padded[i] = 0x00;
}
}
}