blob: 38d9883f0003b4b71b67d5a8be0b68479665194d [file] [log] [blame]
/*
* 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);
}
};