blob: 3846175c65f94d2dcb7c9bf40764314543ec569f [file] [log] [blame]
/*
* Copyright (C) 2020 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.eap.message.ttls;
import static com.android.internal.net.eap.EapAuthenticator.LOG;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.eap.EapResult.EapError;
import com.android.internal.net.eap.exceptions.ttls.EapTtlsParsingException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
/**
* EapTtlsAvp represents the structure of an AVP during an EAP-TTLS session (RFC5281#10.1) The
* structure of the flag byte is as follows:
*
* <pre>
* |---+---+---+---+---+-----------+
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* | V | M | r | r | r | r | r | r |
* |---+---+---+---+---+---+---+---+
* V = Vendor ID present
* M = AVP support is mandatory
* r = Reserved bits (must be ignored)
* </pre>
*
* @see <a href="https://tools.ietf.org/html/rfc5281#section-10.1">RFC 5281, Extensible
* Authentication Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0
* (EAP-TTLSv0)</a>
*/
public class EapTtlsAvp {
private static final String TAG = EapTtlsAvp.class.getSimpleName();
// AVP code derived from RFC3579#3.1. Note that the EAP-TTLS uses an custom AVP structure (see
// RFC 5281, section 10.1), as opposed to the one defined in RFC 3579.
private static final int EAP_MESSAGE_AVP_CODE = 79;
private static final int AVP_CODE_LEN_BYTES = 4;
private static final int AVP_FLAGS_LEN_BYTES = 1;
private static final int AVP_LENGTH_LEN_BYTES = 3;
private static final int AVP_VENDOR_ID_LEN_BYTES = 4;
private static final int AVP_HEADER_LEN_BYTES =
AVP_CODE_LEN_BYTES + AVP_FLAGS_LEN_BYTES + AVP_LENGTH_LEN_BYTES;
private static final int AVP_BYTE_ALIGNMENT = 4;
private static final int FLAG_VENDOR_ID_INCLUDED = 1 << 7;
private static final int FLAG_AVP_MANDATORY = 1 << 6;
public final int avpCode;
public final int avpLength;
public final int vendorId;
public final byte[] data;
public final boolean isMandatory;
public final boolean isVendorIdPresent;
@VisibleForTesting
EapTtlsAvp(ByteBuffer buffer) throws EapTtlsParsingException {
avpCode = buffer.getInt();
byte avpFlags = buffer.get();
isMandatory = (avpFlags & FLAG_AVP_MANDATORY) != 0;
isVendorIdPresent = (avpFlags & FLAG_VENDOR_ID_INCLUDED) != 0;
avpLength = getAvpLength(buffer);
int dataLength = avpLength - AVP_HEADER_LEN_BYTES;
if (isVendorIdPresent) {
dataLength -= AVP_VENDOR_ID_LEN_BYTES;
vendorId = buffer.getInt();
} else {
// no vendor ID is equivalent to a vendor ID of 0 (RFC5281#10.1)
vendorId = 0;
}
if (dataLength < 0) {
throw new EapTtlsParsingException(
"Received an AVP with an invalid length: "
+ avpLength
+ ". Data length was predicted to be "
+ dataLength);
}
data = new byte[dataLength];
buffer.get(data);
// the remaining padding is consumed in order to align with the next AVP header
int paddingSize = getAvpPadding(avpLength);
buffer.get(new byte[paddingSize]);
}
private EapTtlsAvp(int avpCode, int vendorId, boolean isMandatory, byte[] data) {
this.avpCode = avpCode;
this.vendorId = vendorId;
this.isMandatory = isMandatory;
this.data = data;
// A vendor ID of 0 is equivalent to not sending the vendor ID at all (RFC5281#10.1)
if (vendorId != 0) {
avpLength = data.length + AVP_HEADER_LEN_BYTES + AVP_VENDOR_ID_LEN_BYTES;
isVendorIdPresent = true;
} else {
avpLength = data.length + AVP_HEADER_LEN_BYTES;
isVendorIdPresent = false;
}
}
/**
* Assembles each bit from the flag byte into a byte
*
* @return a byte that compromises the avp flags
*/
private byte getFlagByte() {
int flag = 0;
flag |= isVendorIdPresent ? FLAG_VENDOR_ID_INCLUDED : 0;
flag |= isMandatory ? FLAG_AVP_MANDATORY : 0;
return (byte) flag;
}
/**
* Encodes this AVP instance into a byte array.
*
* @return byte[] representing the encoded value of this EapTtlsAvp instance
*/
public byte[] encode() {
// Each AVP must be padded to the next 4 byte boundary (RFC5281#10.2), so 0 to 3 padding
// bytes may be added to the original length
int paddedAvpLength = avpLength + getAvpPadding(avpLength);
ByteBuffer encodedBuffer = ByteBuffer.allocate(paddedAvpLength);
encodedBuffer.putInt(avpCode);
encodedBuffer.put(getFlagByte());
encodeAvpLength(encodedBuffer, avpLength);
if (isVendorIdPresent) {
encodedBuffer.putInt(vendorId);
}
encodedBuffer.put(data);
return encodedBuffer.array();
}
/**
* Produces an EAP-MESSAGE AVP (RFC5281#10.1)
*
* @param data the data to encode in the avp
* @param vendorId the vendorId or 0 if not specified
* @return an EAP-MESSAGE AVP
*/
public static EapTtlsAvp getEapMessageAvp(int vendorId, byte[] data) {
return new EapTtlsAvp(EAP_MESSAGE_AVP_CODE, vendorId, true /* isMandatory */, data);
}
/**
* Retrieves the required padding bytes (4 byte aligned) for a given length
*
* @param avpLength the length to pad
* @return the required padding bytes
*/
@VisibleForTesting
static int getAvpPadding(int avpLength) {
if (avpLength % AVP_BYTE_ALIGNMENT == 0) {
return 0;
}
return AVP_BYTE_ALIGNMENT - (avpLength % AVP_BYTE_ALIGNMENT);
}
/**
* Encodes an AVP length into a given bytebuffer
*
* <p>As per RFC5281#10.2, the avp length field is 3 bytes
*
* @param buffer the bytebuffer to encode the length into
* @param length the length to encode
*/
@VisibleForTesting
static void encodeAvpLength(ByteBuffer buffer, int length) {
buffer.put((byte) (length >> 16));
buffer.put((byte) (length >> 8));
buffer.put((byte) length);
}
/**
* Converts a byte array of size 3 to its integer representation
*
* <p>As per RFC5281#10.2, the AVP length field is 3 bytes
*
* @param buffer a byte buffer to extract the length from
* @return an int representation of the byte array
* @throws BufferUnderflowException if the buffer has less than 3 bytes remaining
*/
@VisibleForTesting
static int getAvpLength(ByteBuffer buffer) throws BufferUnderflowException {
return (Byte.toUnsignedInt(buffer.get()) << 16)
| (Byte.toUnsignedInt(buffer.get()) << 8)
| Byte.toUnsignedInt(buffer.get());
}
/** EapTtlsAvpDecoder will be used for decoding {@link EapTtlsAvp} objects. */
public static class EapTtlsAvpDecoder {
/**
* Decodes and returns an EapTtlsAvp for the specified EAP-TTLS AVP.
*
* <p>In the case that multiple AVPs are received, all AVPs will be decoded, but only the
* EAP-MESSAGE AVP will be stored. All AVP codes and Vendor-IDs will be logged. Furthermore,
* if multiple EAP-MESSAGE AVPs are received, this will be treated as an error.
*
* @param avp a byte array representing the AVP
* @return DecodeResult wrapping an EapTtlsAvp instance for the given EapTtlsAvp iff the
* eapTtlsAvp is formatted correctly. Otherwise, the DecodeResult wraps the appropriate
* EapError.
*/
public AvpDecodeResult decode(byte[] avp) {
try {
// AVPs must be 4 byte aligned (RFC5281#10.2)
if (avp.length % AVP_BYTE_ALIGNMENT != 0) {
return new AvpDecodeResult(
new EapError(
new EapTtlsParsingException(
"Received one or more invalid AVPs: AVPs must be 4"
+ " byte aligned.")));
}
ByteBuffer avpBuffer = ByteBuffer.wrap(avp);
EapTtlsAvp eapMessageAvp = null;
while (avpBuffer.hasRemaining()) {
EapTtlsAvp decodedAvp = new EapTtlsAvp(avpBuffer);
LOG.i(
TAG,
"Decoded AVP with code "
+ decodedAvp.avpCode
+ " and vendor ID "
+ decodedAvp.vendorId);
if (decodedAvp.avpCode == EAP_MESSAGE_AVP_CODE) {
if (eapMessageAvp != null) {
// Only one EAP-MESSAGE AVP is expected at a time
return new AvpDecodeResult(
new EapError(
new EapTtlsParsingException(
"Received multiple EAP-MESSAGE AVPs in one"
+ " message")));
}
eapMessageAvp = decodedAvp;
} else if (decodedAvp.isMandatory) {
// As per RFC5281#10.1, if an AVP tagged as mandatory is unsupported, the
// negotiation should fail
return new AvpDecodeResult(
new EapError(
new EapTtlsParsingException(
"Received an AVP that requires support for AVP code"
+ decodedAvp.avpCode)));
}
}
if (eapMessageAvp == null) {
return new AvpDecodeResult(
new EapError(
new EapTtlsParsingException(
"No EAP-MESSAGE (79) AVP was found")));
}
return new AvpDecodeResult(eapMessageAvp);
} catch (BufferUnderflowException | EapTtlsParsingException e) {
return new AvpDecodeResult(new EapError(e));
}
}
/**
* DecodeResult represents the result from attempting to decode a sequence of EAP-TTLS
* AVPs. It will contain either an EapTtlsAvp or an EapError.
*
* <p>In the case that multiple AVPs are received, all AVPs will be decoded and their AVP
* codes/Vendor-ID will be logged. However, only the EAP-MESSAGE AVP will be stored in the
* decode result. Furthermore, if zero, or multiple EAP-MESSAGE AVPs are received, this will
* be treated as an error.
*/
public static class AvpDecodeResult {
public final EapTtlsAvp eapTtlsAvp;
public final EapError eapError;
public AvpDecodeResult(EapTtlsAvp eapTtlsAvp) {
this.eapTtlsAvp = eapTtlsAvp;
this.eapError = null;
}
public AvpDecodeResult(EapError eapError) {
this.eapTtlsAvp = null;
this.eapError = eapError;
}
/**
* Checks whether this instance represents a successful decode operation.
*
* @return true iff this DecodeResult represents a successfully decoded Type Data
*/
public boolean isSuccessfulDecode() {
return eapTtlsAvp != null;
}
}
}
}