diff options
-rw-r--r-- | src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java | 109 | ||||
-rw-r--r-- | tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java | 177 |
2 files changed, 281 insertions, 5 deletions
diff --git a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java index c4eb3045..52ca2a76 100644 --- a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java +++ b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java @@ -17,9 +17,11 @@ package com.android.ike.ikev2.message; import com.android.ike.ikev2.exceptions.IkeException; +import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; +import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Cipher; @@ -83,30 +85,113 @@ final class IkeEncryptedPayloadBody { inputBuffer.get(mIv).get(mEncryptedAndPaddedData).get(mIntegrityChecksum); // Authenticate and decrypt. - validateChecksumOrThrow(message, integrityMac, expectedChecksumLen, mIntegrityChecksum); + byte[] dataToAuthenticate = + Arrays.copyOfRange(message, 0, message.length - expectedChecksumLen); + validateChecksumOrThrow(dataToAuthenticate, integrityMac, mIntegrityChecksum); mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, dKey, mIv); } + /** + * Package private constructor for constructing an instance of IkeEncryptedPayloadBody for + * building an outbound packet. + */ + IkeEncryptedPayloadBody( + byte[] ikeAndPayloadHeader, + byte[] unencryptedPayloads, + Mac integrityMac, + int expectedChecksumLen, + Cipher encryptCipher, + SecretKey eKey) { + this( + ikeAndPayloadHeader, + unencryptedPayloads, + integrityMac, + expectedChecksumLen, + encryptCipher, + eKey, + encryptCipher.getIV(), + calculatePadding(unencryptedPayloads.length, encryptCipher.getBlockSize())); + } + + /** Package private constructor only for testing. */ + @VisibleForTesting + IkeEncryptedPayloadBody( + byte[] ikeAndPayloadHeader, + byte[] unencryptedPayloads, + Mac integrityMac, + int expectedChecksumLen, + Cipher encryptCipher, + SecretKey eKey, + byte[] iv, + byte[] padding) { + mUnencryptedData = unencryptedPayloads; + + // Encrypt data + mIv = iv; + mEncryptedAndPaddedData = encrypt(unencryptedPayloads, encryptCipher, eKey, iv, padding); + + // Calculate checksum + ByteBuffer inputBuffer = + ByteBuffer.allocate( + ikeAndPayloadHeader.length + iv.length + mEncryptedAndPaddedData.length); + inputBuffer.put(ikeAndPayloadHeader).put(iv).put(mEncryptedAndPaddedData); + mIntegrityChecksum = + calculateChecksum(inputBuffer.array(), integrityMac, expectedChecksumLen); + } + // TODO: Add another constructor for AEAD protected payload. // TODO: Add constructors that initiate IkeEncryptedPayloadBody for an outbound packet - private static void validateChecksumOrThrow( - byte[] message, Mac integrityMac, int expectedChecksumLen, byte[] integrityChecksum) - throws GeneralSecurityException { - ByteBuffer inputBuffer = ByteBuffer.wrap(message, 0, message.length - expectedChecksumLen); + /** Package private for testing */ + @VisibleForTesting + static byte[] calculateChecksum( + byte[] dataToAuthenticate, Mac integrityMac, int expectedChecksumLen) { + ByteBuffer inputBuffer = ByteBuffer.wrap(dataToAuthenticate); integrityMac.update(inputBuffer); byte[] calculatedChecksum = Arrays.copyOfRange(integrityMac.doFinal(), 0, expectedChecksumLen); + return calculatedChecksum; + } + + private static void validateChecksumOrThrow( + byte[] dataToAuthenticate, Mac integrityMac, byte[] integrityChecksum) + throws GeneralSecurityException { + // TODO: Make it package private and add test. + int checkSumLen = integrityChecksum.length; + byte[] calculatedChecksum = + calculateChecksum(dataToAuthenticate, integrityMac, checkSumLen); if (!Arrays.equals(integrityChecksum, calculatedChecksum)) { throw new GeneralSecurityException("Message authentication failed."); } } + /** Package private for testing */ + @VisibleForTesting + static byte[] encrypt( + byte[] dataToEncrypt, Cipher encryptCipher, SecretKey eKey, byte[] iv, byte[] padding) { + int padLength = padding.length; + int paddedDataLength = dataToEncrypt.length + padLength + PAD_LEN_LEN; + ByteBuffer inputBuffer = ByteBuffer.allocate(paddedDataLength); + inputBuffer.put(dataToEncrypt).put(padding).put((byte) padLength); + inputBuffer.rewind(); + + try { + // Encrypt data. + ByteBuffer outputBuffer = ByteBuffer.allocate(paddedDataLength); + encryptCipher.init(Cipher.ENCRYPT_MODE, eKey, new IvParameterSpec(iv)); + encryptCipher.doFinal(inputBuffer, outputBuffer); + return outputBuffer.array(); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException("Fail to encrypt IKE message. ", e); + } + } + private static byte[] decrypt( byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv) throws GeneralSecurityException { + // TODO: Make it package private and add test. decryptCipher.init(Cipher.DECRYPT_MODE, dKey, new IvParameterSpec(iv)); ByteBuffer inputBuffer = ByteBuffer.wrap(encryptedData); @@ -122,6 +207,20 @@ final class IkeEncryptedPayloadBody { return decryptedData; } + /** Package private for testing */ + @VisibleForTesting + static byte[] calculatePadding(int dataToEncryptLength, int blockSize) { + // Sum of dataToEncryptLength, PAD_LEN_LEN and padLength should be aligned with block size. + int unpaddedLen = dataToEncryptLength + PAD_LEN_LEN; + int padLength = (unpaddedLen + blockSize - 1) / blockSize * blockSize - unpaddedLen; + byte[] padding = new byte[padLength]; + + // According to RFC 7296, "Padding MAY contain any value". + new SecureRandom().nextBytes(padding); + + return padding; + } + /** Package private */ byte[] getUnencryptedData() { return mUnencryptedData; diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java new file mode 100644 index 00000000..48669e89 --- /dev/null +++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ike.ikev2.message; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +public final class IkeEncryptedPayloadBodyTest { + + private static final String IKE_AUTH_INIT_REQUEST_HEADER = + "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec"; + private static final String IKE_AUTH_INIT_REQUEST_SK_HEADER = "230000d0"; + private static final String IKE_AUTH_INIT_REQUEST_IV = "b9132b7bb9f658dfdc648e5017a6322a"; + private static final String IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA = + "030c316ce55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58" + + "46de333ecd3ea2b705d18293b130395300ba92a351041345" + + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f" + + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60" + + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b" + + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423" + + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3" + + "59eb4e81"; + private static final String IKE_AUTH_INIT_REQUEST_CHECKSUM = "ae6e0f22abdad69ba8007d50"; + + private static final String IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA = + "2400000c010000000a50500d2700000c010000000a505050" + + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327" + + "44dfb2c12c00002c00000028010304032ad4c0a20300000c" + + "0100000c800e008003000008030000020000000805000000" + + "2d00001801000000070000100000ffff00000000ffffffff" + + "2900001801000000070000100000ffff00000000ffffffff" + + "29000008000040000000000c0000400100000001"; + private static final String IKE_AUTH_INIT_REQUEST_PADDING = "0000000000000000000000"; + private static final int HMAC_SHA1_CHECKSUM_LEN = 12; + + private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1"; + private static final String INTE_KEY_FROM_INIT_TO_RESP = + "554fbf5a05b7f511e05a30ce23d874db9ef55e51"; + + private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding"; + private static final String INTE_ALGO_HMAC_SHA1 = "HmacSHA1"; + + private Cipher mAesCbcCipher; + private SecretKey mAesCbcKey; + private Mac mHmacSha1IntegrityMac; + + // TODO: Add tests for authenticating and decrypting received message. + @Before + public void setUp() throws Exception { + mAesCbcCipher = Cipher.getInstance(ENCR_ALGO_AES_CBC, IkeMessage.getSecurityProvider()); + byte[] encryptKeyBytes = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP); + mAesCbcKey = new SecretKeySpec(encryptKeyBytes, ENCR_ALGO_AES_CBC); + + mHmacSha1IntegrityMac = + Mac.getInstance(INTE_ALGO_HMAC_SHA1, IkeMessage.getSecurityProvider()); + byte[] integrityKeyBytes = TestUtils.hexStringToByteArray(INTE_KEY_FROM_INIT_TO_RESP); + SecretKeySpec integrityKey = new SecretKeySpec(integrityKeyBytes, INTE_ALGO_HMAC_SHA1); + mHmacSha1IntegrityMac.init(integrityKey); + } + + @Test + public void testCalculateChecksum() throws Exception { + String hexStringToAuthenticate = + IKE_AUTH_INIT_REQUEST_HEADER + + IKE_AUTH_INIT_REQUEST_SK_HEADER + + IKE_AUTH_INIT_REQUEST_IV + + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA; + byte[] byteToAuthenticate = TestUtils.hexStringToByteArray(hexStringToAuthenticate); + + byte[] expectedCheckSum = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_CHECKSUM); + + byte[] calculatedChecksum = + IkeEncryptedPayloadBody.calculateChecksum( + byteToAuthenticate, mHmacSha1IntegrityMac, HMAC_SHA1_CHECKSUM_LEN); + + assertArrayEquals(expectedCheckSum, calculatedChecksum); + } + + @Test + public void testCalculatePaddingPlaintextShorterThanBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 15; + int expectedPadLength = 0; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testCalculatePaddingPlaintextInBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 16; + int expectedPadLength = 15; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testCalculatePaddingPlaintextLongerThanBlockSize() throws Exception { + int blockSize = 16; + int plainTextLength = 17; + int expectedPadLength = 14; + + byte[] calculatedPadding = + IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize); + assertEquals(expectedPadLength, calculatedPadding.length); + } + + @Test + public void testEncrypt() throws Exception { + byte[] dataToEncrypt = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA); + byte[] iv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV); + byte[] padding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING); + + byte[] calculatedData = + IkeEncryptedPayloadBody.encrypt( + dataToEncrypt, mAesCbcCipher, mAesCbcKey, iv, padding); + byte[] expectedData = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA); + + assertArrayEquals(expectedData, calculatedData); + } + + @Test + public void testBuildAndEncodeOutboundIkeEncryptedPayloadBody() throws Exception { + byte[] ikeAndPayloadHeader = + TestUtils.hexStringToByteArray( + IKE_AUTH_INIT_REQUEST_HEADER + IKE_AUTH_INIT_REQUEST_SK_HEADER); + byte[] unencryptedPayloads = + TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA); + byte[] iv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV); + byte[] padding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING); + + IkeEncryptedPayloadBody paylaodBody = + new IkeEncryptedPayloadBody( + ikeAndPayloadHeader, + unencryptedPayloads, + mHmacSha1IntegrityMac, + HMAC_SHA1_CHECKSUM_LEN, + mAesCbcCipher, + mAesCbcKey, + iv, + padding); + + byte[] expectedEncodedData = + TestUtils.hexStringToByteArray( + IKE_AUTH_INIT_REQUEST_IV + + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA + + IKE_AUTH_INIT_REQUEST_CHECKSUM); + assertArrayEquals(expectedEncodedData, paylaodBody.encode()); + } +} |