/*
|
* Copyright (C) 2017 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 android.net;
|
|
import android.annotation.NonNull;
|
import android.annotation.StringDef;
|
import android.os.Build;
|
import android.os.Parcel;
|
import android.os.Parcelable;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.HexDump;
|
|
import java.lang.annotation.Retention;
|
import java.lang.annotation.RetentionPolicy;
|
import java.util.Arrays;
|
|
/**
|
* This class represents a single algorithm that can be used by an {@link IpSecTransform}.
|
*
|
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
|
* Internet Protocol</a>
|
*/
|
public final class IpSecAlgorithm implements Parcelable {
|
private static final String TAG = "IpSecAlgorithm";
|
|
/**
|
* Null cipher.
|
*
|
* @hide
|
*/
|
public static final String CRYPT_NULL = "ecb(cipher_null)";
|
|
/**
|
* AES-CBC Encryption/Ciphering Algorithm.
|
*
|
* <p>Valid lengths for this key are {128, 192, 256}.
|
*/
|
public static final String CRYPT_AES_CBC = "cbc(aes)";
|
|
/**
|
* MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
|
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
|
*
|
* <p>Keys for this algorithm must be 128 bits in length.
|
*
|
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
|
*/
|
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
|
|
/**
|
* SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
|
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
|
*
|
* <p>Keys for this algorithm must be 160 bits in length.
|
*
|
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
|
*/
|
public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
|
|
/**
|
* SHA256 HMAC Authentication/Integrity Algorithm.
|
*
|
* <p>Keys for this algorithm must be 256 bits in length.
|
*
|
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
|
*/
|
public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
|
|
/**
|
* SHA384 HMAC Authentication/Integrity Algorithm.
|
*
|
* <p>Keys for this algorithm must be 384 bits in length.
|
*
|
* <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
|
*/
|
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
|
|
/**
|
* SHA512 HMAC Authentication/Integrity Algorithm.
|
*
|
* <p>Keys for this algorithm must be 512 bits in length.
|
*
|
* <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
|
*/
|
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
|
|
/**
|
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
|
*
|
* <p>Valid lengths for keying material are {160, 224, 288}.
|
*
|
* <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
|
* 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
|
* salt. RFC compliance requires that the salt must be unique per invocation with the same key.
|
*
|
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
|
*/
|
public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
|
|
/** @hide */
|
@StringDef({
|
CRYPT_AES_CBC,
|
AUTH_HMAC_MD5,
|
AUTH_HMAC_SHA1,
|
AUTH_HMAC_SHA256,
|
AUTH_HMAC_SHA384,
|
AUTH_HMAC_SHA512,
|
AUTH_CRYPT_AES_GCM
|
})
|
@Retention(RetentionPolicy.SOURCE)
|
public @interface AlgorithmName {}
|
|
private final String mName;
|
private final byte[] mKey;
|
private final int mTruncLenBits;
|
|
/**
|
* Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
|
* defined as constants in this class.
|
*
|
* <p>For algorithms that produce an integrity check value, the truncation length is a required
|
* parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
|
*
|
* @param algorithm name of the algorithm.
|
* @param key key padded to a multiple of 8 bits.
|
*/
|
public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
|
this(algorithm, key, 0);
|
}
|
|
/**
|
* Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
|
* defined as constants in this class.
|
*
|
* <p>This constructor only supports algorithms that use a truncation length. i.e.
|
* Authentication and Authenticated Encryption algorithms.
|
*
|
* @param algorithm name of the algorithm.
|
* @param key key padded to a multiple of 8 bits.
|
* @param truncLenBits number of bits of output hash to use.
|
*/
|
public IpSecAlgorithm(
|
@NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
|
mName = algorithm;
|
mKey = key.clone();
|
mTruncLenBits = truncLenBits;
|
checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
|
}
|
|
/** Get the algorithm name */
|
@NonNull
|
public String getName() {
|
return mName;
|
}
|
|
/** Get the key for this algorithm */
|
@NonNull
|
public byte[] getKey() {
|
return mKey.clone();
|
}
|
|
/** Get the truncation length of this algorithm, in bits */
|
public int getTruncationLengthBits() {
|
return mTruncLenBits;
|
}
|
|
/* Parcelable Implementation */
|
public int describeContents() {
|
return 0;
|
}
|
|
/** Write to parcel */
|
public void writeToParcel(Parcel out, int flags) {
|
out.writeString(mName);
|
out.writeByteArray(mKey);
|
out.writeInt(mTruncLenBits);
|
}
|
|
/** Parcelable Creator */
|
public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
|
new Parcelable.Creator<IpSecAlgorithm>() {
|
public IpSecAlgorithm createFromParcel(Parcel in) {
|
final String name = in.readString();
|
final byte[] key = in.createByteArray();
|
final int truncLenBits = in.readInt();
|
|
return new IpSecAlgorithm(name, key, truncLenBits);
|
}
|
|
public IpSecAlgorithm[] newArray(int size) {
|
return new IpSecAlgorithm[size];
|
}
|
};
|
|
private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
|
boolean isValidLen = true;
|
boolean isValidTruncLen = true;
|
|
switch(name) {
|
case CRYPT_AES_CBC:
|
isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
|
break;
|
case AUTH_HMAC_MD5:
|
isValidLen = keyLen == 128;
|
isValidTruncLen = truncLen >= 96 && truncLen <= 128;
|
break;
|
case AUTH_HMAC_SHA1:
|
isValidLen = keyLen == 160;
|
isValidTruncLen = truncLen >= 96 && truncLen <= 160;
|
break;
|
case AUTH_HMAC_SHA256:
|
isValidLen = keyLen == 256;
|
isValidTruncLen = truncLen >= 96 && truncLen <= 256;
|
break;
|
case AUTH_HMAC_SHA384:
|
isValidLen = keyLen == 384;
|
isValidTruncLen = truncLen >= 192 && truncLen <= 384;
|
break;
|
case AUTH_HMAC_SHA512:
|
isValidLen = keyLen == 512;
|
isValidTruncLen = truncLen >= 256 && truncLen <= 512;
|
break;
|
case AUTH_CRYPT_AES_GCM:
|
// The keying material for GCM is a key plus a 32-bit salt
|
isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
|
isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
|
break;
|
default:
|
throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
|
}
|
|
if (!isValidLen) {
|
throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
|
}
|
if (!isValidTruncLen) {
|
throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
|
}
|
}
|
|
/** @hide */
|
public boolean isAuthentication() {
|
switch (getName()) {
|
// Fallthrough
|
case AUTH_HMAC_MD5:
|
case AUTH_HMAC_SHA1:
|
case AUTH_HMAC_SHA256:
|
case AUTH_HMAC_SHA384:
|
case AUTH_HMAC_SHA512:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
/** @hide */
|
public boolean isEncryption() {
|
return getName().equals(CRYPT_AES_CBC);
|
}
|
|
/** @hide */
|
public boolean isAead() {
|
return getName().equals(AUTH_CRYPT_AES_GCM);
|
}
|
|
// Because encryption keys are sensitive and userdebug builds are used by large user pools
|
// such as beta testers, we only allow sensitive info such as keys on eng builds.
|
private static boolean isUnsafeBuild() {
|
return Build.IS_DEBUGGABLE && Build.IS_ENG;
|
}
|
|
@Override
|
@NonNull
|
public String toString() {
|
return new StringBuilder()
|
.append("{mName=")
|
.append(mName)
|
.append(", mKey=")
|
.append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
|
.append(", mTruncLenBits=")
|
.append(mTruncLenBits)
|
.append("}")
|
.toString();
|
}
|
|
/** @hide */
|
@VisibleForTesting
|
public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
|
if (lhs == null || rhs == null) return (lhs == rhs);
|
return (lhs.mName.equals(rhs.mName)
|
&& Arrays.equals(lhs.mKey, rhs.mKey)
|
&& lhs.mTruncLenBits == rhs.mTruncLenBits);
|
}
|
};
|