aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
commitdb74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch)
treeb39c3e794945072d38b97c963c5a6f10753514e1 /src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java
parent7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff)
parenteb4c77d7228f956f928c7d3500a220339ee78388 (diff)
downloadike-db74d7937f3a9503f0e2590ec24c3bcfbf259ce0.tar.gz
Snap for 6001391 from eb4c77d7228f956f928c7d3500a220339ee78388 to qt-aml-tzdata-release
Change-Id: Ic6fc3b152696e0096d00036f460262a8b0319394
Diffstat (limited to 'src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java')
-rw-r--r--src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java
new file mode 100644
index 00000000..77cc9f4e
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java
@@ -0,0 +1,390 @@
+/*
+ * 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.internal.net.ipsec.ike.message;
+
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeCombinedModeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * 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.
+ *
+ * <p>When using normal cipher with separate integrity algorithm, data to authenticate includes
+ * bytes from beginning of IKE header to the pad length, which are concatenation of IKE header,
+ * current payload header, iv and encrypted and padded data.
+ *
+ * <p>When using AEAD, additional authentication data(also known as) associated data is required. It
+ * MUST include bytes from beginning of IKE header to the last octet of the Payload Header of the
+ * Encrypted Payload. Note fragment number and total fragments are also included if Encrypted
+ * Payload is SKF.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#page-105">RFC 7296, Internet Key Exchange
+ * Protocol Version 2 (IKEv2)</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7383#page-6">RFC 7383, Internet Key Exchange
+ * Protocol Version 2 (IKEv2) Message Fragmentation</a>
+ */
+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,
+ int encryptedBodyOffset,
+ IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ byte[] integrityKey,
+ byte[] decryptionKey)
+ throws IkeProtocolException, GeneralSecurityException {
+ ByteBuffer inputBuffer = ByteBuffer.wrap(message);
+
+ // Skip IKE header and generic payload header (and SKF header)
+ inputBuffer.get(new byte[encryptedBodyOffset]);
+
+ // Extract bytes for authentication and decryption.
+ int expectedIvLen = decryptCipher.getIvLen();
+ mIv = new byte[expectedIvLen];
+
+ int checksumLen = getChecksum(integrityMac, decryptCipher);
+ int encryptedDataLen = message.length - (encryptedBodyOffset + expectedIvLen + checksumLen);
+ // IkeMessage will catch exception if encryptedDataLen is negative.
+ mEncryptedAndPaddedData = new byte[encryptedDataLen];
+
+ mIntegrityChecksum = new byte[checksumLen];
+ inputBuffer.get(mIv).get(mEncryptedAndPaddedData).get(mIntegrityChecksum);
+
+ if (decryptCipher.isAead()) {
+ byte[] dataToAuthenticate = Arrays.copyOfRange(message, 0, encryptedBodyOffset);
+ mUnencryptedData =
+ combinedModeDecrypt(
+ (IkeCombinedModeCipher) decryptCipher,
+ mEncryptedAndPaddedData,
+ mIntegrityChecksum,
+ dataToAuthenticate,
+ decryptionKey,
+ mIv);
+ } else {
+ byte[] dataToAuthenticate =
+ Arrays.copyOfRange(message, 0, message.length - checksumLen);
+
+ validateInboundChecksumOrThrow(
+ dataToAuthenticate, integrityMac, integrityKey, mIntegrityChecksum);
+ mUnencryptedData =
+ normalModeDecrypt(
+ mEncryptedAndPaddedData,
+ (IkeNormalModeCipher) decryptCipher,
+ decryptionKey,
+ mIv);
+ }
+ }
+
+ /**
+ * Package private constructor for constructing an instance of IkeEncryptedPayloadBody for
+ * building an outbound packet.
+ */
+ IkeEncryptedPayloadBody(
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
+ byte[] skfHeaderBytes,
+ byte[] unencryptedPayloads,
+ IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ byte[] integrityKey,
+ byte[] encryptionKey) {
+ this(
+ ikeHeader,
+ firstPayloadType,
+ skfHeaderBytes,
+ unencryptedPayloads,
+ integrityMac,
+ encryptCipher,
+ integrityKey,
+ encryptionKey,
+ encryptCipher.generateIv(),
+ calculatePadding(unencryptedPayloads.length, encryptCipher.getBlockSize()));
+ }
+
+ /** Package private constructor only for testing. */
+ @VisibleForTesting
+ IkeEncryptedPayloadBody(
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
+ byte[] skfHeaderBytes,
+ byte[] unencryptedPayloads,
+ IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ byte[] integrityKey,
+ byte[] encryptionKey,
+ byte[] iv,
+ byte[] padding) {
+ mUnencryptedData = unencryptedPayloads;
+
+ mIv = iv;
+ if (encryptCipher.isAead()) {
+ byte[] paddedDataWithChecksum =
+ combinedModeEncrypt(
+ (IkeCombinedModeCipher) encryptCipher,
+ ikeHeader,
+ firstPayloadType,
+ skfHeaderBytes,
+ unencryptedPayloads,
+ encryptionKey,
+ iv,
+ padding);
+
+ int checkSumLen = ((IkeCombinedModeCipher) encryptCipher).getChecksumLen();
+ mIntegrityChecksum = new byte[checkSumLen];
+ mEncryptedAndPaddedData = new byte[paddedDataWithChecksum.length - checkSumLen];
+
+ ByteBuffer buffer = ByteBuffer.wrap(paddedDataWithChecksum);
+ buffer.get(mEncryptedAndPaddedData);
+ buffer.get(mIntegrityChecksum);
+ } else {
+ // Encrypt data
+ mEncryptedAndPaddedData =
+ normalModeEncrypt(
+ unencryptedPayloads,
+ (IkeNormalModeCipher) encryptCipher,
+ encryptionKey,
+ iv,
+ padding);
+ // Calculate checksum
+ mIntegrityChecksum =
+ generateOutboundChecksum(
+ ikeHeader,
+ firstPayloadType,
+ skfHeaderBytes,
+ integrityMac,
+ iv,
+ mEncryptedAndPaddedData,
+ integrityKey);
+ }
+ }
+
+ private int getChecksum(IkeMacIntegrity integrityMac, IkeCipher decryptCipher) {
+ if (decryptCipher.isAead()) {
+ return ((IkeCombinedModeCipher) decryptCipher).getChecksumLen();
+ } else {
+ return integrityMac.getChecksumLen();
+ }
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] generateOutboundChecksum(
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
+ byte[] skfHeaderBytes,
+ IkeMacIntegrity integrityMac,
+ byte[] iv,
+ byte[] encryptedAndPaddedData,
+ byte[] integrityKey) {
+ // Length from encrypted payload header to the Pad Length field
+ int encryptedPayloadHeaderToPadLen =
+ IkePayload.GENERIC_HEADER_LENGTH
+ + skfHeaderBytes.length
+ + iv.length
+ + encryptedAndPaddedData.length;
+
+ // Calculate length of authentication data and allocate ByteBuffer.
+ int dataToAuthenticateLength = IkeHeader.IKE_HEADER_LENGTH + encryptedPayloadHeaderToPadLen;
+ ByteBuffer authenticatedSectionBuffer = ByteBuffer.allocate(dataToAuthenticateLength);
+
+ // Build data to authenticate.
+ int encryptedPayloadLength = encryptedPayloadHeaderToPadLen + integrityMac.getChecksumLen();
+ ikeHeader.encodeToByteBuffer(authenticatedSectionBuffer, encryptedPayloadLength);
+ IkePayload.encodePayloadHeaderToByteBuffer(
+ firstPayloadType, encryptedPayloadLength, authenticatedSectionBuffer);
+ authenticatedSectionBuffer.put(skfHeaderBytes).put(iv).put(encryptedAndPaddedData);
+
+ // Calculate checksum
+ return integrityMac.generateChecksum(integrityKey, authenticatedSectionBuffer.array());
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static void validateInboundChecksumOrThrow(
+ byte[] dataToAuthenticate,
+ IkeMacIntegrity integrityMac,
+ byte[] integrityKey,
+ byte[] integrityChecksum)
+ throws GeneralSecurityException {
+ // TODO: Make it package private and add test.
+ int checkSumLen = integrityChecksum.length;
+ byte[] calculatedChecksum = integrityMac.generateChecksum(integrityKey, dataToAuthenticate);
+
+ if (!Arrays.equals(integrityChecksum, calculatedChecksum)) {
+ throw new GeneralSecurityException("Message authentication failed.");
+ }
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] normalModeEncrypt(
+ byte[] dataToEncrypt,
+ IkeNormalModeCipher encryptCipher,
+ byte[] encryptionKey,
+ byte[] iv,
+ byte[] padding) {
+ byte[] paddedData = getPaddedData(dataToEncrypt, padding);
+
+ // Encrypt data.
+ return encryptCipher.encrypt(paddedData, encryptionKey, iv);
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] normalModeDecrypt(
+ byte[] encryptedData,
+ IkeNormalModeCipher decryptCipher,
+ byte[] decryptionKey,
+ byte[] iv)
+ throws IllegalBlockSizeException {
+ byte[] paddedPlaintext = decryptCipher.decrypt(encryptedData, decryptionKey, iv);
+
+ return stripPadding(paddedPlaintext);
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] combinedModeEncrypt(
+ IkeCombinedModeCipher encryptCipher,
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
+ byte[] skfHeaderBytes,
+ byte[] dataToEncrypt,
+ byte[] encryptionKey,
+ byte[] iv,
+ byte[] padding) {
+ int dataToAuthenticateLength =
+ IkeHeader.IKE_HEADER_LENGTH
+ + IkePayload.GENERIC_HEADER_LENGTH
+ + skfHeaderBytes.length;
+ ByteBuffer authenticatedSectionBuffer = ByteBuffer.allocate(dataToAuthenticateLength);
+
+ byte[] paddedData = getPaddedData(dataToEncrypt, padding);
+ int encryptedPayloadLength =
+ IkePayload.GENERIC_HEADER_LENGTH
+ + skfHeaderBytes.length
+ + iv.length
+ + paddedData.length
+ + encryptCipher.getChecksumLen();
+ ikeHeader.encodeToByteBuffer(authenticatedSectionBuffer, encryptedPayloadLength);
+ IkePayload.encodePayloadHeaderToByteBuffer(
+ firstPayloadType, encryptedPayloadLength, authenticatedSectionBuffer);
+ authenticatedSectionBuffer.put(skfHeaderBytes);
+
+ return encryptCipher.encrypt(
+ paddedData, authenticatedSectionBuffer.array(), encryptionKey, iv);
+ }
+
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] combinedModeDecrypt(
+ IkeCombinedModeCipher decryptCipher,
+ byte[] encryptedData,
+ byte[] checksum,
+ byte[] dataToAuthenticate,
+ byte[] decryptionKey,
+ byte[] iv)
+ throws AEADBadTagException {
+ ByteBuffer dataWithChecksumBuffer =
+ ByteBuffer.allocate(encryptedData.length + checksum.length);
+ dataWithChecksumBuffer.put(encryptedData);
+ dataWithChecksumBuffer.put(checksum);
+ dataWithChecksumBuffer.rewind();
+
+ byte[] paddedPlaintext =
+ decryptCipher.decrypt(
+ dataWithChecksumBuffer.array(), dataToAuthenticate, decryptionKey, iv);
+
+ return stripPadding(paddedPlaintext);
+ }
+
+ /** 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;
+ }
+
+ private static byte[] getPaddedData(byte[] data, byte[] padding) {
+ int padLength = padding.length;
+ int paddedDataLength = data.length + padLength + PAD_LEN_LEN;
+ ByteBuffer padBuffer = ByteBuffer.allocate(paddedDataLength);
+ padBuffer.put(data).put(padding).put((byte) padLength);
+
+ return padBuffer.array();
+ }
+
+ private static byte[] stripPadding(byte[] paddedPlaintext) {
+ // Remove padding. Pad length value is the last byte of the padded unencrypted data.
+ int padLength = Byte.toUnsignedInt(paddedPlaintext[paddedPlaintext.length - 1]);
+ int decryptedDataLen = paddedPlaintext.length - padLength - PAD_LEN_LEN;
+
+ return Arrays.copyOfRange(paddedPlaintext, 0, decryptedDataLen);
+ }
+
+ /** 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();
+ }
+}