Merge "Support algorithm configurability in PacketUtils EspHeader"

This commit is contained in:
Lorenzo Colitti
2021-06-15 05:17:51 +00:00
committed by Gerrit Code Review

View File

@@ -44,6 +44,7 @@ public class PacketUtils {
static final int TCP_HDRLEN = 20;
static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
static final int ESP_BLK_SIZE = 4; // ESP has to be 4-byte aligned
static final int ESP_TRAILER_LEN = 2;
// Not defined in OsConstants
static final int IPPROTO_IPV4 = 4;
@@ -65,6 +66,7 @@ public class PacketUtils {
static final int CHACHA20_POLY1305_ICV_LEN = 16;
// Authentication parameters
static final int HMAC_SHA256_ICV_LEN = 16;
static final int HMAC_SHA512_KEY_LEN = 64;
static final int HMAC_SHA512_ICV_LEN = 32;
static final int AES_XCBC_KEY_LEN = 16;
@@ -328,8 +330,9 @@ public class PacketUtils {
public final int nextHeader;
public final int spi;
public final int seqNum;
public final byte[] key;
public final byte[] payload;
public final EspCipher cipher;
public final EspAuth auth;
/**
* Generic constructor for ESP headers.
@@ -340,11 +343,43 @@ public class PacketUtils {
* calculated using the pre-encryption IP header
*/
public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) {
this(nextHeader, spi, seqNum, payload, getDefaultCipher(key), getDefaultAuth(key));
}
/**
* Generic constructor for ESP headers that allows configuring encryption and authentication
* algortihms.
*
* <p>For Tunnel mode, payload will be a full IP header + attached payloads
*
* <p>For Transport mode, payload will be only the attached payloads, but with the checksum
* calculated using the pre-encryption IP header
*/
public EspHeader(
int nextHeader,
int spi,
int seqNum,
byte[] payload,
EspCipher cipher,
EspAuth auth) {
this.nextHeader = nextHeader;
this.spi = spi;
this.seqNum = seqNum;
this.key = key;
this.payload = payload;
this.cipher = cipher;
this.auth = auth;
if (cipher instanceof EspCipherNull && auth instanceof EspAuthNull) {
throw new IllegalArgumentException("No algorithm is provided");
}
}
private static EspCipher getDefaultCipher(byte[] key) {
return new EspCryptCipher(AES_CBC, AES_CBC_BLK_SIZE, key, AES_CBC_IV_LEN);
}
private static EspAuth getDefaultAuth(byte[] key) {
return new EspAuth(HMAC_SHA_256, key, HMAC_SHA256_ICV_LEN);
}
public int getProtocolId() {
@@ -352,9 +387,8 @@ public class PacketUtils {
}
public short length() {
// ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len)
return (short)
calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128);
return calculateEspPacketSize(
payload.length, cipher.ivLen, cipher.blockSize, auth.icvLen * 8);
}
public byte[] getPacketBytes(IpHeader header) throws Exception {
@@ -368,58 +402,12 @@ public class PacketUtils {
ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
espPayloadBuffer.putInt(spi);
espPayloadBuffer.putInt(seqNum);
espPayloadBuffer.put(getCiphertext(key));
espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16);
espPayloadBuffer.put(cipher.getCipherText(nextHeader, payload, spi, seqNum));
espPayloadBuffer.put(auth.getIcv(getByteArrayFromBuffer(espPayloadBuffer)));
resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer));
}
private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256);
SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
sha256HMAC.init(authKey);
return sha256HMAC.doFinal(authenticatedSection);
}
/**
* Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks
*
* <p>The ciphertext does NOT include the SPI/Sequence numbers, or the ICV.
*/
private byte[] getCiphertext(byte[] key) throws GeneralSecurityException {
int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE);
ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
paddedPayload.put(payload);
// Add padding - consecutive integers from 0x01
int pad = 1;
while (paddedPayload.position() < paddedPayload.limit()) {
paddedPayload.put((byte) pad++);
}
paddedPayload.position(paddedPayload.limit() - 2);
paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length
paddedPayload.put((byte) nextHeader);
// Generate Initialization Vector
byte[] iv = new byte[AES_CBC_IV_LEN];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
// Encrypt payload
Cipher cipher = Cipher.getInstance(AES_CBC);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload));
// Build ciphertext
ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length);
cipherText.put(iv);
cipherText.put(encrypted);
return getByteArrayFromBuffer(cipherText);
}
}
private static int addAndWrapForChecksum(int currentChecksum, int value) {
@@ -436,7 +424,7 @@ public class PacketUtils {
return (short) ((~val) & 0xffff);
}
public static int calculateEspPacketSize(
public static short calculateEspPacketSize(
int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
@@ -444,7 +432,7 @@ public class PacketUtils {
// Align to block size of encryption algorithm
payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize);
return payloadLen + ESP_HDRLEN + ICV_LEN;
return (short) (payloadLen + ESP_HDRLEN + ICV_LEN);
}
private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) {
@@ -475,6 +463,144 @@ public class PacketUtils {
}
}
public abstract static class EspCipher {
public final String algoName;
public final int blockSize;
public final byte[] key;
public final int ivLen;
protected byte[] iv;
public EspCipher(String algoName, int blockSize, byte[] key, int ivLen) {
this.algoName = algoName;
this.blockSize = blockSize;
this.key = key;
this.ivLen = ivLen;
this.iv = getIv(ivLen);
}
public static byte[] getPaddedPayload(int nextHeader, byte[] payload, int blockSize) {
final int paddedLen = calculateEspEncryptedLength(payload.length, blockSize);
final ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
paddedPayload.put(payload);
// Add padding - consecutive integers from 0x01
byte pad = 1;
while (paddedPayload.position() < paddedPayload.limit() - ESP_TRAILER_LEN) {
paddedPayload.put((byte) pad++);
}
// Add padding length and next header
paddedPayload.put((byte) (paddedLen - ESP_TRAILER_LEN - payload.length));
paddedPayload.put((byte) nextHeader);
return getByteArrayFromBuffer(paddedPayload);
}
private static byte[] getIv(int ivLen) {
final byte[] iv = new byte[ivLen];
new SecureRandom().nextBytes(iv);
return iv;
}
public abstract byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
throws GeneralSecurityException;
}
public static class EspCipherNull extends EspCipher {
private static final String CRYPT_NULL = "CRYPT_NULL";
private static final int IV_LEN_UNUSED = 0;
private static final byte[] KEY_UNUSED = new byte[0];
private static final EspCipherNull INSTANCE = new EspCipherNull();
private EspCipherNull() {
super(CRYPT_NULL, ESP_BLK_SIZE, KEY_UNUSED, IV_LEN_UNUSED);
}
public static EspCipherNull getInstance() {
return INSTANCE;
}
@Override
public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
throws GeneralSecurityException {
return getPaddedPayload(nextHeader, payload, blockSize);
}
}
public static class EspCryptCipher extends EspCipher {
public EspCryptCipher(String algoName, int blockSize, byte[] key, int ivLen) {
super(algoName, blockSize, key, ivLen);
}
@Override
public byte[] getCipherText(int nextHeader, byte[] payload, int spi, int seqNum)
throws GeneralSecurityException {
final IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
final SecretKeySpec secretKeySpec = new SecretKeySpec(key, algoName);
// Encrypt payload
final Cipher cipher = Cipher.getInstance(algoName);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
final byte[] encrypted =
cipher.doFinal(getPaddedPayload(nextHeader, payload, blockSize));
// Build ciphertext
final ByteBuffer cipherText = ByteBuffer.allocate(iv.length + encrypted.length);
cipherText.put(iv);
cipherText.put(encrypted);
return getByteArrayFromBuffer(cipherText);
}
}
// TODO: Implement EspAeadCipher in the following CL
public static class EspAuth {
public final String algoName;
public final byte[] key;
public final int icvLen;
public EspAuth(String algoName, byte[] key, int icvLen) {
this.algoName = algoName;
this.key = key;
this.icvLen = icvLen;
}
public byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
final Mac mac = Mac.getInstance(algoName);
final SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
mac.init(authKey);
final ByteBuffer buffer = ByteBuffer.wrap(mac.doFinal(authenticatedSection));
final byte[] icv = new byte[icvLen];
buffer.get(icv);
return icv;
}
}
public static class EspAuthNull extends EspAuth {
private static final String AUTH_NULL = "AUTH_NULL";
private static final int ICV_LEN_UNUSED = 0;
private static final byte[] KEY_UNUSED = new byte[0];
private static final byte[] ICV_EMPTY = new byte[0];
private static final EspAuthNull INSTANCE = new EspAuthNull();
private EspAuthNull() {
super(AUTH_NULL, KEY_UNUSED, ICV_LEN_UNUSED);
}
public static EspAuthNull getInstance() {
return INSTANCE;
}
@Override
public byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
return ICV_EMPTY;
}
}
/*
* Debug printing
*/