aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java')
-rw-r--r--src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java b/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java
new file mode 100644
index 00000000..4bb1d34b
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipher.java
@@ -0,0 +1,209 @@
+/*
+ * 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.crypto;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.SaProposal;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * IkeCipher represents a negotiated combined-mode cipher(AEAD) encryption algorithm.
+ *
+ * <p>Checksum mentioned in this class is also known as authentication tag or Integrity Checksum
+ * Vector(ICV)
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key Exchange
+ * Protocol Version 2 (IKEv2)</a>
+ * @see <a href="https://tools.ietf.org/html/rfc5282">RFC 5282,Using Authenticated Encryption
+ * Algorithms with the Encrypted Payload of the Internet Key Exchange version 2 (IKEv2)
+ * Protocol</a>
+ */
+public final class IkeCombinedModeCipher extends IkeCipher {
+ private static final int SALT_LEN_GCM = 4;
+
+ private final int mChecksumLen;
+ private final int mSaltLen;
+
+ /** Package private */
+ IkeCombinedModeCipher(
+ int algorithmId, int keyLength, int ivLength, String algorithmName, Provider provider) {
+ super(algorithmId, keyLength, ivLength, algorithmName, true /*isAead*/, provider);
+ switch (algorithmId) {
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8:
+ mSaltLen = SALT_LEN_GCM;
+ mChecksumLen = 8;
+ break;
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
+ mSaltLen = SALT_LEN_GCM;
+ mChecksumLen = 12;
+ break;
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
+ mSaltLen = SALT_LEN_GCM;
+ mChecksumLen = 16;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unrecognized Encryption Algorithm ID: " + algorithmId);
+ }
+ }
+
+ private byte[] doCipherAction(
+ byte[] data, byte[] additionalAuthData, byte[] keyBytes, byte[] ivBytes, int opmode)
+ throws AEADBadTagException {
+ try {
+ // Provided key consists of encryption/decryption key plus 4-byte salt. Salt is used
+ // with IV to build the nonce.
+ ByteBuffer secretKeyAndSaltBuffer = ByteBuffer.wrap(keyBytes);
+ byte[] secretKeyBytes = new byte[keyBytes.length - mSaltLen];
+ byte[] salt = new byte[mSaltLen];
+ secretKeyAndSaltBuffer.get(secretKeyBytes);
+ secretKeyAndSaltBuffer.get(salt);
+
+ SecretKeySpec key = new SecretKeySpec(secretKeyBytes, getAlgorithmName());
+
+ ByteBuffer nonceBuffer = ByteBuffer.allocate(mSaltLen + ivBytes.length);
+ nonceBuffer.put(salt);
+ nonceBuffer.put(ivBytes);
+
+ mCipher.init(opmode, key, getParamSpec(nonceBuffer.array()));
+ mCipher.updateAAD(additionalAuthData);
+
+ ByteBuffer inputBuffer = ByteBuffer.wrap(data);
+
+ int outputLen = data.length;
+ if (opmode == Cipher.ENCRYPT_MODE) outputLen += mChecksumLen;
+ ByteBuffer outputBuffer = ByteBuffer.allocate(outputLen);
+
+ mCipher.doFinal(inputBuffer, outputBuffer);
+ return outputBuffer.array();
+ } catch (AEADBadTagException e) {
+ // Checksum failed in decryption
+ throw (AEADBadTagException) e;
+ } catch (InvalidKeyException
+ | InvalidAlgorithmParameterException
+ | IllegalBlockSizeException
+ | BadPaddingException
+ | ShortBufferException e) {
+ String errorMessage =
+ Cipher.ENCRYPT_MODE == opmode
+ ? "Failed to encrypt data: "
+ : "Failed to decrypt data: ";
+ throw new IllegalArgumentException(errorMessage, e);
+ }
+ }
+
+ private AlgorithmParameterSpec getParamSpec(byte[] nonce) {
+ switch (getAlgorithmId()) {
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8:
+ // Fall through
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
+ // Fall through
+ case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
+ return new GCMParameterSpec(mChecksumLen * 8, nonce);
+ default:
+ throw new IllegalArgumentException(
+ "Unrecognized Encryption Algorithm ID: " + getAlgorithmId());
+ }
+ }
+
+ /**
+ * Encrypt padded data and calculate checksum for it.
+ *
+ * @param paddedData the padded data to encrypt.
+ * @param additionalAuthData additional data to authenticate (also known as associated data).
+ * @param keyBytes the encryption key.
+ * @param ivBytes the initialization vector (IV).
+ * @return the encrypted and padded data with checksum.
+ */
+ public byte[] encrypt(
+ byte[] paddedData, byte[] additionalAuthData, byte[] keyBytes, byte[] ivBytes) {
+ try {
+ return doCipherAction(
+ paddedData, additionalAuthData, keyBytes, ivBytes, Cipher.ENCRYPT_MODE);
+ } catch (AEADBadTagException e) {
+ throw new IllegalArgumentException("Failed to encrypt data: ", e);
+ }
+ }
+
+ /**
+ * Authenticate and decrypt the padded data with checksum.
+ *
+ * @param paddedDataWithChecksum the padded data with checksum.
+ * @param additionalAuthData additional data to authenticate (also known as associated data).
+ * @param keyBytes the decryption key.
+ * @param ivBytes the initialization vector (IV).
+ * @return the decrypted and padded data
+ * @throws AEADBadTagException if authentication or decryption fails
+ */
+ public byte[] decrypt(
+ byte[] paddedDataWithChecksum,
+ byte[] additionalAuthData,
+ byte[] keyBytes,
+ byte[] ivBytes)
+ throws AEADBadTagException {
+
+ byte[] decryptPaddedDataAndAuthTag =
+ doCipherAction(
+ paddedDataWithChecksum,
+ additionalAuthData,
+ keyBytes,
+ ivBytes,
+ Cipher.DECRYPT_MODE);
+
+ int decryptPaddedDataLen = decryptPaddedDataAndAuthTag.length - mChecksumLen;
+ return Arrays.copyOf(decryptPaddedDataAndAuthTag, decryptPaddedDataLen);
+ }
+
+ /**
+ * Gets key length of this algorithm (in bytes).
+ *
+ * @return the key length (in bytes).
+ */
+ @Override
+ public int getKeyLength() {
+ return super.getKeyLength() + mSaltLen;
+ }
+
+ /**
+ * Returns length of checksum.
+ *
+ * @return the length of checksum in bytes.
+ */
+ public int getChecksumLen() {
+ return mChecksumLen;
+ }
+
+ @Override
+ public IpSecAlgorithm buildIpSecAlgorithmWithKey(byte[] key) {
+ validateKeyLenOrThrow(key);
+ return new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, key, mChecksumLen * 8);
+ }
+}