blob: 70280843c5bef315b6b3a8793541f3a6395d492c [file] [log] [blame]
/*
* Copyright (C) 2018 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.ikev2.message;
import static com.android.ike.ikev2.SaProposal.DhGroup;
import static com.android.ike.ikev2.SaProposal.EncryptionAlgorithm;
import static com.android.ike.ikev2.SaProposal.IntegrityAlgorithm;
import static com.android.ike.ikev2.SaProposal.PseudorandomFunction;
import android.annotation.IntDef;
import android.util.ArraySet;
import android.util.Pair;
import com.android.ike.ikev2.SaProposal;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.NoValidProposalChosenException;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* IkeSaPayload represents a Security Association payload. It contains one or more {@link Proposal}.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange
* Protocol Version 2 (IKEv2)</a>
*/
public final class IkeSaPayload extends IkePayload {
public final boolean isSaResponse;
public final List<Proposal> proposalList;
/**
* Construct an instance of IkeSaPayload for decoding an inbound packet.
*
* @param critical indicates if this payload is critical. Ignored in supported payload as
* instructed by the RFC 7296.
* @param isResp indicates if this payload is in a response message.
* @param payloadBody the encoded payload body in byte array.
*/
IkeSaPayload(boolean critical, boolean isResp, byte[] payloadBody) throws IkeException {
super(IkePayload.PAYLOAD_TYPE_SA, critical);
ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
proposalList = new LinkedList<>();
while (inputBuffer.hasRemaining()) {
Proposal proposal = Proposal.readFrom(inputBuffer);
proposalList.add(proposal);
}
// An SA response must have exactly one SA proposal.
if (isResp && proposalList.size() != 1) {
throw new InvalidSyntaxException(
"Expected only one negotiated proposal from SA response: "
+ "Multiple negotiated proposals found.");
}
isSaResponse = isResp;
}
/**
* Construct an instance of IkeSaPayload for building outbound packet.
*
* <p>The length of spis must be the same as saProposals.
*
* @param isResp indicates if this payload is in a response message.
* @param isIkeSa indicates if this payload is for IKE SA or Child SA
* @param spiSize the size of attached SPIs.
* @param spis the array of all attached SPIs.
* @param saProposals the array of all SA Proposals.
*/
public IkeSaPayload(
boolean isResp, boolean isIkeSa, byte spiSize, long[] spis, SaProposal[] saProposals) {
super(IkePayload.PAYLOAD_TYPE_SA, false);
if (saProposals.length < 1
|| isResp && (saProposals.length > 1)
|| saProposals.length != spis.length) {
throw new IllegalArgumentException("Invalid SA payload.");
}
// TODO: Check that saProposals.length <= 255 in IkeSessionOptions and ChildSessionOptions
isSaResponse = isResp;
proposalList = new ArrayList<Proposal>(saProposals.length);
int protocolId = isIkeSa ? PROTOCOL_ID_IKE : PROTOCOL_ID_ESP;
for (int i = 0; i < saProposals.length; i++) {
// Proposal number must start from 1.
Proposal proposal =
new Proposal(
(byte) (i + 1) /*proposal number*/,
protocolId,
spiSize,
spis[i],
saProposals[i],
false /*does not have unrecognized Transform*/);
proposalList.add(proposal);
}
}
/**
* Construct an instance of IkeSaPayload for building outbound IKE initial setup request.
*
* <p>According to RFC 7296, for an initial IKE SA negotiation, no SPI is included in SA
* Proposal. IKE library, as a client, only supports requesting this initial negotiation.
*
* @param saProposals the array of all SA Proposals.
*/
public IkeSaPayload(SaProposal[] saProposals) {
this(
false /*is request*/,
true /*is IKE SA*/,
(byte) 0,
new long[saProposals.length],
saProposals);
}
/**
* Validate and return the negotiated SA proposal from the received SA payload.
*
* @param reqSaPayload SA payload from SA initiator to validate against.
* @return the validated negotiated SA proposal.
* @throws NoValidProposalChosenException if received SA proposal is invalid.
*/
public SaProposal getVerifiedNegotiatedProposal(IkeSaPayload reqSaPayload)
throws NoValidProposalChosenException {
if (!isSaResponse) {
throw new UnsupportedOperationException(
"Cannot get negotiated SA proposal from a request message.");
}
// If negotiated proposal has an unrecognized Transform, throw an exception.
Proposal respProposal = proposalList.get(0);
if (respProposal.hasUnrecognizedTransform) {
throw new NoValidProposalChosenException(
"Negotiated proposal has unrecognized Transform.");
}
// In SA request payload, the first proposal MUST be 1, and subsequent proposals MUST be one
// more than the previous proposal. In SA response payload, the negotiated proposal number
// MUST match the selected proposal number in SA request Payload.
int negotiatedProposalNum = respProposal.number;
List<Proposal> reqProposalList = reqSaPayload.proposalList;
if (negotiatedProposalNum < 1 || negotiatedProposalNum > reqProposalList.size()) {
throw new NoValidProposalChosenException(
"Negotiated proposal has invalid proposal number.");
}
Proposal reqProposal = reqProposalList.get(negotiatedProposalNum - 1);
if (!respProposal.isNegotiatedFrom(reqProposal)) {
throw new NoValidProposalChosenException("Invalid negotiated proposal.");
}
return respProposal.saProposal;
}
@VisibleForTesting
interface TransformDecoder {
Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeException;
}
// TODO: Add another constructor for building outbound message.
/**
* Proposal represents a set contains cryptographic algorithms and key generating materials. It
* contains multiple {@link Transform}.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.1">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
* <p>Proposals with an unrecognized Protocol ID, containing an unrecognized Transform Type
* or lacking a necessary Transform Type shall be ignored when processing a received SA
* Payload.
*/
public static final class Proposal {
private static final byte LAST_PROPOSAL = 0;
private static final byte NOT_LAST_PROPOSAL = 2;
@VisibleForTesting
static TransformDecoder sTransformDecoder =
new TransformDecoder() {
@Override
public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
throws IkeException {
Transform[] transformArray = new Transform[count];
for (int i = 0; i < count; i++) {
Transform transform = Transform.readFrom(inputBuffer);
if (transform.isSupported) {
transformArray[i] = transform;
}
}
return transformArray;
}
};
public final byte number;
/** All supported protocol will fall into {@link ProtocolId} */
public final int protocolId;
public final byte spiSize;
public final long spi;
public final SaProposal saProposal;
public final boolean hasUnrecognizedTransform;
// TODO: Validate this proposal
@VisibleForTesting
Proposal(
byte number,
int protocolId,
byte spiSize,
long spi,
SaProposal saProposal,
boolean hasUnrecognizedTransform) {
this.number = number;
this.protocolId = protocolId;
this.spiSize = spiSize;
this.spi = spi;
this.saProposal = saProposal;
this.hasUnrecognizedTransform = hasUnrecognizedTransform;
}
@VisibleForTesting
static Proposal readFrom(ByteBuffer inputBuffer) throws IkeException {
byte isLast = inputBuffer.get();
if (isLast != LAST_PROPOSAL && isLast != NOT_LAST_PROPOSAL) {
throw new InvalidSyntaxException(
"Invalid value of Last Proposal Substructure: " + isLast);
}
// Skip RESERVED byte
inputBuffer.get();
int length = Short.toUnsignedInt(inputBuffer.getShort());
byte number = inputBuffer.get();
int protocolId = Byte.toUnsignedInt(inputBuffer.get());
byte spiSize = inputBuffer.get();
int transformCount = Byte.toUnsignedInt(inputBuffer.get());
// TODO: Add check: spiSize must be 0 in initial IKE SA negotiation
// spiSize should be either 8 for IKE or 4 for IPsec.
long spi = SPI_NOT_INCLUDED;
switch (spiSize) {
case 0:
// No SPI field here.
break;
case SPI_LEN_IPSEC:
spi = Integer.toUnsignedLong(inputBuffer.getInt());
break;
case SPI_LEN_IKE:
spi = inputBuffer.getLong();
break;
default:
throw new InvalidSyntaxException(
"Invalid value of spiSize in Proposal Substructure: " + spiSize);
}
Transform[] transformArray =
sTransformDecoder.decodeTransforms(transformCount, inputBuffer);
// TODO: Validate that sum of all Transforms' lengths plus Proposal header length equals
// to Proposal's length.
List<EncryptionTransform> encryptAlgoList = new LinkedList<>();
List<PrfTransform> prfList = new LinkedList<>();
List<IntegrityTransform> integAlgoList = new LinkedList<>();
List<DhGroupTransform> dhGroupList = new LinkedList<>();
List<EsnTransform> esnList = new LinkedList<>();
boolean hasUnrecognizedTransform = false;
for (Transform transform : transformArray) {
switch (transform.type) {
case Transform.TRANSFORM_TYPE_ENCR:
encryptAlgoList.add((EncryptionTransform) transform);
break;
case Transform.TRANSFORM_TYPE_PRF:
prfList.add((PrfTransform) transform);
break;
case Transform.TRANSFORM_TYPE_INTEG:
integAlgoList.add((IntegrityTransform) transform);
break;
case Transform.TRANSFORM_TYPE_DH:
dhGroupList.add((DhGroupTransform) transform);
break;
case Transform.TRANSFORM_TYPE_ESN:
esnList.add((EsnTransform) transform);
break;
default:
hasUnrecognizedTransform = true;
}
}
SaProposal saProposal =
new SaProposal(
protocolId,
encryptAlgoList.toArray(
new EncryptionTransform[encryptAlgoList.size()]),
prfList.toArray(new PrfTransform[prfList.size()]),
integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]),
esnList.toArray(new EsnTransform[esnList.size()]));
return new Proposal(
number, protocolId, spiSize, spi, saProposal, hasUnrecognizedTransform);
}
// TODO: Add another contructor for encoding.
/** Package private */
boolean isNegotiatedFrom(Proposal reqProposal) {
if (protocolId != reqProposal.protocolId || number != reqProposal.number) {
return false;
}
return saProposal.isNegotiatedFrom(reqProposal.saProposal);
}
}
@VisibleForTesting
interface AttributeDecoder {
List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer) throws IkeException;
}
/**
* Transform is an abstract base class that represents the common information for all Transform
* types. It may contain one or more {@link Attribute}.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
* <p>Transforms with unrecognized Transform ID or containing unrecognized Attribute Type
* shall be ignored when processing received SA payload.
*/
public abstract static class Transform {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TRANSFORM_TYPE_ENCR,
TRANSFORM_TYPE_PRF,
TRANSFORM_TYPE_INTEG,
TRANSFORM_TYPE_DH,
TRANSFORM_TYPE_ESN
})
public @interface TransformType {}
public static final int TRANSFORM_TYPE_ENCR = 1;
public static final int TRANSFORM_TYPE_PRF = 2;
public static final int TRANSFORM_TYPE_INTEG = 3;
public static final int TRANSFORM_TYPE_DH = 4;
public static final int TRANSFORM_TYPE_ESN = 5;
private static final byte LAST_TRANSFORM = 0;
private static final byte NOT_LAST_TRANSFORM = 3;
private static final int TRANSFORM_HEADER_LEN = 8;
// TODO: Add constants for supported algorithms
@VisibleForTesting
static AttributeDecoder sAttributeDecoder =
new AttributeDecoder() {
public List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
throws IkeException {
List<Attribute> list = new LinkedList<>();
int parsedLength = TRANSFORM_HEADER_LEN;
while (parsedLength < length) {
Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
parsedLength += pair.second;
list.add(pair.first);
}
// TODO: Validate that parsedLength equals to length.
return list;
}
};
// Only supported type falls into {@link TransformType}
public final int type;
public final int id;
public final boolean isSupported;
/** Construct an instance of Transform for building an outbound packet. */
protected Transform(int type, int id) {
this.type = type;
this.id = id;
if (!isSupportedTransformId(id)) {
throw new IllegalArgumentException(
"Unsupported " + getTransformTypeString() + " Algorithm ID: " + id);
}
this.isSupported = true;
}
/** Construct an instance of Transform for decoding an inbound packet. */
protected Transform(int type, int id, List<Attribute> attributeList) {
this.type = type;
this.id = id;
this.isSupported =
isSupportedTransformId(id) && !hasUnrecognizedAttribute(attributeList);
}
@VisibleForTesting
static Transform readFrom(ByteBuffer inputBuffer) throws IkeException {
byte isLast = inputBuffer.get();
if (isLast != LAST_TRANSFORM && isLast != NOT_LAST_TRANSFORM) {
throw new InvalidSyntaxException(
"Invalid value of Last Transform Substructure: " + isLast);
}
// Skip RESERVED byte
inputBuffer.get();
int length = Short.toUnsignedInt(inputBuffer.getShort());
int type = Byte.toUnsignedInt(inputBuffer.get());
// Skip RESERVED byte
inputBuffer.get();
int id = Short.toUnsignedInt(inputBuffer.getShort());
// Decode attributes
List<Attribute> attributeList = sAttributeDecoder.decodeAttributes(length, inputBuffer);
validateAttributeUniqueness(attributeList);
switch (type) {
case TRANSFORM_TYPE_ENCR:
return new EncryptionTransform(id, attributeList);
case TRANSFORM_TYPE_PRF:
return new PrfTransform(id, attributeList);
case TRANSFORM_TYPE_INTEG:
return new IntegrityTransform(id, attributeList);
case TRANSFORM_TYPE_DH:
return new DhGroupTransform(id, attributeList);
case TRANSFORM_TYPE_ESN:
return new EsnTransform(id, attributeList);
default:
return new UnrecognizedTransform(type, id, attributeList);
}
}
// Throw InvalidSyntaxException if there are multiple Attributes of the same type
private static void validateAttributeUniqueness(List<Attribute> attributeList)
throws IkeException {
Set<Integer> foundTypes = new ArraySet<>();
for (Attribute attr : attributeList) {
if (!foundTypes.add(attr.type)) {
throw new InvalidSyntaxException(
"There are multiple Attributes of the same type. ");
}
}
}
// Check if there is Attribute with unrecognized type.
protected abstract boolean hasUnrecognizedAttribute(List<Attribute> attributeList);
// Check if this Transform ID is supported.
protected abstract boolean isSupportedTransformId(int id);
/**
* Get Tranform Type as a String.
*
* @return Tranform Type as a String.
*/
public abstract String getTransformTypeString();
// TODO: Add abstract getTransformIdString() to return specific algorithm/dhGroup name
}
/**
* EncryptionTransform represents an encryption algorithm. It may contain an Atrribute
* specifying the key length.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class EncryptionTransform extends Transform {
private static final int KEY_LEN_UNASSIGNED = 0;
public final int keyLength;
/**
* Contruct an instance of EncryptionTransform with fixed key length for building an
* outbound packet.
*
* @param id the IKE standard Transform ID.
*/
public EncryptionTransform(@EncryptionAlgorithm int id) {
this(id, KEY_LEN_UNASSIGNED);
}
/**
* Contruct an instance of EncryptionTransform with variable key length for building an
* outbound packet.
*
* @param id the IKE standard Transform ID.
* @param keyLength the specified key length of this encryption algorithm.
*/
public EncryptionTransform(@EncryptionAlgorithm int id, int keyLength) {
super(Transform.TRANSFORM_TYPE_ENCR, id);
this.keyLength = keyLength;
try {
validateKeyLength();
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Contruct an instance of EncryptionTransform for decoding an inbound packet.
*
* @param id the IKE standard Transform ID.
* @param attributeList the decoded list of Attribute.
* @throws InvalidSyntaxException for syntax error.
*/
protected EncryptionTransform(int id, List<Attribute> attributeList)
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_ENCR, id, attributeList);
if (!isSupported) {
keyLength = KEY_LEN_UNASSIGNED;
} else {
if (attributeList.size() == 0) {
keyLength = KEY_LEN_UNASSIGNED;
} else {
KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
keyLength = attr.keyLength;
}
validateKeyLength();
}
}
@Override
public int hashCode() {
return Objects.hash(type, id, keyLength);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof EncryptionTransform)) return false;
EncryptionTransform other = (EncryptionTransform) o;
return (type == other.type && id == other.id && keyLength == other.keyLength);
}
@Override
protected boolean isSupportedTransformId(int id) {
return SaProposal.isSupportedEncryptionAlgorithm(id);
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
for (Attribute attr : attributeList) {
if (attr instanceof UnrecognizedAttribute) {
return true;
}
}
return false;
}
private KeyLengthAttribute getKeyLengthAttribute(List<Attribute> attributeList) {
for (Attribute attr : attributeList) {
if (attr.type == Attribute.ATTRIBUTE_TYPE_KEY_LENGTH) {
return (KeyLengthAttribute) attr;
}
}
throw new IllegalArgumentException("Cannot find Attribute with Key Length type");
}
private void validateKeyLength() throws InvalidSyntaxException {
switch (id) {
case SaProposal.ENCRYPTION_ALGORITHM_3DES:
if (keyLength != KEY_LEN_UNASSIGNED) {
throw new InvalidSyntaxException(
"Must not set Key Length value for this "
+ getTransformTypeString()
+ " Algorithm ID: "
+ id);
}
return;
case SaProposal.ENCRYPTION_ALGORITHM_AES_CBC:
/* fall through */
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:
if (keyLength == KEY_LEN_UNASSIGNED) {
throw new InvalidSyntaxException(
"Must set Key Length value for this "
+ getTransformTypeString()
+ " Algorithm ID: "
+ id);
}
if (keyLength != SaProposal.KEY_LEN_AES_128
&& keyLength != SaProposal.KEY_LEN_AES_192
&& keyLength != SaProposal.KEY_LEN_AES_256) {
throw new InvalidSyntaxException(
"Invalid key length for this "
+ getTransformTypeString()
+ " Algorithm ID: "
+ id);
}
return;
default:
// Won't hit here.
throw new IllegalArgumentException(
"Unrecognized Encryption Algorithm ID: " + id);
}
}
@Override
public String getTransformTypeString() {
return "Encryption Algorithm";
}
}
/**
* PrfTransform represents an pseudorandom function.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class PrfTransform extends Transform {
/**
* Contruct an instance of PrfTransform for building an outbound packet.
*
* @param id the IKE standard Transform ID.
*/
public PrfTransform(@PseudorandomFunction int id) {
super(Transform.TRANSFORM_TYPE_PRF, id);
}
/**
* Contruct an instance of PrfTransform for decoding an inbound packet.
*
* @param id the IKE standard Transform ID.
* @param attributeList the decoded list of Attribute.
* @throws InvalidSyntaxException for syntax error.
*/
protected PrfTransform(int id, List<Attribute> attributeList)
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_PRF, id, attributeList);
}
@Override
public int hashCode() {
return Objects.hash(type, id);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof PrfTransform)) return false;
PrfTransform other = (PrfTransform) o;
return (type == other.type && id == other.id);
}
@Override
protected boolean isSupportedTransformId(int id) {
return SaProposal.isSupportedPseudorandomFunction(id);
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
return !attributeList.isEmpty();
}
@Override
public String getTransformTypeString() {
return "Pseudorandom Function";
}
}
/**
* IntegrityTransform represents an integrity algorithm.
*
* <p>Proposing integrity algorithm for ESP SA is optional. Omitting the IntegrityTransform is
* equivalent to including it with a value of NONE. When multiple integrity algorithms are
* provided, choosing any of them are acceptable.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class IntegrityTransform extends Transform {
/**
* Contruct an instance of IntegrityTransform for building an outbound packet.
*
* @param id the IKE standard Transform ID.
*/
public IntegrityTransform(@IntegrityAlgorithm int id) {
super(Transform.TRANSFORM_TYPE_INTEG, id);
}
/**
* Contruct an instance of IntegrityTransform for decoding an inbound packet.
*
* @param id the IKE standard Transform ID.
* @param attributeList the decoded list of Attribute.
* @throws InvalidSyntaxException for syntax error.
*/
protected IntegrityTransform(int id, List<Attribute> attributeList)
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_INTEG, id, attributeList);
}
@Override
public int hashCode() {
return Objects.hash(type, id);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof IntegrityTransform)) return false;
IntegrityTransform other = (IntegrityTransform) o;
return (type == other.type && id == other.id);
}
@Override
protected boolean isSupportedTransformId(int id) {
return SaProposal.isSupportedIntegrityAlgorithm(id);
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
return !attributeList.isEmpty();
}
@Override
public String getTransformTypeString() {
return "Integrity Algorithm";
}
}
/**
* DhGroupTransform represents a Diffie-Hellman Group
*
* <p>Proposing DH group for non-first Child SA is optional. Omitting the DhGroupTransform is
* equivalent to including it with a value of NONE. When multiple DH groups are provided,
* choosing any of them are acceptable.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class DhGroupTransform extends Transform {
/**
* Contruct an instance of DhGroupTransform for building an outbound packet.
*
* @param id the IKE standard Transform ID.
*/
public DhGroupTransform(@DhGroup int id) {
super(Transform.TRANSFORM_TYPE_DH, id);
}
/**
* Contruct an instance of DhGroupTransform for decoding an inbound packet.
*
* @param id the IKE standard Transform ID.
* @param attributeList the decoded list of Attribute.
* @throws InvalidSyntaxException for syntax error.
*/
protected DhGroupTransform(int id, List<Attribute> attributeList)
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_DH, id, attributeList);
}
@Override
public int hashCode() {
return Objects.hash(type, id);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DhGroupTransform)) return false;
DhGroupTransform other = (DhGroupTransform) o;
return (type == other.type && id == other.id);
}
@Override
protected boolean isSupportedTransformId(int id) {
return SaProposal.isSupportedDhGroup(id);
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
return !attributeList.isEmpty();
}
@Override
public String getTransformTypeString() {
return "Diffie-Hellman Group";
}
}
/**
* EsnTransform represents ESN policy that indicates if IPsec SA uses tranditional 32-bit
* sequence numbers or extended(64-bit) sequence numbers.
*
* <p>Currently IKE library only supports negotiating IPsec SA that do not use extended sequence
* numbers. The Transform ID of EsnTransform in outbound packets is not user configurable.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class EsnTransform extends Transform {
@Retention(RetentionPolicy.SOURCE)
@IntDef({ESN_POLICY_NO_EXTENDED, ESN_POLICY_EXTENDED})
public @interface EsnPolicy {}
public static final int ESN_POLICY_NO_EXTENDED = 0;
public static final int ESN_POLICY_EXTENDED = 1;
/**
* Construct an instance of EsnTransform indicates using no-extended sequence numbers for
* building an outbound packet.
*/
public EsnTransform() {
super(Transform.TRANSFORM_TYPE_ESN, ESN_POLICY_NO_EXTENDED);
}
/**
* Contruct an instance of EsnTransform for decoding an inbound packet.
*
* @param id the IKE standard Transform ID.
* @param attributeList the decoded list of Attribute.
* @throws InvalidSyntaxException for syntax error.
*/
protected EsnTransform(int id, List<Attribute> attributeList)
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_ESN, id, attributeList);
}
@Override
public int hashCode() {
return Objects.hash(type, id);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof EsnTransform)) return false;
EsnTransform other = (EsnTransform) o;
return (type == other.type && id == other.id);
}
@Override
protected boolean isSupportedTransformId(int id) {
return (id == ESN_POLICY_NO_EXTENDED || id == ESN_POLICY_EXTENDED);
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
return !attributeList.isEmpty();
}
@Override
public String getTransformTypeString() {
return "Extended Sequence Numbers";
}
}
/**
* UnrecognizedTransform represents a Transform with unrecognized Transform Type.
*
* <p>Proposals containing an UnrecognizedTransform should be ignored.
*/
protected static final class UnrecognizedTransform extends Transform {
protected UnrecognizedTransform(int type, int id, List<Attribute> attributeList) {
super(type, id, attributeList);
}
@Override
protected boolean isSupportedTransformId(int id) {
return false;
}
@Override
protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
return !attributeList.isEmpty();
}
/**
* Return Tranform Type of Unrecognized Transform as a String.
*
* @return Tranform Type of Unrecognized Transform as a String.
*/
@Override
public String getTransformTypeString() {
return "Unrecognized Transform Type";
}
}
/**
* Attribute is an abtract base class for completing the specification of some {@link
* Transform}.
*
* <p>Attribute is either in Type/Value format or Type/Length/Value format. For TV format,
* Attribute length is always 4 bytes containing value for 2 bytes. While for TLV format,
* Attribute length is determined by length field.
*
* <p>Currently only Key Length type is supported
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.5">RFC 7296, Internet Key
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public abstract static class Attribute {
@Retention(RetentionPolicy.SOURCE)
@IntDef({ATTRIBUTE_TYPE_KEY_LENGTH})
public @interface AttributeType {}
// Support only one Attribute type: Key Length. Should use Type/Value format.
public static final int ATTRIBUTE_TYPE_KEY_LENGTH = 14;
private static final int TV_ATTRIBUTE_VALUE_LEN = 2;
private static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
private static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
// Only Key Length type belongs to AttributeType
public final int type;
/** Construct an instance of an Attribute when decoding message. */
protected Attribute(int type) {
this.type = type;
}
@VisibleForTesting
static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer) throws IkeException {
short formatAndType = inputBuffer.getShort();
int type = formatAndType & 0x7fff;
int length = 0;
byte[] value = new byte[0];
if ((formatAndType & 0x8000) == 0x8000) {
// Type/Value format
length = TV_ATTRIBUTE_TOTAL_LEN;
value = new byte[TV_ATTRIBUTE_VALUE_LEN];
} else {
// Type/Length/Value format
if (type == ATTRIBUTE_TYPE_KEY_LENGTH) {
throw new InvalidSyntaxException("Wrong format in Transform Attribute");
}
length = Short.toUnsignedInt(inputBuffer.getShort());
int valueLen = length - TVL_ATTRIBUTE_HEADER_LEN;
// IkeMessage will catch exception if valueLen is negative.
value = new byte[valueLen];
}
inputBuffer.get(value);
switch (type) {
case ATTRIBUTE_TYPE_KEY_LENGTH:
return new Pair(new KeyLengthAttribute(value), length);
default:
return new Pair(new UnrecognizedAttribute(type, value), length);
}
}
}
/** KeyLengthAttribute represents a Key Length type Attribute */
public static final class KeyLengthAttribute extends Attribute {
public final int keyLength;
protected KeyLengthAttribute(byte[] value) {
this(Short.toUnsignedInt(ByteBuffer.wrap(value).getShort()));
}
protected KeyLengthAttribute(int keyLength) {
super(ATTRIBUTE_TYPE_KEY_LENGTH);
this.keyLength = keyLength;
}
}
/**
* UnrecognizedAttribute represents a Attribute with unrecoginzed Attribute Type.
*
* <p>Transforms containing UnrecognizedAttribute should be ignored.
*/
protected static final class UnrecognizedAttribute extends Attribute {
protected UnrecognizedAttribute(int type, byte[] value) {
super(type);
}
}
/**
* Encode SA payload to ByteBUffer.
*
* @param nextPayload type of payload that follows this payload.
* @param byteBuffer destination ByteBuffer that stores encoded payload.
*/
@Override
protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
throw new UnsupportedOperationException(
"It is not supported to encode a " + getTypeString());
// TODO: Implement encoding SA payload.
}
/**
* Get entire payload length.
*
* @return entire payload length.
*/
@Override
protected int getPayloadLength() {
throw new UnsupportedOperationException(
"It is not supported to get payload length of " + getTypeString());
// TODO: Implement this method for SA payload.
}
/**
* Return the payload type as a String.
*
* @return the payload type as a String.
*/
@Override
public String getTypeString() {
return "SA Payload";
}
}