blob: c0651ae03f31a4f8c877be15af6f6e90735ac9a0 [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;
private static final int PROPOSAL_RESERVED_FIELD_LEN = 1;
private static final int PROPOSAL_HEADER_LEN = 8;
@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(new byte[PROPOSAL_RESERVED_FIELD_LEN]);
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 SPI_LEN_NOT_INCLUDED:
// No SPI attached for IKE initial exchange.
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);
}
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
Transform[] allTransforms = saProposal.getAllTransforms();
byte isLastIndicator = isLast ? LAST_PROPOSAL : NOT_LAST_PROPOSAL;
byteBuffer
.put(isLastIndicator)
.put(new byte[PROPOSAL_RESERVED_FIELD_LEN])
.putShort((short) getProposalLength())
.put(number)
.put((byte) protocolId)
.put(spiSize)
.put((byte) allTransforms.length);
switch (spiSize) {
case SPI_LEN_NOT_INCLUDED:
// No SPI attached for IKE initial exchange.
break;
case SPI_LEN_IPSEC:
byteBuffer.putInt((int) spi);
break;
case SPI_LEN_IKE:
byteBuffer.putLong((long) spi);
break;
default:
throw new IllegalArgumentException(
"Invalid value of spiSize in Proposal Substructure: " + spiSize);
}
// Encode all Transform.
for (int i = 0; i < allTransforms.length; i++) {
// The last transform has the isLast flag set to true.
allTransforms[i].encodeToByteBuffer(i == allTransforms.length - 1, byteBuffer);
}
}
protected int getProposalLength() {
int len = PROPOSAL_HEADER_LEN + spiSize;
Transform[] allTransforms = saProposal.getAllTransforms();
for (Transform t : allTransforms) len += t.getTransformLength();
return len;
}
}
@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;
// Length of reserved field of a Transform.
private static final int TRANSFORM_RESERVED_FIELD_LEN = 1;
// Length of the Transform that with no Attribute.
protected static final int BASIC_TRANSFORM_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 = BASIC_TRANSFORM_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(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
int length = Short.toUnsignedInt(inputBuffer.getShort());
int type = Byte.toUnsignedInt(inputBuffer.get());
// Skip RESERVED byte
inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
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);
// Encode Transform to a ByteBuffer.
protected abstract void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer);
// Get entire Transform length.
protected abstract int getTransformLength();
protected void encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
byte isLastIndicator = isLast ? LAST_TRANSFORM : NOT_LAST_TRANSFORM;
byteBuffer
.put(isLastIndicator)
.put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
.putShort((short) getTransformLength())
.put((byte) type)
.put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
.putShort((short) 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_UNSPECIFIED = 0;
// When using encryption algorithm with variable-length keys, mSpecifiedKeyLength MUST be
// set and a KeyLengthAttribute MUST be attached. Otherwise, mSpecifiedKeyLength MUST NOT be
// set and KeyLengthAttribute MUST NOT be attached.
private final int mSpecifiedKeyLength;
/**
* 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_UNSPECIFIED);
}
/**
* Contruct an instance of EncryptionTransform with variable key length for building an
* outbound packet.
*
* @param id the IKE standard Transform ID.
* @param specifiedKeyLength the specified key length of this encryption algorithm.
*/
public EncryptionTransform(@EncryptionAlgorithm int id, int specifiedKeyLength) {
super(Transform.TRANSFORM_TYPE_ENCR, id);
mSpecifiedKeyLength = specifiedKeyLength;
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) {
mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
} else {
if (attributeList.size() == 0) {
mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
} else {
KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
mSpecifiedKeyLength = attr.keyLength;
}
validateKeyLength();
}
}
@Override
public int hashCode() {
return Objects.hash(type, id, mSpecifiedKeyLength);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof EncryptionTransform)) return false;
EncryptionTransform other = (EncryptionTransform) o;
return (type == other.type
&& id == other.id
&& mSpecifiedKeyLength == other.mSpecifiedKeyLength);
}
@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 (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
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 (mSpecifiedKeyLength == KEY_LEN_UNSPECIFIED) {
throw new InvalidSyntaxException(
"Must set Key Length value for this "
+ getTransformTypeString()
+ " Algorithm ID: "
+ id);
}
if (mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_128
&& mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_192
&& mSpecifiedKeyLength != 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
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
encodeBasicTransformToByteBuffer(isLast, byteBuffer);
if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
new KeyLengthAttribute(mSpecifiedKeyLength).encodeToByteBuffer(byteBuffer);
}
}
@Override
protected int getTransformLength() {
int len = BASIC_TRANSFORM_LEN;
if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
len += new KeyLengthAttribute(mSpecifiedKeyLength).getAttributeLength();
}
return len;
}
@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
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
encodeBasicTransformToByteBuffer(isLast, byteBuffer);
}
@Override
protected int getTransformLength() {
return BASIC_TRANSFORM_LEN;
}
@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
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
encodeBasicTransformToByteBuffer(isLast, byteBuffer);
}
@Override
protected int getTransformLength() {
return BASIC_TRANSFORM_LEN;
}
@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
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
encodeBasicTransformToByteBuffer(isLast, byteBuffer);
}
@Override
protected int getTransformLength() {
return BASIC_TRANSFORM_LEN;
}
@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
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
encodeBasicTransformToByteBuffer(isLast, byteBuffer);
}
@Override
protected int getTransformLength() {
return BASIC_TRANSFORM_LEN;
}
@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();
}
@Override
protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
throw new UnsupportedOperationException(
"It is not supported to encode a Transform with" + getTransformTypeString());
}
@Override
protected int getTransformLength() {
throw new UnsupportedOperationException(
"It is not supported to get length of a Transform with "
+ getTransformTypeString());
}
/**
* 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;
// Mask to extract the left most AF bit to indicate Attribute Format.
private static final int ATTRIBUTE_FORMAT_MASK = 0x8000;
// Mask to extract 15 bits after the AF bit to indicate Attribute Type.
private static final int ATTRIBUTE_TYPE_MASK = 0x7fff;
// Package private mask to indicate that Type-Value (TV) Attribute Format is used.
static final int ATTRIBUTE_FORMAT_TV = ATTRIBUTE_FORMAT_MASK;
// Package private
static final int TV_ATTRIBUTE_VALUE_LEN = 2;
static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
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 format = formatAndType & ATTRIBUTE_FORMAT_MASK;
int type = formatAndType & ATTRIBUTE_TYPE_MASK;
int length = 0;
byte[] value = new byte[0];
if (format == ATTRIBUTE_FORMAT_TV) {
// 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);
}
}
// Encode Attribute to a ByteBuffer.
protected abstract void encodeToByteBuffer(ByteBuffer byteBuffer);
// Get entire Attribute length.
protected abstract int getAttributeLength();
}
/** 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;
}
@Override
protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
byteBuffer
.putShort((short) (ATTRIBUTE_FORMAT_TV | ATTRIBUTE_TYPE_KEY_LENGTH))
.putShort((short) keyLength);
}
@Override
protected int getAttributeLength() {
return TV_ATTRIBUTE_TOTAL_LEN;
}
}
/**
* 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);
}
@Override
protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
throw new UnsupportedOperationException(
"It is not supported to encode an unrecognized Attribute.");
}
@Override
protected int getAttributeLength() {
throw new UnsupportedOperationException(
"It is not supported to get length of an unrecognized Attribute.");
}
}
/**
* 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) {
encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
for (int i = 0; i < proposalList.size(); i++) {
// The last proposal has the isLast flag set to true.
proposalList.get(i).encodeToByteBuffer(i == proposalList.size() - 1, byteBuffer);
}
}
/**
* Get entire payload length.
*
* @return entire payload length.
*/
@Override
protected int getPayloadLength() {
int len = GENERIC_HEADER_LENGTH;
for (Proposal p : proposalList) len += p.getProposalLength();
return len;
}
/**
* Return the payload type as a String.
*
* @return the payload type as a String.
*/
@Override
public String getTypeString() {
return "SA Payload";
}
}