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