blob: 8c7a8d5c5f4784b398b93338fac56958a0d4b8c2 [file] [log] [blame]
/*
* 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.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) {
super(algorithmId, keyLength, ivLength, algorithmName, true /*isAead*/);
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);
}
}