diff options
author | Yan Yan <evitayan@google.com> | 2019-02-11 18:09:03 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-02-11 18:09:03 +0000 |
commit | a82e2910b79091e539328ccb7f296e0b9981aaf1 (patch) | |
tree | 68ad1a0783647f53509293690f56d884600844ef | |
parent | fb7c210d1e75c8029edf32aeccdb432b3222fb69 (diff) | |
parent | ca7997888c7142211bd2e41f77a1f25d4e8f4f0b (diff) | |
download | ike-a82e2910b79091e539328ccb7f296e0b9981aaf1.tar.gz |
Merge "Create IkeEncryptedPayloadBody"
5 files changed, 191 insertions, 84 deletions
diff --git a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java new file mode 100644 index 00000000..5bfed5d4 --- /dev/null +++ b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java @@ -0,0 +1,141 @@ +/* + * 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 com.android.ike.ikev2.exceptions.IkeException; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +/** + * IkeEncryptedPayloadBody is a package private class that represents an IKE payload substructure + * that contains initialization vector, encrypted content, padding, pad length and integrity + * checksum. + * + * <p>Both an Encrypted Payload (IkeSkPayload) and an EncryptedFragmentPayload (IkeSkfPayload) + * consists of an IkeEncryptedPayloadBody instance. + * + * @see <a href="https://tools.ietf.org/html/rfc7296#page-105">RFC 7296, Internet Key Exchange + * Protocol Version 2 (IKEv2). + * @see <a href="https://tools.ietf.org/html/rfc7383#page-6">RFC 7383, Internet Key Exchange + * Protocol Version 2 (IKEv2) Message Fragmentation + */ +final class IkeEncryptedPayloadBody { + // Length of pad length field. + private static final int PAD_LEN_LEN = 1; + + private final byte[] mUnencryptedData; + private final byte[] mEncryptedAndPaddedData; + private final byte[] mIv; + private final byte[] mIntegrityChecksum; + + /** + * Package private constructor for constructing an instance of IkeEncryptedPayloadBody from + * decrypting an incoming packet. + */ + IkeEncryptedPayloadBody( + byte[] message, + Mac integrityMac, + int expectedChecksumLen, + Cipher decryptCipher, + SecretKey dKey) + throws IkeException, GeneralSecurityException { + ByteBuffer inputBuffer = ByteBuffer.wrap(message); + + // Skip IKE header and SK payload header + byte[] tempArray = new byte[IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH]; + inputBuffer.get(tempArray); + + // Extract bytes for authentication and decryption. + int expectedIvLen = decryptCipher.getBlockSize(); + mIv = new byte[expectedIvLen]; + + int encryptedDataLen = + message.length + - (IkeHeader.IKE_HEADER_LENGTH + + IkePayload.GENERIC_HEADER_LENGTH + + expectedIvLen + + expectedChecksumLen); + // IkeMessage will catch exception if encryptedDataLen is negative. + mEncryptedAndPaddedData = new byte[encryptedDataLen]; + + mIntegrityChecksum = new byte[expectedChecksumLen]; + inputBuffer.get(mIv).get(mEncryptedAndPaddedData).get(mIntegrityChecksum); + + // Authenticate and decrypt. + validateChecksumOrThrow(message, integrityMac, expectedChecksumLen, mIntegrityChecksum); + mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, dKey, mIv); + } + + // 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); + integrityMac.update(inputBuffer); + byte[] calculatedChecksum = + Arrays.copyOfRange(integrityMac.doFinal(), 0, expectedChecksumLen); + + if (!Arrays.equals(integrityChecksum, calculatedChecksum)) { + throw new GeneralSecurityException("Message authentication failed."); + } + } + + private static byte[] decrypt( + byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv) + throws GeneralSecurityException { + decryptCipher.init(Cipher.DECRYPT_MODE, dKey, new IvParameterSpec(iv)); + + ByteBuffer inputBuffer = ByteBuffer.wrap(encryptedData); + ByteBuffer outputBuffer = ByteBuffer.allocate(encryptedData.length); + decryptCipher.doFinal(inputBuffer, outputBuffer); + + // Remove padding + outputBuffer.rewind(); + int padLength = Byte.toUnsignedInt(outputBuffer.get(encryptedData.length - PAD_LEN_LEN)); + byte[] decryptedData = new byte[encryptedData.length - padLength - PAD_LEN_LEN]; + + outputBuffer.get(decryptedData); + return decryptedData; + } + + /** Package private */ + byte[] getUnencryptedData() { + return mUnencryptedData; + } + + /** Package private */ + int getLength() { + return (mIv.length + mEncryptedAndPaddedData.length + mIntegrityChecksum.length); + } + + /** Package private */ + byte[] encode() { + ByteBuffer buffer = ByteBuffer.allocate(getLength()); + buffer.put(mIv).put(mEncryptedAndPaddedData).put(mIntegrityChecksum); + return buffer.array(); + } +} diff --git a/src/java/com/android/ike/ikev2/message/IkeMessage.java b/src/java/com/android/ike/ikev2/message/IkeMessage.java index cb19317c..b2185bfe 100644 --- a/src/java/com/android/ike/ikev2/message/IkeMessage.java +++ b/src/java/com/android/ike/ikev2/message/IkeMessage.java @@ -347,6 +347,7 @@ public final class IkeMessage { IkeHeader ikeHeader, byte[] packet) throws IkeException, GeneralSecurityException { + //TODO: Extract crypto params and call private decode method. return null; } @@ -356,8 +357,7 @@ public final class IkeMessage { Mac integrityMac, int checksumLen, Cipher decryptCipher, - SecretKey dKey, - int ivLen) + SecretKey dKey) throws IkeException, GeneralSecurityException { header.checkValidOrThrow(inputPacket.length); @@ -370,12 +370,12 @@ public final class IkeMessage { try { Pair<IkeSkPayload, Integer> pair = IkePayloadFactory.getIkeSkPayload( - inputPacket, integrityMac, checksumLen, decryptCipher, dKey, ivLen); + inputPacket, integrityMac, checksumLen, decryptCipher, dKey); IkeSkPayload skPayload = pair.first; int firstPayloadType = pair.second; List<IkePayload> supportedPayloadList = - decodePayloadList(firstPayloadType, skPayload.unencryptedPayloads); + decodePayloadList(firstPayloadType, skPayload.getUnencryptedPayloads()); return new IkeMessage(header, supportedPayloadList); } catch (NegativeArraySizeException | BufferUnderflowException e) { diff --git a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java index 409d25bb..a091618d 100644 --- a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java +++ b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java @@ -121,7 +121,6 @@ final class IkePayloadFactory { * @param expectedChecksumLen the expected length of integrity checksum. * @param decryptCipher the uninitialized Cipher for doing decryption. * @param dKey the decryption key. - * @param expectedIvLen the expected length of Initialization Vector. * @return a pair including IkePayload and next payload type. * @throws IkeException for decoding errors. * @throws GeneralSecurityException if there is any error during integrity check or decryption. @@ -131,8 +130,7 @@ final class IkePayloadFactory { Mac integrityMac, int expectedChecksumLen, Cipher decryptCipher, - SecretKey dKey, - int expectedIvLen) + SecretKey dKey) throws IkeException, GeneralSecurityException { ByteBuffer input = ByteBuffer.wrap( @@ -168,8 +166,7 @@ final class IkePayloadFactory { integrityMac, expectedChecksumLen, decryptCipher, - dKey, - expectedIvLen); + dKey); return new Pair(payload, nextPayloadType); } diff --git a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java index b108580b..f2395157 100644 --- a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java +++ b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java @@ -21,12 +21,10 @@ import com.android.ike.ikev2.message.IkePayload.PayloadType; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; -import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; /** * IkeSkPayload represents a Encrypted Payload. @@ -41,10 +39,10 @@ import javax.crypto.spec.IvParameterSpec; */ public final class IkeSkPayload extends IkePayload { - public final byte[] unencryptedPayloads; + private final IkeEncryptedPayloadBody mIkeEncryptedPayloadBody; /** - * Construct an instance of IkeSkPayload in the context of {@link IkePayloadFactory}. + * Construct an instance of IkeSkPayload from decrypting an incoming packet. * * @param critical indicates if it is a critical payload. * @param message the byte array contains the whole IKE message. @@ -52,7 +50,6 @@ public final class IkeSkPayload extends IkePayload { * @param expectedChecksumLen the expected length of integrity checksum. * @param decryptCipher the uninitialized Cipher for doing decryption. * @param dKey the decryption key. - * @param expectedIvLen the expected length of Initialization Vector. */ IkeSkPayload( boolean critical, @@ -60,80 +57,36 @@ public final class IkeSkPayload extends IkePayload { Mac integrityMac, int expectedChecksumLen, Cipher decryptCipher, - SecretKey dKey, - int expectedIvLen) + SecretKey dKey) throws IkeException, GeneralSecurityException { super(PAYLOAD_TYPE_SK, critical); - ByteBuffer inputBuffer = ByteBuffer.wrap(message); - - // Skip IKE header and SK payload header - byte[] tempArray = new byte[IkeHeader.IKE_HEADER_LENGTH + GENERIC_HEADER_LENGTH]; - inputBuffer.get(tempArray); - - // Extract bytes for authentication and decryption. - byte[] iv = new byte[expectedIvLen]; - - int encryptedDataLen = - message.length - - (IkeHeader.IKE_HEADER_LENGTH - + GENERIC_HEADER_LENGTH - + expectedIvLen - + expectedChecksumLen); - // IkeMessage will catch exception if encryptedDataLen is negative. - byte[] encryptedData = new byte[encryptedDataLen]; - - byte[] integrityChecksum = new byte[expectedChecksumLen]; - inputBuffer.get(iv).get(encryptedData).get(integrityChecksum); - - // Authenticate and decrypt. - validateChecksumOrThrow(message, integrityMac, expectedChecksumLen, integrityChecksum); - unencryptedPayloads = decrypt(encryptedData, decryptCipher, dKey, iv); + mIkeEncryptedPayloadBody = + new IkeEncryptedPayloadBody( + message, integrityMac, expectedChecksumLen, decryptCipher, dKey); } - // TODO: Add another constructor for AEAD protected payload. - - private void validateChecksumOrThrow( - byte[] message, Mac integrityMac, int expectedChecksumLen, byte[] integrityChecksum) - throws GeneralSecurityException { - ByteBuffer inputBuffer = ByteBuffer.wrap(message, 0, message.length - expectedChecksumLen); - integrityMac.update(inputBuffer); - byte[] calculatedChecksum = - Arrays.copyOfRange(integrityMac.doFinal(), 0, expectedChecksumLen); - - if (!Arrays.equals(integrityChecksum, calculatedChecksum)) { - throw new GeneralSecurityException("Message authentication failed. "); - } + /** + * Return unencrypted payload list + * + * @return unencrypted payload list in a byte array. + */ + public byte[] getUnencryptedPayloads() { + return mIkeEncryptedPayloadBody.getUnencryptedData(); } - private byte[] decrypt(byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv) - throws GeneralSecurityException { - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - decryptCipher.init(Cipher.DECRYPT_MODE, dKey, ivParameterSpec); - - ByteBuffer inputBuffer = ByteBuffer.wrap(encryptedData); - ByteBuffer outputBuffer = ByteBuffer.allocate(encryptedData.length); - decryptCipher.doFinal(inputBuffer, outputBuffer); - - // Remove padding - outputBuffer.rewind(); - int padLength = Byte.toUnsignedInt(outputBuffer.get(encryptedData.length - 1)); - byte[] decryptedData = new byte[encryptedData.length - padLength - 1]; - - outputBuffer.get(decryptedData); - return decryptedData; - } + // TODO: Add another constructor for AEAD protected payload. /** - * Throw an Exception when trying to encode this payload. + * Encode this payload to a ByteBuffer. * - * @throws UnsupportedOperationException for this payload. + * @param nextPayload type of payload that follows this payload. + * @param byteBuffer destination ByteBuffer that stores encoded payload. */ @Override protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) { - // TODO: Implement thie method - throw new UnsupportedOperationException( - "It is not supported to encode a " + getTypeString()); + encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer); + byteBuffer.put(mIkeEncryptedPayloadBody.encode()); } /** @@ -143,9 +96,7 @@ public final class IkeSkPayload extends IkePayload { */ @Override protected int getPayloadLength() { - // TODO: Implement thie method - throw new UnsupportedOperationException( - "It is not supported to get length of a " + getTypeString()); + return GENERIC_HEADER_LENGTH + mIkeEncryptedPayloadBody.getLength(); } /** diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java index 3f97162e..aebd1194 100644 --- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java +++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java @@ -22,7 +22,9 @@ import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; +import java.nio.ByteBuffer; import java.security.GeneralSecurityException; +import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.Mac; @@ -59,7 +61,6 @@ public final class IkeSkPayloadTest { private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding"; private static final String INTE_ALGO_HMAC_SHA1 = "HmacSHA1"; - private static final int IV_LEN = 16; private static final int CHECKSUM_LEN = 12; private Cipher mAesCbcDecryptCipher; @@ -90,12 +91,11 @@ public final class IkeSkPayloadTest { mHmacSha1IntegrityMac, CHECKSUM_LEN, mAesCbcDecryptCipher, - mAesCbcDecryptKey, - IV_LEN) + mAesCbcDecryptKey) .first; byte[] expectedPlaintext = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_DECRYPTED_BODY_HEX_STRING); - assertArrayEquals(expectedPlaintext, payload.unencryptedPayloads); + assertArrayEquals(expectedPlaintext, payload.getUnencryptedPayloads()); } @Test @@ -109,10 +109,28 @@ public final class IkeSkPayloadTest { mHmacSha1IntegrityMac, CHECKSUM_LEN, mAesCbcDecryptCipher, - mAesCbcDecryptKey, - IV_LEN); + mAesCbcDecryptKey); fail("Expected GeneralSecurityException: Invalid checksum."); } catch (GeneralSecurityException expected) { } } + + @Test + public void testEncode() throws Exception { + byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING); + byte[] payloadBytes = + Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length); + IkeSkPayload payload = + IkePayloadFactory.getIkeSkPayload( + message, + mHmacSha1IntegrityMac, + CHECKSUM_LEN, + mAesCbcDecryptCipher, + mAesCbcDecryptKey) + .first; + int payloadLength = payload.getPayloadLength(); + ByteBuffer buffer = ByteBuffer.allocate(payloadLength); + payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, buffer); + assertArrayEquals(payloadBytes, buffer.array()); + } } |