blob: 78aede515c780dd450ec5a285bc0e56e4e7f90f7 [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.ike.eap.message.simaka;
import static com.android.ike.eap.EapAuthenticator.LOG;
import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_ATTRIBUTE_STRING;
import android.annotation.NonNull;
import com.android.ike.eap.exceptions.simaka.EapSimAkaInvalidAttributeException;
import com.android.ike.eap.exceptions.simaka.EapSimAkaUnsupportedAttributeException;
import com.android.ike.eap.exceptions.simaka.EapSimInvalidAtRandException;
import com.android.ike.eap.message.EapMessage;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
import com.android.ike.eap.message.simaka.EapSimAkaAttribute.EapSimAkaUnsupportedAttribute;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* EapSimTypeData represents the Type Data for an {@link EapMessage} during an EAP-SIM session.
*/
public abstract class EapSimAkaTypeData {
private static final int MIN_LEN_BYTES = 3; // subtype (1B) + reserved bytes (2B)
private static final int RESERVED_BYTES = 2; // RFC 4186#8.1, RFC 4187#8.1
public final int eapSubtype;
// LinkedHashMap used to preserve encoded ordering of attributes. This is necessary for checking
// the MAC value for the message
public final LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap;
public EapSimAkaTypeData(
int eapSubType,
LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap) {
this.eapSubtype = eapSubType;
this.attributeMap = attributeMap;
}
/**
* Creates and returns the byte-array encoding of this EapSimTypeData instance.
*
* @return byte[] representing the byte-encoding of this EapSimTypeData instance.
*/
public byte[] encode() {
int lengthInBytes = MIN_LEN_BYTES;
for (EapSimAkaAttribute attribute : attributeMap.values()) {
lengthInBytes += attribute.lengthInBytes;
}
ByteBuffer output = ByteBuffer.allocate(lengthInBytes);
output.put((byte) eapSubtype);
// two reserved bytes (RFC 4186#8.1, RFC 4187#8.1)
output.put(new byte[RESERVED_BYTES]);
for (EapSimAkaAttribute attribute : attributeMap.values()) {
attribute.encode(output);
}
return output.array();
}
/**
* Class for decoding EAP-SIM and EAP-AKA type-datas.
*
* @param <T> The EapSimAkaTypeData type that the EapSimAkaTypeDataDecoder will be decoding
*/
public abstract static class EapSimAkaTypeDataDecoder<T extends EapSimAkaTypeData> {
private final String mTAG;
private final String mEapMethod;
private final Set<Integer> mSupportedSubtypes;
private final EapSimAkaAttributeFactory mAttributeFactory;
private final Map<Integer, String> mEapSubtypeStrings;
EapSimAkaTypeDataDecoder(
String tag,
String eapMethod,
Set<Integer> supportedSubtypes,
EapSimAkaAttributeFactory eapSimAkaAttributeFactory,
Map<Integer, String> eapSubtypeStrings) {
this.mTAG = tag;
this.mEapMethod = eapMethod;
this.mSupportedSubtypes = supportedSubtypes;
this.mAttributeFactory = eapSimAkaAttributeFactory;
this.mEapSubtypeStrings = eapSubtypeStrings;
}
protected DecodeResult<T> decode(@NonNull byte[] typeData) {
if (typeData == null) {
LOG.d(mTAG, "Invalid EAP Type-Data");
return new DecodeResult<>(AtClientErrorCode.UNABLE_TO_PROCESS);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(typeData);
try {
int eapSubType = Byte.toUnsignedInt(byteBuffer.get());
if (!mSupportedSubtypes.contains(eapSubType)) {
LOG.d(mTAG, "Invalid EAP Type-Data");
return new DecodeResult<>(AtClientErrorCode.UNABLE_TO_PROCESS);
}
// next two bytes are reserved (RFC 4186#8.1, RFC 4187#8.1)
byteBuffer.get(new byte[RESERVED_BYTES]);
// read attributes
LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap = new LinkedHashMap<>();
while (byteBuffer.hasRemaining()) {
EapSimAkaAttribute attribute = mAttributeFactory.getAttribute(byteBuffer);
if (attributeMap.containsKey(attribute.attributeType)) {
// Duplicate attributes are not allowed (RFC 4186#6.3.1, RFC 4187#6.3.1)
LOG.e(mTAG, "Duplicate attribute in parsed EAP-Message");
return new DecodeResult<>(AtClientErrorCode.UNABLE_TO_PROCESS);
}
if (attribute instanceof EapSimAkaUnsupportedAttribute) {
LOG.d(mTAG, "Unsupported EAP-SIM attribute during decoding: "
+ attribute.attributeType);
}
attributeMap.put(attribute.attributeType, attribute);
}
T eapSimAkaTypeData = getInstance(eapSubType, attributeMap);
logDecodedEapSimTypeData(eapSimAkaTypeData);
return new DecodeResult<>(eapSimAkaTypeData);
} catch (EapSimInvalidAtRandException ex) {
LOG.e(mTAG, "Invalid AtRand attribute", ex);
return new DecodeResult<>(AtClientErrorCode.INSUFFICIENT_CHALLENGES);
} catch (EapSimAkaInvalidAttributeException | BufferUnderflowException ex) {
LOG.e(mTAG, "Incorrectly formatted attribute", ex);
return new DecodeResult<>(AtClientErrorCode.UNABLE_TO_PROCESS);
} catch (EapSimAkaUnsupportedAttributeException ex) {
LOG.e(mTAG, "Unrecognized, non-skippable attribute encountered", ex);
return new DecodeResult<>(AtClientErrorCode.UNABLE_TO_PROCESS);
}
}
protected abstract T getInstance(
int eapSubType,
LinkedHashMap<Integer, EapSimAkaAttribute> attributeMap);
private void logDecodedEapSimTypeData(EapSimAkaTypeData eapSimAkaTypeData) {
StringBuilder msg = new StringBuilder();
msg.append("Decoded ");
msg.append(mEapMethod);
msg.append(" type data: ");
msg.append(mEapSubtypeStrings.getOrDefault(eapSimAkaTypeData.eapSubtype, "Unknown"));
msg.append(" attributes=[ ");
for (int attributeType : eapSimAkaTypeData.attributeMap.keySet()) {
msg.append(
EAP_ATTRIBUTE_STRING.getOrDefault(attributeType,
"Unknown(" + attributeType + ")"));
msg.append(" ");
}
msg.append("]");
LOG.i(mTAG, msg.toString());
}
}
/**
* DecodeResult represents the result from calling EapSimTypeDataDecoder.decode(). It will
* contain either a decoded EapSimTypeData or the relevant AtClientErrorCode.
*
* @param <T> The EapSimAkaTypeData type that is wrapped in this DecodeResult
*/
public static class DecodeResult<T extends EapSimAkaTypeData> {
public final T eapTypeData;
public final EapSimAkaAttribute.AtClientErrorCode atClientErrorCode;
public DecodeResult(T eapTypeData) {
this.eapTypeData = eapTypeData;
this.atClientErrorCode = null;
}
public DecodeResult(EapSimAkaAttribute.AtClientErrorCode atClientErrorCode) {
this.atClientErrorCode = atClientErrorCode;
eapTypeData = null;
}
public boolean isSuccessfulDecode() {
return eapTypeData != null;
}
}
}