diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
commit | db74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch) | |
tree | b39c3e794945072d38b97c963c5a6f10753514e1 /src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBody.java | |
parent | 7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff) | |
parent | eb4c77d7228f956f928c7d3500a220339ee78388 (diff) | |
download | ike-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.java | 390 |
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(); + } +} |