blob: d2dd39439746ac060d6ceb87df90d648404f59d1 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.net.ipsec.ike.message;
18
19import static android.net.ipsec.ike.IkeManager.getIkeLog;
20import static android.net.ipsec.ike.SaProposal.DhGroup;
21import static android.net.ipsec.ike.SaProposal.EncryptionAlgorithm;
22import static android.net.ipsec.ike.SaProposal.IntegrityAlgorithm;
23import static android.net.ipsec.ike.SaProposal.PseudorandomFunction;
24
25import android.annotation.IntDef;
26import android.annotation.NonNull;
27import android.net.IpSecManager.ResourceUnavailableException;
28import android.net.IpSecManager.SecurityParameterIndex;
29import android.net.IpSecManager.SpiUnavailableException;
30import android.net.ipsec.ike.ChildSaProposal;
31import android.net.ipsec.ike.IkeSaProposal;
32import android.net.ipsec.ike.SaProposal;
33import android.net.ipsec.ike.exceptions.IkeProtocolException;
34import android.util.ArraySet;
35import android.util.Pair;
36
37import com.android.internal.annotations.VisibleForTesting;
38import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
39import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
40import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
41import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
42import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
43
44import java.io.IOException;
45import java.lang.annotation.Retention;
46import java.lang.annotation.RetentionPolicy;
47import java.net.InetAddress;
48import java.nio.ByteBuffer;
49import java.util.ArrayList;
50import java.util.LinkedList;
51import java.util.List;
52import java.util.Objects;
53import java.util.Set;
54
55/**
56 * IkeSaPayload represents a Security Association payload. It contains one or more {@link Proposal}.
57 *
58 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange
59 * Protocol Version 2 (IKEv2)</a>
60 */
61public final class IkeSaPayload extends IkePayload {
62 private static final String TAG = "IkeSaPayload";
63
64 public final boolean isSaResponse;
65 public final List<Proposal> proposalList;
66 /**
67 * Construct an instance of IkeSaPayload for decoding an inbound packet.
68 *
69 * @param critical indicates if this payload is critical. Ignored in supported payload as
70 * instructed by the RFC 7296.
71 * @param isResp indicates if this payload is in a response message.
72 * @param payloadBody the encoded payload body in byte array.
73 */
74 IkeSaPayload(boolean critical, boolean isResp, byte[] payloadBody) throws IkeProtocolException {
75 super(IkePayload.PAYLOAD_TYPE_SA, critical);
76
77 ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
78 proposalList = new LinkedList<>();
79 while (inputBuffer.hasRemaining()) {
80 Proposal proposal = Proposal.readFrom(inputBuffer);
81 proposalList.add(proposal);
82 }
83
84 if (proposalList.isEmpty()) {
85 throw new InvalidSyntaxException("Found no SA Proposal in this SA Payload.");
86 }
87
88 // An SA response must have exactly one SA proposal.
89 if (isResp && proposalList.size() != 1) {
90 throw new InvalidSyntaxException(
91 "Expected only one negotiated proposal from SA response: "
92 + "Multiple negotiated proposals found.");
93 }
94 isSaResponse = isResp;
95
96 boolean firstIsIkeProposal = (proposalList.get(0).protocolId == PROTOCOL_ID_IKE);
97 for (int i = 1; i < proposalList.size(); i++) {
98 boolean isIkeProposal = (proposalList.get(i).protocolId == PROTOCOL_ID_IKE);
99 if (firstIsIkeProposal != isIkeProposal) {
100 getIkeLog()
101 .w(TAG, "Found both IKE proposals and Child proposals in this SA Payload.");
102 break;
103 }
104 }
105
106 getIkeLog().d(TAG, "Receive " + toString());
107 }
108
109 /** Package private constructor for building a request for IKE SA initial creation or rekey */
110 @VisibleForTesting
111 IkeSaPayload(
112 boolean isResp,
113 byte spiSize,
114 IkeSaProposal[] saProposals,
115 IkeSpiGenerator ikeSpiGenerator,
116 InetAddress localAddress)
117 throws IOException {
118 this(isResp, spiSize, localAddress);
119
120 if (saProposals.length < 1 || isResp && (saProposals.length > 1)) {
121 throw new IllegalArgumentException("Invalid SA payload.");
122 }
123
124 for (int i = 0; i < saProposals.length; i++) {
125 // Proposal number must start from 1.
126 proposalList.add(
127 IkeProposal.createIkeProposal(
128 (byte) (i + 1) /* number */,
129 spiSize,
130 saProposals[i],
131 ikeSpiGenerator,
132 localAddress));
133 }
134
135 getIkeLog().d(TAG, "Generate " + toString());
136 }
137
138 /** Package private constructor for building an response SA Payload for IKE SA rekeys. */
139 @VisibleForTesting
140 IkeSaPayload(
141 boolean isResp,
142 byte spiSize,
143 byte proposalNumber,
144 IkeSaProposal saProposal,
145 IkeSpiGenerator ikeSpiGenerator,
146 InetAddress localAddress)
147 throws IOException {
148 this(isResp, spiSize, localAddress);
149
150 proposalList.add(
151 IkeProposal.createIkeProposal(
152 proposalNumber /* number */,
153 spiSize,
154 saProposal,
155 ikeSpiGenerator,
156 localAddress));
157
158 getIkeLog().d(TAG, "Generate " + toString());
159 }
160
161 private IkeSaPayload(boolean isResp, byte spiSize, InetAddress localAddress)
162 throws IOException {
163 super(IkePayload.PAYLOAD_TYPE_SA, false);
164
165 // TODO: Check that proposals.length <= 255 in IkeSessionParams and ChildSessionParams
166 isSaResponse = isResp;
167
168 // TODO: Allocate IKE SPI and pass to IkeProposal.createIkeProposal()
169
170 // ProposalList populated in other constructors
171 proposalList = new ArrayList<Proposal>();
172 }
173
174 /**
175 * Package private constructor for building an outbound request SA Payload for Child SA
176 * negotiation.
177 */
178 @VisibleForTesting
179 IkeSaPayload(
180 ChildSaProposal[] saProposals,
181 IpSecSpiGenerator ipSecSpiGenerator,
182 InetAddress localAddress)
183 throws SpiUnavailableException, ResourceUnavailableException {
184 this(false /* isResp */, ipSecSpiGenerator, localAddress);
185
186 if (saProposals.length < 1) {
187 throw new IllegalArgumentException("Invalid SA payload.");
188 }
189
190 // TODO: Check that saProposals.length <= 255 in IkeSessionParams and ChildSessionParams
191
192 for (int i = 0; i < saProposals.length; i++) {
193 // Proposal number must start from 1.
194 proposalList.add(
195 ChildProposal.createChildProposal(
196 (byte) (i + 1) /* number */,
197 saProposals[i],
198 ipSecSpiGenerator,
199 localAddress));
200 }
201
202 getIkeLog().d(TAG, "Generate " + toString());
203 }
204
205 /**
206 * Package private constructor for building an outbound response SA Payload for Child SA
207 * negotiation.
208 */
209 @VisibleForTesting
210 IkeSaPayload(
211 byte proposalNumber,
212 ChildSaProposal saProposal,
213 IpSecSpiGenerator ipSecSpiGenerator,
214 InetAddress localAddress)
215 throws SpiUnavailableException, ResourceUnavailableException {
216 this(true /* isResp */, ipSecSpiGenerator, localAddress);
217
218 proposalList.add(
219 ChildProposal.createChildProposal(
220 proposalNumber /* number */, saProposal, ipSecSpiGenerator, localAddress));
221
222 getIkeLog().d(TAG, "Generate " + toString());
223 }
224
225 /** Constructor for building an outbound SA Payload for Child SA negotiation. */
226 private IkeSaPayload(
227 boolean isResp, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress) {
228 super(IkePayload.PAYLOAD_TYPE_SA, false);
229
230 isSaResponse = isResp;
231
232 // TODO: Allocate Child SPI and pass to ChildProposal.createChildProposal()
233
234 // ProposalList populated in other constructors
235 proposalList = new ArrayList<Proposal>();
236 }
237
238 /**
239 * Construct an instance of IkeSaPayload for building an outbound IKE initial setup request.
240 *
241 * <p>According to RFC 7296, for an initial IKE SA negotiation, no SPI is included in SA
242 * Proposal. IKE library, as a client, only supports requesting this initial negotiation.
243 *
244 * @param saProposals the array of all SA Proposals.
245 */
246 public static IkeSaPayload createInitialIkeSaPayload(IkeSaProposal[] saProposals)
247 throws IOException {
248 return new IkeSaPayload(
249 false /* isResp */,
250 SPI_LEN_NOT_INCLUDED,
251 saProposals,
252 null /* ikeSpiGenerator unused */,
253 null /* localAddress unused */);
254 }
255
256 /**
257 * Construct an instance of IkeSaPayload for building an outbound request for Rekey IKE.
258 *
259 * @param saProposals the array of all IKE SA Proposals.
260 * @param ikeSpiGenerator the IKE SPI generator.
261 * @param localAddress the local address assigned on-device.
262 */
263 public static IkeSaPayload createRekeyIkeSaRequestPayload(
264 IkeSaProposal[] saProposals, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)
265 throws IOException {
266 return new IkeSaPayload(
267 false /* isResp */, SPI_LEN_IKE, saProposals, ikeSpiGenerator, localAddress);
268 }
269
270 /**
271 * Construct an instance of IkeSaPayload for building an outbound response for Rekey IKE.
272 *
273 * @param respProposalNumber the selected proposal's number.
274 * @param saProposal the expected selected IKE SA Proposal.
275 * @param ikeSpiGenerator the IKE SPI generator.
276 * @param localAddress the local address assigned on-device.
277 */
278 public static IkeSaPayload createRekeyIkeSaResponsePayload(
279 byte respProposalNumber,
280 IkeSaProposal saProposal,
281 IkeSpiGenerator ikeSpiGenerator,
282 InetAddress localAddress)
283 throws IOException {
284 return new IkeSaPayload(
285 true /* isResp */,
286 SPI_LEN_IKE,
287 respProposalNumber,
288 saProposal,
289 ikeSpiGenerator,
290 localAddress);
291 }
292
293 /**
294 * Construct an instance of IkeSaPayload for building an outbound request for Child SA
295 * negotiation.
296 *
297 * @param saProposals the array of all Child SA Proposals.
298 * @param ipSecSpiGenerator the IPsec SPI generator.
299 * @param localAddress the local address assigned on-device.
300 * @throws ResourceUnavailableException if too many SPIs are currently allocated for this user.
301 */
302 public static IkeSaPayload createChildSaRequestPayload(
303 ChildSaProposal[] saProposals,
304 IpSecSpiGenerator ipSecSpiGenerator,
305 InetAddress localAddress)
306 throws SpiUnavailableException, ResourceUnavailableException {
307
308 return new IkeSaPayload(saProposals, ipSecSpiGenerator, localAddress);
309 }
310
311 /**
312 * Construct an instance of IkeSaPayload for building an outbound response for Child SA
313 * negotiation.
314 *
315 * @param respProposalNumber the selected proposal's number.
316 * @param saProposal the expected selected Child SA Proposal.
317 * @param ipSecSpiGenerator the IPsec SPI generator.
318 * @param localAddress the local address assigned on-device.
319 */
320 public static IkeSaPayload createChildSaResponsePayload(
321 byte respProposalNumber,
322 ChildSaProposal saProposal,
323 IpSecSpiGenerator ipSecSpiGenerator,
324 InetAddress localAddress)
325 throws SpiUnavailableException, ResourceUnavailableException {
326 return new IkeSaPayload(respProposalNumber, saProposal, ipSecSpiGenerator, localAddress);
327 }
328
329 /**
330 * Finds the proposal in this (request) payload that matches the response proposal.
331 *
332 * @param respProposal the Proposal to match against.
333 * @return the byte-value proposal number of the selected proposal
334 * @throws NoValidProposalChosenException if no matching proposal was found.
335 */
336 public byte getNegotiatedProposalNumber(SaProposal respProposal)
337 throws NoValidProposalChosenException {
338 for (int i = 0; i < proposalList.size(); i++) {
339 Proposal reqProposal = proposalList.get(i);
340 if (respProposal.isNegotiatedFrom(reqProposal.getSaProposal())
341 && reqProposal.getSaProposal().getProtocolId()
342 == respProposal.getProtocolId()) {
343 return reqProposal.number;
344 }
345 }
346 throw new NoValidProposalChosenException("No remotely proposed protocol acceptable");
347 }
348
349 /**
350 * Validate the IKE SA Payload pair (request/response) and return the IKE SA negotiation result.
351 *
352 * <p>Caller is able to extract the negotiated IKE SA Proposal from the response Proposal and
353 * the IKE SPI pair generated by both sides.
354 *
355 * <p>In a locally-initiated case all IKE SA proposals (from users in initial creation or from
356 * previously negotiated proposal in rekey creation) in the locally generated reqSaPayload have
357 * been validated during building and are unmodified. All Transform combinations in these SA
358 * proposals are valid for IKE SA negotiation. It means each IKE SA request proposal MUST have
359 * Encryption algorithms, DH group configurations and PRFs. Integrity algorithms can only be
360 * omitted when AEAD is used.
361 *
362 * <p>In a remotely-initiated case the locally generated respSaPayload has exactly one SA
363 * proposal. It is validated during building and are unmodified. This proposal has a valid
364 * Transform combination for an IKE SA and has at most one value for each Transform type.
365 *
366 * <p>The response IKE SA proposal is validated against one of the request IKE SA proposals. It
367 * is guaranteed that for each Transform type that the request proposal has provided options,
368 * the response proposal has exact one Transform value.
369 *
370 * @param reqSaPayload the request payload.
371 * @param respSaPayload the response payload.
372 * @param remoteAddress the address of the remote IKE peer.
373 * @return the Pair of selected IkeProposal in request and the IkeProposal in response.
374 * @throws NoValidProposalChosenException if the response SA Payload cannot be negotiated from
375 * the request SA Payload.
376 */
377 public static Pair<IkeProposal, IkeProposal> getVerifiedNegotiatedIkeProposalPair(
378 IkeSaPayload reqSaPayload,
379 IkeSaPayload respSaPayload,
380 IkeSpiGenerator ikeSpiGenerator,
381 InetAddress remoteAddress)
382 throws NoValidProposalChosenException, IOException {
383 Pair<Proposal, Proposal> proposalPair =
384 getVerifiedNegotiatedProposalPair(reqSaPayload, respSaPayload);
385 IkeProposal reqProposal = (IkeProposal) proposalPair.first;
386 IkeProposal respProposal = (IkeProposal) proposalPair.second;
387
388 try {
389 // Allocate initiator's inbound SPI as needed for remotely initiated IKE SA creation
390 if (reqProposal.spiSize != SPI_NOT_INCLUDED
391 && reqProposal.getIkeSpiResource() == null) {
392 reqProposal.allocateResourceForRemoteIkeSpi(ikeSpiGenerator, remoteAddress);
393 }
394 // Allocate responder's inbound SPI as needed for locally initiated IKE SA creation
395 if (respProposal.spiSize != SPI_NOT_INCLUDED
396 && respProposal.getIkeSpiResource() == null) {
397 respProposal.allocateResourceForRemoteIkeSpi(ikeSpiGenerator, remoteAddress);
398 }
399
400 return new Pair(reqProposal, respProposal);
401 } catch (Exception e) {
402 reqProposal.releaseSpiResourceIfExists();
403 respProposal.releaseSpiResourceIfExists();
404 throw e;
405 }
406 }
407
408 /**
409 * Validate the SA Payload pair (request/response) and return the Child SA negotiation result.
410 *
411 * <p>Caller is able to extract the negotiated SA Proposal from the response Proposal and the
412 * IPsec SPI pair generated by both sides.
413 *
414 * <p>In a locally-initiated case all Child SA proposals (from users in initial creation or from
415 * previously negotiated proposal in rekey creation) in the locally generated reqSaPayload have
416 * been validated during building and are unmodified. All Transform combinations in these SA
417 * proposals are valid for Child SA negotiation. It means each request SA proposal MUST have
418 * Encryption algorithms and ESN configurations.
419 *
420 * <p>In a remotely-initiated case the locally generated respSapayload has exactly one SA
421 * proposal. It is validated during building and are unmodified. This proposal has a valid
422 * Transform combination for an Child SA and has at most one value for each Transform type.
423 *
424 * <p>The response Child SA proposal is validated against one of the request SA proposals. It is
425 * guaranteed that for each Transform type that the request proposal has provided options, the
426 * response proposal has exact one Transform value.
427 *
428 * @param reqSaPayload the request payload.
429 * @param respSaPayload the response payload.
430 * @param ipSecSpiGenerator the SPI generator to allocate SPI resource for the Proposal in this
431 * inbound SA Payload.
432 * @param remoteAddress the address of the remote IKE peer.
433 * @return the Pair of selected ChildProposal in the locally generated request and the
434 * ChildProposal in this response.
435 * @throws NoValidProposalChosenException if the response SA Payload cannot be negotiated from
436 * the request SA Payload.
437 * @throws ResourceUnavailableException if too many SPIs are currently allocated for this user.
438 * @throws SpiUnavailableException if the remotely generated SPI is in use.
439 */
440 public static Pair<ChildProposal, ChildProposal> getVerifiedNegotiatedChildProposalPair(
441 IkeSaPayload reqSaPayload,
442 IkeSaPayload respSaPayload,
443 IpSecSpiGenerator ipSecSpiGenerator,
444 InetAddress remoteAddress)
445 throws NoValidProposalChosenException, ResourceUnavailableException,
446 SpiUnavailableException {
447 Pair<Proposal, Proposal> proposalPair =
448 getVerifiedNegotiatedProposalPair(reqSaPayload, respSaPayload);
449 ChildProposal reqProposal = (ChildProposal) proposalPair.first;
450 ChildProposal respProposal = (ChildProposal) proposalPair.second;
451
452 try {
453 // Allocate initiator's inbound SPI as needed for remotely initiated Child SA creation
454 if (reqProposal.getChildSpiResource() == null) {
455 reqProposal.allocateResourceForRemoteChildSpi(ipSecSpiGenerator, remoteAddress);
456 }
457 // Allocate responder's inbound SPI as needed for locally initiated Child SA creation
458 if (respProposal.getChildSpiResource() == null) {
459 respProposal.allocateResourceForRemoteChildSpi(ipSecSpiGenerator, remoteAddress);
460 }
461
462 return new Pair(reqProposal, respProposal);
463 } catch (Exception e) {
464 reqProposal.releaseSpiResourceIfExists();
465 respProposal.releaseSpiResourceIfExists();
466 throw e;
467 }
468 }
469
470 private static Pair<Proposal, Proposal> getVerifiedNegotiatedProposalPair(
471 IkeSaPayload reqSaPayload, IkeSaPayload respSaPayload)
472 throws NoValidProposalChosenException {
473 try {
474 // If negotiated proposal has an unrecognized Transform, throw an exception.
475 Proposal respProposal = respSaPayload.proposalList.get(0);
476 if (respProposal.hasUnrecognizedTransform) {
477 throw new NoValidProposalChosenException(
478 "Negotiated proposal has unrecognized Transform.");
479 }
480
481 // In SA request payload, the first proposal MUST be 1, and subsequent proposals MUST be
482 // one more than the previous proposal. In SA response payload, the negotiated proposal
483 // number MUST match the selected proposal number in SA request Payload.
484 int negotiatedProposalNum = respProposal.number;
485 List<Proposal> reqProposalList = reqSaPayload.proposalList;
486 if (negotiatedProposalNum < 1 || negotiatedProposalNum > reqProposalList.size()) {
487 throw new NoValidProposalChosenException(
488 "Negotiated proposal has invalid proposal number.");
489 }
490
491 Proposal reqProposal = reqProposalList.get(negotiatedProposalNum - 1);
492 if (!respProposal.isNegotiatedFrom(reqProposal)) {
493 throw new NoValidProposalChosenException("Invalid negotiated proposal.");
494 }
495
496 // In a locally-initiated creation, release locally generated SPIs in unselected request
497 // Proposals. In remotely-initiated SA creation, unused proposals do not have SPIs, and
498 // will silently succeed.
499 for (Proposal p : reqProposalList) {
500 if (reqProposal != p) p.releaseSpiResourceIfExists();
501 }
502
503 return new Pair<Proposal, Proposal>(reqProposal, respProposal);
504 } catch (Exception e) {
505 // In a locally-initiated case, release all locally generated SPIs in the SA request
506 // payload.
507 for (Proposal p : reqSaPayload.proposalList) p.releaseSpiResourceIfExists();
508 throw e;
509 }
510 }
511
512 @VisibleForTesting
513 interface TransformDecoder {
514 Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeProtocolException;
515 }
516
517 // TODO: Add another constructor for building outbound message.
518
519 /**
520 * This class represents the common information of an IKE Proposal and a Child Proposal.
521 *
522 * <p>Proposal represents a set contains cryptographic algorithms and key generating materials.
523 * It contains multiple {@link Transform}.
524 *
525 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.1">RFC 7296, Internet Key
526 * Exchange Protocol Version 2 (IKEv2)</a>
527 * <p>Proposals with an unrecognized Protocol ID, containing an unrecognized Transform Type
528 * or lacking a necessary Transform Type shall be ignored when processing a received SA
529 * Payload.
530 */
531 public abstract static class Proposal {
532 private static final byte LAST_PROPOSAL = 0;
533 private static final byte NOT_LAST_PROPOSAL = 2;
534
535 private static final int PROPOSAL_RESERVED_FIELD_LEN = 1;
536 private static final int PROPOSAL_HEADER_LEN = 8;
537
538 @VisibleForTesting
539 static TransformDecoder sTransformDecoder =
540 new TransformDecoder() {
541 @Override
542 public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
543 throws IkeProtocolException {
544 Transform[] transformArray = new Transform[count];
545 for (int i = 0; i < count; i++) {
546 Transform transform = Transform.readFrom(inputBuffer);
547 if (transform.isSupported) {
548 transformArray[i] = transform;
549 }
550 }
551 return transformArray;
552 }
553 };
554
555 public final byte number;
556 /** All supported protocol will fall into {@link ProtocolId} */
557 public final int protocolId;
558
559 public final byte spiSize;
560 public final long spi;
561
562 public final boolean hasUnrecognizedTransform;
563
564 @VisibleForTesting
565 Proposal(
566 byte number,
567 int protocolId,
568 byte spiSize,
569 long spi,
570 boolean hasUnrecognizedTransform) {
571 this.number = number;
572 this.protocolId = protocolId;
573 this.spiSize = spiSize;
574 this.spi = spi;
575 this.hasUnrecognizedTransform = hasUnrecognizedTransform;
576 }
577
578 @VisibleForTesting
579 static Proposal readFrom(ByteBuffer inputBuffer) throws IkeProtocolException {
580 byte isLast = inputBuffer.get();
581 if (isLast != LAST_PROPOSAL && isLast != NOT_LAST_PROPOSAL) {
582 throw new InvalidSyntaxException(
583 "Invalid value of Last Proposal Substructure: " + isLast);
584 }
585 // Skip RESERVED byte
586 inputBuffer.get(new byte[PROPOSAL_RESERVED_FIELD_LEN]);
587
588 int length = Short.toUnsignedInt(inputBuffer.getShort());
589 byte number = inputBuffer.get();
590 int protocolId = Byte.toUnsignedInt(inputBuffer.get());
591
592 byte spiSize = inputBuffer.get();
593 int transformCount = Byte.toUnsignedInt(inputBuffer.get());
594
595 // TODO: Add check: spiSize must be 0 in initial IKE SA negotiation
596 // spiSize should be either 8 for IKE or 4 for IPsec.
597 long spi = SPI_NOT_INCLUDED;
598 switch (spiSize) {
599 case SPI_LEN_NOT_INCLUDED:
600 // No SPI attached for IKE initial exchange.
601 break;
602 case SPI_LEN_IPSEC:
603 spi = Integer.toUnsignedLong(inputBuffer.getInt());
604 break;
605 case SPI_LEN_IKE:
606 spi = inputBuffer.getLong();
607 break;
608 default:
609 throw new InvalidSyntaxException(
610 "Invalid value of spiSize in Proposal Substructure: " + spiSize);
611 }
612
613 Transform[] transformArray =
614 sTransformDecoder.decodeTransforms(transformCount, inputBuffer);
615 // TODO: Validate that sum of all Transforms' lengths plus Proposal header length equals
616 // to Proposal's length.
617
618 List<EncryptionTransform> encryptAlgoList = new LinkedList<>();
619 List<PrfTransform> prfList = new LinkedList<>();
620 List<IntegrityTransform> integAlgoList = new LinkedList<>();
621 List<DhGroupTransform> dhGroupList = new LinkedList<>();
622 List<EsnTransform> esnList = new LinkedList<>();
623
624 boolean hasUnrecognizedTransform = false;
625
626 for (Transform transform : transformArray) {
627 switch (transform.type) {
628 case Transform.TRANSFORM_TYPE_ENCR:
629 encryptAlgoList.add((EncryptionTransform) transform);
630 break;
631 case Transform.TRANSFORM_TYPE_PRF:
632 prfList.add((PrfTransform) transform);
633 break;
634 case Transform.TRANSFORM_TYPE_INTEG:
635 integAlgoList.add((IntegrityTransform) transform);
636 break;
637 case Transform.TRANSFORM_TYPE_DH:
638 dhGroupList.add((DhGroupTransform) transform);
639 break;
640 case Transform.TRANSFORM_TYPE_ESN:
641 esnList.add((EsnTransform) transform);
642 break;
643 default:
644 hasUnrecognizedTransform = true;
645 }
646 }
647
648 if (protocolId == PROTOCOL_ID_IKE) {
649 IkeSaProposal saProposal =
650 new IkeSaProposal(
651 encryptAlgoList.toArray(
652 new EncryptionTransform[encryptAlgoList.size()]),
653 prfList.toArray(new PrfTransform[prfList.size()]),
654 integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
655 dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]));
656 return new IkeProposal(number, spiSize, spi, saProposal, hasUnrecognizedTransform);
657 } else {
658 ChildSaProposal saProposal =
659 new ChildSaProposal(
660 encryptAlgoList.toArray(
661 new EncryptionTransform[encryptAlgoList.size()]),
662 integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
663 dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]),
664 esnList.toArray(new EsnTransform[esnList.size()]));
665 return new ChildProposal(number, spi, saProposal, hasUnrecognizedTransform);
666 }
667 }
668
669 /** Package private */
670 boolean isNegotiatedFrom(Proposal reqProposal) {
671 if (protocolId != reqProposal.protocolId || number != reqProposal.number) {
672 return false;
673 }
674 return getSaProposal().isNegotiatedFrom(reqProposal.getSaProposal());
675 }
676
677 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
678 Transform[] allTransforms = getSaProposal().getAllTransforms();
679 byte isLastIndicator = isLast ? LAST_PROPOSAL : NOT_LAST_PROPOSAL;
680
681 byteBuffer
682 .put(isLastIndicator)
683 .put(new byte[PROPOSAL_RESERVED_FIELD_LEN])
684 .putShort((short) getProposalLength())
685 .put(number)
686 .put((byte) protocolId)
687 .put(spiSize)
688 .put((byte) allTransforms.length);
689
690 switch (spiSize) {
691 case SPI_LEN_NOT_INCLUDED:
692 // No SPI attached for IKE initial exchange.
693 break;
694 case SPI_LEN_IPSEC:
695 byteBuffer.putInt((int) spi);
696 break;
697 case SPI_LEN_IKE:
698 byteBuffer.putLong((long) spi);
699 break;
700 default:
701 throw new IllegalArgumentException(
702 "Invalid value of spiSize in Proposal Substructure: " + spiSize);
703 }
704
705 // Encode all Transform.
706 for (int i = 0; i < allTransforms.length; i++) {
707 // The last transform has the isLast flag set to true.
708 allTransforms[i].encodeToByteBuffer(i == allTransforms.length - 1, byteBuffer);
709 }
710 }
711
712 protected int getProposalLength() {
713 int len = PROPOSAL_HEADER_LEN + spiSize;
714
715 Transform[] allTransforms = getSaProposal().getAllTransforms();
716 for (Transform t : allTransforms) len += t.getTransformLength();
717 return len;
718 }
719
720 @Override
721 @NonNull
722 public String toString() {
723 return "Proposal(" + number + ") " + getSaProposal().toString();
724 }
725
726 /** Package private method for releasing SPI resource in this unselected Proposal. */
727 abstract void releaseSpiResourceIfExists();
728
729 /** Package private method for getting SaProposal */
730 abstract SaProposal getSaProposal();
731 }
732
733 /** This class represents a Proposal for IKE SA negotiation. */
734 public static final class IkeProposal extends Proposal {
735 private IkeSecurityParameterIndex mIkeSpiResource;
736
737 public final IkeSaProposal saProposal;
738
739 /**
740 * Construct IkeProposal from a decoded inbound message for IKE negotiation.
741 *
742 * <p>Package private
743 */
744 IkeProposal(
745 byte number,
746 byte spiSize,
747 long spi,
748 IkeSaProposal saProposal,
749 boolean hasUnrecognizedTransform) {
750 super(number, PROTOCOL_ID_IKE, spiSize, spi, hasUnrecognizedTransform);
751 this.saProposal = saProposal;
752 }
753
754 /** Construct IkeProposal for an outbound message for IKE negotiation. */
755 private IkeProposal(
756 byte number,
757 byte spiSize,
758 IkeSecurityParameterIndex ikeSpiResource,
759 IkeSaProposal saProposal) {
760 super(
761 number,
762 PROTOCOL_ID_IKE,
763 spiSize,
764 ikeSpiResource == null ? SPI_NOT_INCLUDED : ikeSpiResource.getSpi(),
765 false /* hasUnrecognizedTransform */);
766 mIkeSpiResource = ikeSpiResource;
767 this.saProposal = saProposal;
768 }
769
770 /**
771 * Construct IkeProposal for an outbound message for IKE negotiation.
772 *
773 * <p>Package private
774 */
775 @VisibleForTesting
776 static IkeProposal createIkeProposal(
777 byte number,
778 byte spiSize,
779 IkeSaProposal saProposal,
780 IkeSpiGenerator ikeSpiGenerator,
781 InetAddress localAddress)
782 throws IOException {
783 // IKE_INIT uses SPI_LEN_NOT_INCLUDED, while rekeys use SPI_LEN_IKE
784 IkeSecurityParameterIndex spiResource =
785 (spiSize == SPI_LEN_NOT_INCLUDED
786 ? null
787 : ikeSpiGenerator.allocateSpi(localAddress));
788 return new IkeProposal(number, spiSize, spiResource, saProposal);
789 }
790
791 /** Package private method for releasing SPI resource in this unselected Proposal. */
792 void releaseSpiResourceIfExists() {
793 // mIkeSpiResource is null when doing IKE initial exchanges.
794 if (mIkeSpiResource == null) return;
795 mIkeSpiResource.close();
796 mIkeSpiResource = null;
797 }
798
799 /**
800 * Package private method for allocating SPI resource for a validated remotely generated IKE
801 * SA proposal.
802 */
803 void allocateResourceForRemoteIkeSpi(
804 IkeSpiGenerator ikeSpiGenerator, InetAddress remoteAddress) throws IOException {
805 mIkeSpiResource = ikeSpiGenerator.allocateSpi(remoteAddress, spi);
806 }
807
808 @Override
809 public SaProposal getSaProposal() {
810 return saProposal;
811 }
812
813 /**
814 * Get the IKE SPI resource.
815 *
816 * @return the IKE SPI resource or null for IKE initial exchanges.
817 */
818 public IkeSecurityParameterIndex getIkeSpiResource() {
819 return mIkeSpiResource;
820 }
821 }
822
823 /** This class represents a Proposal for Child SA negotiation. */
824 public static final class ChildProposal extends Proposal {
825 private SecurityParameterIndex mChildSpiResource;
826
827 public final ChildSaProposal saProposal;
828
829 /**
830 * Construct ChildProposal from a decoded inbound message for Child SA negotiation.
831 *
832 * <p>Package private
833 */
834 ChildProposal(
835 byte number,
836 long spi,
837 ChildSaProposal saProposal,
838 boolean hasUnrecognizedTransform) {
839 super(
840 number,
841 PROTOCOL_ID_ESP,
842 SPI_LEN_IPSEC,
843 spi,
844 hasUnrecognizedTransform);
845 this.saProposal = saProposal;
846 }
847
848 /** Construct ChildProposal for an outbound message for Child SA negotiation. */
849 private ChildProposal(
850 byte number, SecurityParameterIndex childSpiResource, ChildSaProposal saProposal) {
851 super(
852 number,
853 PROTOCOL_ID_ESP,
854 SPI_LEN_IPSEC,
855 (long) childSpiResource.getSpi(),
856 false /* hasUnrecognizedTransform */);
857 mChildSpiResource = childSpiResource;
858 this.saProposal = saProposal;
859 }
860
861 /**
862 * Construct ChildProposal for an outbound message for Child SA negotiation.
863 *
864 * <p>Package private
865 */
866 @VisibleForTesting
867 static ChildProposal createChildProposal(
868 byte number,
869 ChildSaProposal saProposal,
870 IpSecSpiGenerator ipSecSpiGenerator,
871 InetAddress localAddress)
872 throws SpiUnavailableException, ResourceUnavailableException {
873 return new ChildProposal(
874 number, ipSecSpiGenerator.allocateSpi(localAddress), saProposal);
875 }
876
877 /** Package private method for releasing SPI resource in this unselected Proposal. */
878 void releaseSpiResourceIfExists() {
879 if (mChildSpiResource == null) return;
880
881 mChildSpiResource.close();
882 mChildSpiResource = null;
883 }
884
885 /**
886 * Package private method for allocating SPI resource for a validated remotely generated
887 * Child SA proposal.
888 */
889 void allocateResourceForRemoteChildSpi(
890 IpSecSpiGenerator ipSecSpiGenerator, InetAddress remoteAddress)
891 throws ResourceUnavailableException, SpiUnavailableException {
892 mChildSpiResource = ipSecSpiGenerator.allocateSpi(remoteAddress, (int) spi);
893 }
894
895 @Override
896 public SaProposal getSaProposal() {
897 return saProposal;
898 }
899
900 /**
901 * Get the IPsec SPI resource.
902 *
903 * @return the IPsec SPI resource.
904 */
905 public SecurityParameterIndex getChildSpiResource() {
906 return mChildSpiResource;
907 }
908 }
909
910 @VisibleForTesting
911 interface AttributeDecoder {
912 List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
913 throws IkeProtocolException;
914 }
915
916 /**
917 * Transform is an abstract base class that represents the common information for all Transform
918 * types. It may contain one or more {@link Attribute}.
919 *
920 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
921 * Exchange Protocol Version 2 (IKEv2)</a>
922 * <p>Transforms with unrecognized Transform ID or containing unrecognized Attribute Type
923 * shall be ignored when processing received SA payload.
924 */
925 public abstract static class Transform {
926
927 @Retention(RetentionPolicy.SOURCE)
928 @IntDef({
929 TRANSFORM_TYPE_ENCR,
930 TRANSFORM_TYPE_PRF,
931 TRANSFORM_TYPE_INTEG,
932 TRANSFORM_TYPE_DH,
933 TRANSFORM_TYPE_ESN
934 })
935 public @interface TransformType {}
936
937 public static final int TRANSFORM_TYPE_ENCR = 1;
938 public static final int TRANSFORM_TYPE_PRF = 2;
939 public static final int TRANSFORM_TYPE_INTEG = 3;
940 public static final int TRANSFORM_TYPE_DH = 4;
941 public static final int TRANSFORM_TYPE_ESN = 5;
942
943 private static final byte LAST_TRANSFORM = 0;
944 private static final byte NOT_LAST_TRANSFORM = 3;
945
946 // Length of reserved field of a Transform.
947 private static final int TRANSFORM_RESERVED_FIELD_LEN = 1;
948
949 // Length of the Transform that with no Attribute.
950 protected static final int BASIC_TRANSFORM_LEN = 8;
951
952 // TODO: Add constants for supported algorithms
953
954 @VisibleForTesting
955 static AttributeDecoder sAttributeDecoder =
956 new AttributeDecoder() {
957 public List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
958 throws IkeProtocolException {
959 List<Attribute> list = new LinkedList<>();
960 int parsedLength = BASIC_TRANSFORM_LEN;
961 while (parsedLength < length) {
962 Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
963 parsedLength += pair.second;
964 list.add(pair.first);
965 }
966 // TODO: Validate that parsedLength equals to length.
967 return list;
968 }
969 };
970
971 // Only supported type falls into {@link TransformType}
972 public final int type;
973 public final int id;
974 public final boolean isSupported;
975
976 /** Construct an instance of Transform for building an outbound packet. */
977 protected Transform(int type, int id) {
978 this.type = type;
979 this.id = id;
980 if (!isSupportedTransformId(id)) {
981 throw new IllegalArgumentException(
982 "Unsupported " + getTransformTypeString() + " Algorithm ID: " + id);
983 }
984 this.isSupported = true;
985 }
986
987 /** Construct an instance of Transform for decoding an inbound packet. */
988 protected Transform(int type, int id, List<Attribute> attributeList) {
989 this.type = type;
990 this.id = id;
991 this.isSupported =
992 isSupportedTransformId(id) && !hasUnrecognizedAttribute(attributeList);
993 }
994
995 @VisibleForTesting
996 static Transform readFrom(ByteBuffer inputBuffer) throws IkeProtocolException {
997 byte isLast = inputBuffer.get();
998 if (isLast != LAST_TRANSFORM && isLast != NOT_LAST_TRANSFORM) {
999 throw new InvalidSyntaxException(
1000 "Invalid value of Last Transform Substructure: " + isLast);
1001 }
1002
1003 // Skip RESERVED byte
1004 inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
1005
1006 int length = Short.toUnsignedInt(inputBuffer.getShort());
1007 int type = Byte.toUnsignedInt(inputBuffer.get());
1008
1009 // Skip RESERVED byte
1010 inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
1011
1012 int id = Short.toUnsignedInt(inputBuffer.getShort());
1013
1014 // Decode attributes
1015 List<Attribute> attributeList = sAttributeDecoder.decodeAttributes(length, inputBuffer);
1016
1017 validateAttributeUniqueness(attributeList);
1018
1019 switch (type) {
1020 case TRANSFORM_TYPE_ENCR:
1021 return new EncryptionTransform(id, attributeList);
1022 case TRANSFORM_TYPE_PRF:
1023 return new PrfTransform(id, attributeList);
1024 case TRANSFORM_TYPE_INTEG:
1025 return new IntegrityTransform(id, attributeList);
1026 case TRANSFORM_TYPE_DH:
1027 return new DhGroupTransform(id, attributeList);
1028 case TRANSFORM_TYPE_ESN:
1029 return new EsnTransform(id, attributeList);
1030 default:
1031 return new UnrecognizedTransform(type, id, attributeList);
1032 }
1033 }
1034
1035 // Throw InvalidSyntaxException if there are multiple Attributes of the same type
1036 private static void validateAttributeUniqueness(List<Attribute> attributeList)
1037 throws IkeProtocolException {
1038 Set<Integer> foundTypes = new ArraySet<>();
1039 for (Attribute attr : attributeList) {
1040 if (!foundTypes.add(attr.type)) {
1041 throw new InvalidSyntaxException(
1042 "There are multiple Attributes of the same type. ");
1043 }
1044 }
1045 }
1046
1047 // Check if there is Attribute with unrecognized type.
1048 protected abstract boolean hasUnrecognizedAttribute(List<Attribute> attributeList);
1049
1050 // Check if this Transform ID is supported.
1051 protected abstract boolean isSupportedTransformId(int id);
1052
1053 // Encode Transform to a ByteBuffer.
1054 protected abstract void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer);
1055
1056 // Get entire Transform length.
1057 protected abstract int getTransformLength();
1058
1059 protected void encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1060 byte isLastIndicator = isLast ? LAST_TRANSFORM : NOT_LAST_TRANSFORM;
1061 byteBuffer
1062 .put(isLastIndicator)
1063 .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
1064 .putShort((short) getTransformLength())
1065 .put((byte) type)
1066 .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
1067 .putShort((short) id);
1068 }
1069
1070 /**
1071 * Get Tranform Type as a String.
1072 *
1073 * @return Tranform Type as a String.
1074 */
1075 public abstract String getTransformTypeString();
1076
1077 // TODO: Add abstract getTransformIdString() to return specific algorithm/dhGroup name
1078 }
1079
1080 /**
1081 * EncryptionTransform represents an encryption algorithm. It may contain an Atrribute
1082 * specifying the key length.
1083 *
1084 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1085 * Exchange Protocol Version 2 (IKEv2)</a>
1086 */
1087 public static final class EncryptionTransform extends Transform {
1088 public static final int KEY_LEN_UNSPECIFIED = 0;
1089
1090 // When using encryption algorithm with variable-length keys, mSpecifiedKeyLength MUST be
1091 // set and a KeyLengthAttribute MUST be attached. Otherwise, mSpecifiedKeyLength MUST NOT be
1092 // set and KeyLengthAttribute MUST NOT be attached.
1093 private final int mSpecifiedKeyLength;
1094
1095 /**
1096 * Contruct an instance of EncryptionTransform with fixed key length for building an
1097 * outbound packet.
1098 *
1099 * @param id the IKE standard Transform ID.
1100 */
1101 public EncryptionTransform(@EncryptionAlgorithm int id) {
1102 this(id, KEY_LEN_UNSPECIFIED);
1103 }
1104
1105 /**
1106 * Contruct an instance of EncryptionTransform with variable key length for building an
1107 * outbound packet.
1108 *
1109 * @param id the IKE standard Transform ID.
1110 * @param specifiedKeyLength the specified key length of this encryption algorithm.
1111 */
1112 public EncryptionTransform(@EncryptionAlgorithm int id, int specifiedKeyLength) {
1113 super(Transform.TRANSFORM_TYPE_ENCR, id);
1114
1115 mSpecifiedKeyLength = specifiedKeyLength;
1116 try {
1117 validateKeyLength();
1118 } catch (InvalidSyntaxException e) {
1119 throw new IllegalArgumentException(e);
1120 }
1121 }
1122
1123 /**
1124 * Contruct an instance of EncryptionTransform for decoding an inbound packet.
1125 *
1126 * @param id the IKE standard Transform ID.
1127 * @param attributeList the decoded list of Attribute.
1128 * @throws InvalidSyntaxException for syntax error.
1129 */
1130 protected EncryptionTransform(int id, List<Attribute> attributeList)
1131 throws InvalidSyntaxException {
1132 super(Transform.TRANSFORM_TYPE_ENCR, id, attributeList);
1133 if (!isSupported) {
1134 mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
1135 } else {
1136 if (attributeList.size() == 0) {
1137 mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
1138 } else {
1139 KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
1140 mSpecifiedKeyLength = attr.keyLength;
1141 }
1142 validateKeyLength();
1143 }
1144 }
1145
1146 /**
1147 * Get the specified key length.
1148 *
1149 * @return the specified key length.
1150 */
1151 public int getSpecifiedKeyLength() {
1152 return mSpecifiedKeyLength;
1153 }
1154
1155 @Override
1156 public int hashCode() {
1157 return Objects.hash(type, id, mSpecifiedKeyLength);
1158 }
1159
1160 @Override
1161 public boolean equals(Object o) {
1162 if (!(o instanceof EncryptionTransform)) return false;
1163
1164 EncryptionTransform other = (EncryptionTransform) o;
1165 return (type == other.type
1166 && id == other.id
1167 && mSpecifiedKeyLength == other.mSpecifiedKeyLength);
1168 }
1169
1170 @Override
1171 protected boolean isSupportedTransformId(int id) {
1172 return SaProposal.isSupportedEncryptionAlgorithm(id);
1173 }
1174
1175 @Override
1176 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1177 for (Attribute attr : attributeList) {
1178 if (attr instanceof UnrecognizedAttribute) {
1179 return true;
1180 }
1181 }
1182 return false;
1183 }
1184
1185 private KeyLengthAttribute getKeyLengthAttribute(List<Attribute> attributeList) {
1186 for (Attribute attr : attributeList) {
1187 if (attr.type == Attribute.ATTRIBUTE_TYPE_KEY_LENGTH) {
1188 return (KeyLengthAttribute) attr;
1189 }
1190 }
1191 throw new IllegalArgumentException("Cannot find Attribute with Key Length type");
1192 }
1193
1194 private void validateKeyLength() throws InvalidSyntaxException {
1195 switch (id) {
1196 case SaProposal.ENCRYPTION_ALGORITHM_3DES:
1197 if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1198 throw new InvalidSyntaxException(
1199 "Must not set Key Length value for this "
1200 + getTransformTypeString()
1201 + " Algorithm ID: "
1202 + id);
1203 }
1204 return;
1205 case SaProposal.ENCRYPTION_ALGORITHM_AES_CBC:
1206 /* fall through */
1207 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8:
1208 /* fall through */
1209 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
1210 /* fall through */
1211 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
1212 if (mSpecifiedKeyLength == KEY_LEN_UNSPECIFIED) {
1213 throw new InvalidSyntaxException(
1214 "Must set Key Length value for this "
1215 + getTransformTypeString()
1216 + " Algorithm ID: "
1217 + id);
1218 }
1219 if (mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_128
1220 && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_192
1221 && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_256) {
1222 throw new InvalidSyntaxException(
1223 "Invalid key length for this "
1224 + getTransformTypeString()
1225 + " Algorithm ID: "
1226 + id);
1227 }
1228 return;
1229 default:
1230 // Won't hit here.
1231 throw new IllegalArgumentException(
1232 "Unrecognized Encryption Algorithm ID: " + id);
1233 }
1234 }
1235
1236 @Override
1237 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1238 encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1239
1240 if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1241 new KeyLengthAttribute(mSpecifiedKeyLength).encodeToByteBuffer(byteBuffer);
1242 }
1243 }
1244
1245 @Override
1246 protected int getTransformLength() {
1247 int len = BASIC_TRANSFORM_LEN;
1248
1249 if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1250 len += new KeyLengthAttribute(mSpecifiedKeyLength).getAttributeLength();
1251 }
1252
1253 return len;
1254 }
1255
1256 @Override
1257 public String getTransformTypeString() {
1258 return "Encryption Algorithm";
1259 }
1260
1261 @Override
1262 @NonNull
1263 public String toString() {
1264 return SaProposal.getEncryptionAlgorithmString(id)
1265 + "("
1266 + getSpecifiedKeyLength()
1267 + ")";
1268 }
1269 }
1270
1271 /**
1272 * PrfTransform represents an pseudorandom function.
1273 *
1274 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1275 * Exchange Protocol Version 2 (IKEv2)</a>
1276 */
1277 public static final class PrfTransform extends Transform {
1278 /**
1279 * Contruct an instance of PrfTransform for building an outbound packet.
1280 *
1281 * @param id the IKE standard Transform ID.
1282 */
1283 public PrfTransform(@PseudorandomFunction int id) {
1284 super(Transform.TRANSFORM_TYPE_PRF, id);
1285 }
1286
1287 /**
1288 * Contruct an instance of PrfTransform for decoding an inbound packet.
1289 *
1290 * @param id the IKE standard Transform ID.
1291 * @param attributeList the decoded list of Attribute.
1292 * @throws InvalidSyntaxException for syntax error.
1293 */
1294 protected PrfTransform(int id, List<Attribute> attributeList)
1295 throws InvalidSyntaxException {
1296 super(Transform.TRANSFORM_TYPE_PRF, id, attributeList);
1297 }
1298
1299 @Override
1300 public int hashCode() {
1301 return Objects.hash(type, id);
1302 }
1303
1304 @Override
1305 public boolean equals(Object o) {
1306 if (!(o instanceof PrfTransform)) return false;
1307
1308 PrfTransform other = (PrfTransform) o;
1309 return (type == other.type && id == other.id);
1310 }
1311
1312 @Override
1313 protected boolean isSupportedTransformId(int id) {
1314 return SaProposal.isSupportedPseudorandomFunction(id);
1315 }
1316
1317 @Override
1318 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1319 return !attributeList.isEmpty();
1320 }
1321
1322 @Override
1323 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1324 encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1325 }
1326
1327 @Override
1328 protected int getTransformLength() {
1329 return BASIC_TRANSFORM_LEN;
1330 }
1331
1332 @Override
1333 public String getTransformTypeString() {
1334 return "Pseudorandom Function";
1335 }
1336
1337 @Override
1338 @NonNull
1339 public String toString() {
1340 return SaProposal.getPseudorandomFunctionString(id);
1341 }
1342 }
1343
1344 /**
1345 * IntegrityTransform represents an integrity algorithm.
1346 *
1347 * <p>Proposing integrity algorithm for ESP SA is optional. Omitting the IntegrityTransform is
1348 * equivalent to including it with a value of NONE. When multiple integrity algorithms are
1349 * provided, choosing any of them are acceptable.
1350 *
1351 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1352 * Exchange Protocol Version 2 (IKEv2)</a>
1353 */
1354 public static final class IntegrityTransform extends Transform {
1355 /**
1356 * Contruct an instance of IntegrityTransform for building an outbound packet.
1357 *
1358 * @param id the IKE standard Transform ID.
1359 */
1360 public IntegrityTransform(@IntegrityAlgorithm int id) {
1361 super(Transform.TRANSFORM_TYPE_INTEG, id);
1362 }
1363
1364 /**
1365 * Contruct an instance of IntegrityTransform for decoding an inbound packet.
1366 *
1367 * @param id the IKE standard Transform ID.
1368 * @param attributeList the decoded list of Attribute.
1369 * @throws InvalidSyntaxException for syntax error.
1370 */
1371 protected IntegrityTransform(int id, List<Attribute> attributeList)
1372 throws InvalidSyntaxException {
1373 super(Transform.TRANSFORM_TYPE_INTEG, id, attributeList);
1374 }
1375
1376 @Override
1377 public int hashCode() {
1378 return Objects.hash(type, id);
1379 }
1380
1381 @Override
1382 public boolean equals(Object o) {
1383 if (!(o instanceof IntegrityTransform)) return false;
1384
1385 IntegrityTransform other = (IntegrityTransform) o;
1386 return (type == other.type && id == other.id);
1387 }
1388
1389 @Override
1390 protected boolean isSupportedTransformId(int id) {
1391 return SaProposal.isSupportedIntegrityAlgorithm(id);
1392 }
1393
1394 @Override
1395 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1396 return !attributeList.isEmpty();
1397 }
1398
1399 @Override
1400 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1401 encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1402 }
1403
1404 @Override
1405 protected int getTransformLength() {
1406 return BASIC_TRANSFORM_LEN;
1407 }
1408
1409 @Override
1410 public String getTransformTypeString() {
1411 return "Integrity Algorithm";
1412 }
1413
1414 @Override
1415 @NonNull
1416 public String toString() {
1417 return SaProposal.getIntegrityAlgorithmString(id);
1418 }
1419 }
1420
1421 /**
1422 * DhGroupTransform represents a Diffie-Hellman Group
1423 *
1424 * <p>Proposing DH group for non-first Child SA is optional. Omitting the DhGroupTransform is
1425 * equivalent to including it with a value of NONE. When multiple DH groups are provided,
1426 * choosing any of them are acceptable.
1427 *
1428 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1429 * Exchange Protocol Version 2 (IKEv2)</a>
1430 */
1431 public static final class DhGroupTransform extends Transform {
1432 /**
1433 * Contruct an instance of DhGroupTransform for building an outbound packet.
1434 *
1435 * @param id the IKE standard Transform ID.
1436 */
1437 public DhGroupTransform(@DhGroup int id) {
1438 super(Transform.TRANSFORM_TYPE_DH, id);
1439 }
1440
1441 /**
1442 * Contruct an instance of DhGroupTransform for decoding an inbound packet.
1443 *
1444 * @param id the IKE standard Transform ID.
1445 * @param attributeList the decoded list of Attribute.
1446 * @throws InvalidSyntaxException for syntax error.
1447 */
1448 protected DhGroupTransform(int id, List<Attribute> attributeList)
1449 throws InvalidSyntaxException {
1450 super(Transform.TRANSFORM_TYPE_DH, id, attributeList);
1451 }
1452
1453 @Override
1454 public int hashCode() {
1455 return Objects.hash(type, id);
1456 }
1457
1458 @Override
1459 public boolean equals(Object o) {
1460 if (!(o instanceof DhGroupTransform)) return false;
1461
1462 DhGroupTransform other = (DhGroupTransform) o;
1463 return (type == other.type && id == other.id);
1464 }
1465
1466 @Override
1467 protected boolean isSupportedTransformId(int id) {
1468 return SaProposal.isSupportedDhGroup(id);
1469 }
1470
1471 @Override
1472 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1473 return !attributeList.isEmpty();
1474 }
1475
1476 @Override
1477 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1478 encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1479 }
1480
1481 @Override
1482 protected int getTransformLength() {
1483 return BASIC_TRANSFORM_LEN;
1484 }
1485
1486 @Override
1487 public String getTransformTypeString() {
1488 return "Diffie-Hellman Group";
1489 }
1490
1491 @Override
1492 @NonNull
1493 public String toString() {
1494 return SaProposal.getDhGroupString(id);
1495 }
1496 }
1497
1498 /**
1499 * EsnTransform represents ESN policy that indicates if IPsec SA uses tranditional 32-bit
1500 * sequence numbers or extended(64-bit) sequence numbers.
1501 *
1502 * <p>Currently IKE library only supports negotiating IPsec SA that do not use extended sequence
1503 * numbers. The Transform ID of EsnTransform in outbound packets is not user configurable.
1504 *
1505 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1506 * Exchange Protocol Version 2 (IKEv2)</a>
1507 */
1508 public static final class EsnTransform extends Transform {
1509 @Retention(RetentionPolicy.SOURCE)
1510 @IntDef({ESN_POLICY_NO_EXTENDED, ESN_POLICY_EXTENDED})
1511 public @interface EsnPolicy {}
1512
1513 public static final int ESN_POLICY_NO_EXTENDED = 0;
1514 public static final int ESN_POLICY_EXTENDED = 1;
1515
1516 /**
1517 * Construct an instance of EsnTransform indicates using no-extended sequence numbers for
1518 * building an outbound packet.
1519 */
1520 public EsnTransform() {
1521 super(Transform.TRANSFORM_TYPE_ESN, ESN_POLICY_NO_EXTENDED);
1522 }
1523
1524 /**
1525 * Contruct an instance of EsnTransform for decoding an inbound packet.
1526 *
1527 * @param id the IKE standard Transform ID.
1528 * @param attributeList the decoded list of Attribute.
1529 * @throws InvalidSyntaxException for syntax error.
1530 */
1531 protected EsnTransform(int id, List<Attribute> attributeList)
1532 throws InvalidSyntaxException {
1533 super(Transform.TRANSFORM_TYPE_ESN, id, attributeList);
1534 }
1535
1536 @Override
1537 public int hashCode() {
1538 return Objects.hash(type, id);
1539 }
1540
1541 @Override
1542 public boolean equals(Object o) {
1543 if (!(o instanceof EsnTransform)) return false;
1544
1545 EsnTransform other = (EsnTransform) o;
1546 return (type == other.type && id == other.id);
1547 }
1548
1549 @Override
1550 protected boolean isSupportedTransformId(int id) {
1551 return (id == ESN_POLICY_NO_EXTENDED || id == ESN_POLICY_EXTENDED);
1552 }
1553
1554 @Override
1555 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1556 return !attributeList.isEmpty();
1557 }
1558
1559 @Override
1560 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1561 encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1562 }
1563
1564 @Override
1565 protected int getTransformLength() {
1566 return BASIC_TRANSFORM_LEN;
1567 }
1568
1569 @Override
1570 public String getTransformTypeString() {
1571 return "Extended Sequence Numbers";
1572 }
1573
1574 @Override
1575 @NonNull
1576 public String toString() {
1577 if (id == ESN_POLICY_NO_EXTENDED) {
1578 return "ESN_No_Extended";
1579 }
1580 return "ESN_Extended";
1581 }
1582 }
1583
1584 /**
1585 * UnrecognizedTransform represents a Transform with unrecognized Transform Type.
1586 *
1587 * <p>Proposals containing an UnrecognizedTransform should be ignored.
1588 */
1589 protected static final class UnrecognizedTransform extends Transform {
1590 protected UnrecognizedTransform(int type, int id, List<Attribute> attributeList) {
1591 super(type, id, attributeList);
1592 }
1593
1594 @Override
1595 protected boolean isSupportedTransformId(int id) {
1596 return false;
1597 }
1598
1599 @Override
1600 protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1601 return !attributeList.isEmpty();
1602 }
1603
1604 @Override
1605 protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1606 throw new UnsupportedOperationException(
1607 "It is not supported to encode a Transform with" + getTransformTypeString());
1608 }
1609
1610 @Override
1611 protected int getTransformLength() {
1612 throw new UnsupportedOperationException(
1613 "It is not supported to get length of a Transform with "
1614 + getTransformTypeString());
1615 }
1616
1617 /**
1618 * Return Tranform Type of Unrecognized Transform as a String.
1619 *
1620 * @return Tranform Type of Unrecognized Transform as a String.
1621 */
1622 @Override
1623 public String getTransformTypeString() {
1624 return "Unrecognized Transform Type.";
1625 }
1626 }
1627
1628 /**
1629 * Attribute is an abtract base class for completing the specification of some {@link
1630 * Transform}.
1631 *
1632 * <p>Attribute is either in Type/Value format or Type/Length/Value format. For TV format,
1633 * Attribute length is always 4 bytes containing value for 2 bytes. While for TLV format,
1634 * Attribute length is determined by length field.
1635 *
1636 * <p>Currently only Key Length type is supported
1637 *
1638 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.5">RFC 7296, Internet Key
1639 * Exchange Protocol Version 2 (IKEv2)</a>
1640 */
1641 public abstract static class Attribute {
1642 @Retention(RetentionPolicy.SOURCE)
1643 @IntDef({ATTRIBUTE_TYPE_KEY_LENGTH})
1644 public @interface AttributeType {}
1645
1646 // Support only one Attribute type: Key Length. Should use Type/Value format.
1647 public static final int ATTRIBUTE_TYPE_KEY_LENGTH = 14;
1648
1649 // Mask to extract the left most AF bit to indicate Attribute Format.
1650 private static final int ATTRIBUTE_FORMAT_MASK = 0x8000;
1651 // Mask to extract 15 bits after the AF bit to indicate Attribute Type.
1652 private static final int ATTRIBUTE_TYPE_MASK = 0x7fff;
1653
1654 // Package private mask to indicate that Type-Value (TV) Attribute Format is used.
1655 static final int ATTRIBUTE_FORMAT_TV = ATTRIBUTE_FORMAT_MASK;
1656
1657 // Package private
1658 static final int TV_ATTRIBUTE_VALUE_LEN = 2;
1659 static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
1660 static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
1661
1662 // Only Key Length type belongs to AttributeType
1663 public final int type;
1664
1665 /** Construct an instance of an Attribute when decoding message. */
1666 protected Attribute(int type) {
1667 this.type = type;
1668 }
1669
1670 @VisibleForTesting
1671 static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer)
1672 throws IkeProtocolException {
1673 short formatAndType = inputBuffer.getShort();
1674 int format = formatAndType & ATTRIBUTE_FORMAT_MASK;
1675 int type = formatAndType & ATTRIBUTE_TYPE_MASK;
1676
1677 int length = 0;
1678 byte[] value = new byte[0];
1679 if (format == ATTRIBUTE_FORMAT_TV) {
1680 // Type/Value format
1681 length = TV_ATTRIBUTE_TOTAL_LEN;
1682 value = new byte[TV_ATTRIBUTE_VALUE_LEN];
1683 } else {
1684 // Type/Length/Value format
1685 if (type == ATTRIBUTE_TYPE_KEY_LENGTH) {
1686 throw new InvalidSyntaxException("Wrong format in Transform Attribute");
1687 }
1688
1689 length = Short.toUnsignedInt(inputBuffer.getShort());
1690 int valueLen = length - TVL_ATTRIBUTE_HEADER_LEN;
1691 // IkeMessage will catch exception if valueLen is negative.
1692 value = new byte[valueLen];
1693 }
1694
1695 inputBuffer.get(value);
1696
1697 switch (type) {
1698 case ATTRIBUTE_TYPE_KEY_LENGTH:
1699 return new Pair(new KeyLengthAttribute(value), length);
1700 default:
1701 return new Pair(new UnrecognizedAttribute(type, value), length);
1702 }
1703 }
1704
1705 // Encode Attribute to a ByteBuffer.
1706 protected abstract void encodeToByteBuffer(ByteBuffer byteBuffer);
1707
1708 // Get entire Attribute length.
1709 protected abstract int getAttributeLength();
1710 }
1711
1712 /** KeyLengthAttribute represents a Key Length type Attribute */
1713 public static final class KeyLengthAttribute extends Attribute {
1714 public final int keyLength;
1715
1716 protected KeyLengthAttribute(byte[] value) {
1717 this(Short.toUnsignedInt(ByteBuffer.wrap(value).getShort()));
1718 }
1719
1720 protected KeyLengthAttribute(int keyLength) {
1721 super(ATTRIBUTE_TYPE_KEY_LENGTH);
1722 this.keyLength = keyLength;
1723 }
1724
1725 @Override
1726 protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
1727 byteBuffer
1728 .putShort((short) (ATTRIBUTE_FORMAT_TV | ATTRIBUTE_TYPE_KEY_LENGTH))
1729 .putShort((short) keyLength);
1730 }
1731
1732 @Override
1733 protected int getAttributeLength() {
1734 return TV_ATTRIBUTE_TOTAL_LEN;
1735 }
1736 }
1737
1738 /**
1739 * UnrecognizedAttribute represents a Attribute with unrecoginzed Attribute Type.
1740 *
1741 * <p>Transforms containing UnrecognizedAttribute should be ignored.
1742 */
1743 protected static final class UnrecognizedAttribute extends Attribute {
1744 protected UnrecognizedAttribute(int type, byte[] value) {
1745 super(type);
1746 }
1747
1748 @Override
1749 protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
1750 throw new UnsupportedOperationException(
1751 "It is not supported to encode an unrecognized Attribute.");
1752 }
1753
1754 @Override
1755 protected int getAttributeLength() {
1756 throw new UnsupportedOperationException(
1757 "It is not supported to get length of an unrecognized Attribute.");
1758 }
1759 }
1760
1761 /**
1762 * Encode SA payload to ByteBUffer.
1763 *
1764 * @param nextPayload type of payload that follows this payload.
1765 * @param byteBuffer destination ByteBuffer that stores encoded payload.
1766 */
1767 @Override
1768 protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
1769 encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
1770
1771 for (int i = 0; i < proposalList.size(); i++) {
1772 // The last proposal has the isLast flag set to true.
1773 proposalList.get(i).encodeToByteBuffer(i == proposalList.size() - 1, byteBuffer);
1774 }
1775 }
1776
1777 /**
1778 * Get entire payload length.
1779 *
1780 * @return entire payload length.
1781 */
1782 @Override
1783 protected int getPayloadLength() {
1784 int len = GENERIC_HEADER_LENGTH;
1785
1786 for (Proposal p : proposalList) len += p.getProposalLength();
1787
1788 return len;
1789 }
1790
1791 /**
1792 * Return the payload type as a String.
1793 *
1794 * @return the payload type as a String.
1795 */
1796 @Override
1797 public String getTypeString() {
1798 return "SA";
1799 }
1800
1801 @Override
1802 @NonNull
1803 public String toString() {
1804 StringBuilder sb = new StringBuilder();
1805 if (isSaResponse) {
1806 sb.append("SA Response: ");
1807 } else {
1808 sb.append("SA Request: ");
1809 }
1810
1811 int len = proposalList.size();
1812 for (int i = 0; i < len; i++) {
1813 sb.append(proposalList.get(i).toString());
1814 if (i < len - 1) sb.append(", ");
1815 }
1816
1817 return sb.toString();
1818 }
1819}