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.util.ArraySet;
35 import android.util.Pair;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
39 import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
40 import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
41 import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
42 import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
43 
44 import java.io.IOException;
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.net.InetAddress;
48 import java.nio.ByteBuffer;
49 import java.util.ArrayList;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Objects;
53 import 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  */
61 public 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      */
IkeSaPayload(boolean critical, boolean isResp, byte[] payloadBody)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
IkeSaPayload( boolean isResp, byte spiSize, IkeSaProposal[] saProposals, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)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
IkeSaPayload( boolean isResp, byte spiSize, byte proposalNumber, IkeSaProposal saProposal, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)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 
IkeSaPayload(boolean isResp, byte spiSize, InetAddress localAddress)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
IkeSaPayload( ChildSaProposal[] saProposals, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)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
IkeSaPayload( byte proposalNumber, ChildSaProposal saProposal, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)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. */
IkeSaPayload( boolean isResp, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)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      */
createInitialIkeSaPayload(IkeSaProposal[] saProposals)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      */
createRekeyIkeSaRequestPayload( IkeSaProposal[] saProposals, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)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      */
createRekeyIkeSaResponsePayload( byte respProposalNumber, IkeSaProposal saProposal, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)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      */
createChildSaRequestPayload( ChildSaProposal[] saProposals, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)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      */
createChildSaResponsePayload( byte respProposalNumber, ChildSaProposal saProposal, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)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      */
getNegotiatedProposalNumber(SaProposal respProposal)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      */
getVerifiedNegotiatedIkeProposalPair( IkeSaPayload reqSaPayload, IkeSaPayload respSaPayload, IkeSpiGenerator ikeSpiGenerator, InetAddress remoteAddress)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      */
getVerifiedNegotiatedChildProposalPair( IkeSaPayload reqSaPayload, IkeSaPayload respSaPayload, IpSecSpiGenerator ipSecSpiGenerator, InetAddress remoteAddress)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 
getVerifiedNegotiatedProposalPair( IkeSaPayload reqSaPayload, IkeSaPayload respSaPayload)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 {
decodeTransforms(int count, ByteBuffer inputBuffer)514         Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeProtocolException;
515     }
516 
517     /**
518      * Release IPsec SPI resources in the outbound Create Child request
519      *
520      * <p>This method is usually called when an IKE library fails to receive a Create Child response
521      * before it is terminated. It is also safe to call after the Create Child exchange has
522      * succeeded because the newly created IpSecTransform pair will hold the IPsec SPI resource.
523      */
releaseChildSpiResourcesIfExists()524     public void releaseChildSpiResourcesIfExists() {
525         for (Proposal proposal : proposalList) {
526             if (proposal instanceof ChildProposal) {
527                 proposal.releaseSpiResourceIfExists();
528             }
529         }
530     }
531 
532     /**
533      * This class represents the common information of an IKE Proposal and a Child Proposal.
534      *
535      * <p>Proposal represents a set contains cryptographic algorithms and key generating materials.
536      * It contains multiple {@link Transform}.
537      *
538      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.1">RFC 7296, Internet Key
539      *     Exchange Protocol Version 2 (IKEv2)</a>
540      *     <p>Proposals with an unrecognized Protocol ID, containing an unrecognized Transform Type
541      *     or lacking a necessary Transform Type shall be ignored when processing a received SA
542      *     Payload.
543      */
544     public abstract static class Proposal {
545         private static final byte LAST_PROPOSAL = 0;
546         private static final byte NOT_LAST_PROPOSAL = 2;
547 
548         private static final int PROPOSAL_RESERVED_FIELD_LEN = 1;
549         private static final int PROPOSAL_HEADER_LEN = 8;
550 
551         private static TransformDecoder sTransformDecoder = new TransformDecoderImpl();
552 
553         public final byte number;
554         /** All supported protocol will fall into {@link ProtocolId} */
555         public final int protocolId;
556 
557         public final byte spiSize;
558         public final long spi;
559 
560         public final boolean hasUnrecognizedTransform;
561 
562         @VisibleForTesting
Proposal( byte number, int protocolId, byte spiSize, long spi, boolean hasUnrecognizedTransform)563         Proposal(
564                 byte number,
565                 int protocolId,
566                 byte spiSize,
567                 long spi,
568                 boolean hasUnrecognizedTransform) {
569             this.number = number;
570             this.protocolId = protocolId;
571             this.spiSize = spiSize;
572             this.spi = spi;
573             this.hasUnrecognizedTransform = hasUnrecognizedTransform;
574         }
575 
576         @VisibleForTesting
readFrom(ByteBuffer inputBuffer)577         static Proposal readFrom(ByteBuffer inputBuffer) throws IkeProtocolException {
578             byte isLast = inputBuffer.get();
579             if (isLast != LAST_PROPOSAL && isLast != NOT_LAST_PROPOSAL) {
580                 throw new InvalidSyntaxException(
581                         "Invalid value of Last Proposal Substructure: " + isLast);
582             }
583             // Skip RESERVED byte
584             inputBuffer.get(new byte[PROPOSAL_RESERVED_FIELD_LEN]);
585 
586             int length = Short.toUnsignedInt(inputBuffer.getShort());
587             byte number = inputBuffer.get();
588             int protocolId = Byte.toUnsignedInt(inputBuffer.get());
589 
590             byte spiSize = inputBuffer.get();
591             int transformCount = Byte.toUnsignedInt(inputBuffer.get());
592 
593             // TODO: Add check: spiSize must be 0 in initial IKE SA negotiation
594             // spiSize should be either 8 for IKE or 4 for IPsec.
595             long spi = SPI_NOT_INCLUDED;
596             switch (spiSize) {
597                 case SPI_LEN_NOT_INCLUDED:
598                     // No SPI attached for IKE initial exchange.
599                     break;
600                 case SPI_LEN_IPSEC:
601                     spi = Integer.toUnsignedLong(inputBuffer.getInt());
602                     break;
603                 case SPI_LEN_IKE:
604                     spi = inputBuffer.getLong();
605                     break;
606                 default:
607                     throw new InvalidSyntaxException(
608                             "Invalid value of spiSize in Proposal Substructure: " + spiSize);
609             }
610 
611             Transform[] transformArray =
612                     sTransformDecoder.decodeTransforms(transformCount, inputBuffer);
613             // TODO: Validate that sum of all Transforms' lengths plus Proposal header length equals
614             // to Proposal's length.
615 
616             List<EncryptionTransform> encryptAlgoList = new LinkedList<>();
617             List<PrfTransform> prfList = new LinkedList<>();
618             List<IntegrityTransform> integAlgoList = new LinkedList<>();
619             List<DhGroupTransform> dhGroupList = new LinkedList<>();
620             List<EsnTransform> esnList = new LinkedList<>();
621 
622             boolean hasUnrecognizedTransform = false;
623 
624             for (Transform transform : transformArray) {
625                 switch (transform.type) {
626                     case Transform.TRANSFORM_TYPE_ENCR:
627                         encryptAlgoList.add((EncryptionTransform) transform);
628                         break;
629                     case Transform.TRANSFORM_TYPE_PRF:
630                         prfList.add((PrfTransform) transform);
631                         break;
632                     case Transform.TRANSFORM_TYPE_INTEG:
633                         integAlgoList.add((IntegrityTransform) transform);
634                         break;
635                     case Transform.TRANSFORM_TYPE_DH:
636                         dhGroupList.add((DhGroupTransform) transform);
637                         break;
638                     case Transform.TRANSFORM_TYPE_ESN:
639                         esnList.add((EsnTransform) transform);
640                         break;
641                     default:
642                         hasUnrecognizedTransform = true;
643                 }
644             }
645 
646             if (protocolId == PROTOCOL_ID_IKE) {
647                 IkeSaProposal saProposal =
648                         new IkeSaProposal(
649                                 encryptAlgoList.toArray(
650                                         new EncryptionTransform[encryptAlgoList.size()]),
651                                 prfList.toArray(new PrfTransform[prfList.size()]),
652                                 integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
653                                 dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]));
654                 return new IkeProposal(number, spiSize, spi, saProposal, hasUnrecognizedTransform);
655             } else {
656                 ChildSaProposal saProposal =
657                         new ChildSaProposal(
658                                 encryptAlgoList.toArray(
659                                         new EncryptionTransform[encryptAlgoList.size()]),
660                                 integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
661                                 dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]),
662                                 esnList.toArray(new EsnTransform[esnList.size()]));
663                 return new ChildProposal(number, spi, saProposal, hasUnrecognizedTransform);
664             }
665         }
666 
667         private static class TransformDecoderImpl implements TransformDecoder {
668             @Override
decodeTransforms(int count, ByteBuffer inputBuffer)669             public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
670                     throws IkeProtocolException {
671                 Transform[] transformArray = new Transform[count];
672                 for (int i = 0; i < count; i++) {
673                     Transform transform = Transform.readFrom(inputBuffer);
674                     transformArray[i] = transform;
675                 }
676                 return transformArray;
677             }
678         }
679 
680         /** Package private method to set TransformDecoder for testing purposes */
681         @VisibleForTesting
setTransformDecoder(TransformDecoder decoder)682         static void setTransformDecoder(TransformDecoder decoder) {
683             sTransformDecoder = decoder;
684         }
685 
686         /** Package private method to reset TransformDecoder */
687         @VisibleForTesting
resetTransformDecoder()688         static void resetTransformDecoder() {
689             sTransformDecoder = new TransformDecoderImpl();
690         }
691 
692         /** Package private */
isNegotiatedFrom(Proposal reqProposal)693         boolean isNegotiatedFrom(Proposal reqProposal) {
694             if (protocolId != reqProposal.protocolId || number != reqProposal.number) {
695                 return false;
696             }
697             return getSaProposal().isNegotiatedFrom(reqProposal.getSaProposal());
698         }
699 
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)700         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
701             Transform[] allTransforms = getSaProposal().getAllTransforms();
702             byte isLastIndicator = isLast ? LAST_PROPOSAL : NOT_LAST_PROPOSAL;
703 
704             byteBuffer
705                     .put(isLastIndicator)
706                     .put(new byte[PROPOSAL_RESERVED_FIELD_LEN])
707                     .putShort((short) getProposalLength())
708                     .put(number)
709                     .put((byte) protocolId)
710                     .put(spiSize)
711                     .put((byte) allTransforms.length);
712 
713             switch (spiSize) {
714                 case SPI_LEN_NOT_INCLUDED:
715                     // No SPI attached for IKE initial exchange.
716                     break;
717                 case SPI_LEN_IPSEC:
718                     byteBuffer.putInt((int) spi);
719                     break;
720                 case SPI_LEN_IKE:
721                     byteBuffer.putLong((long) spi);
722                     break;
723                 default:
724                     throw new IllegalArgumentException(
725                             "Invalid value of spiSize in Proposal Substructure: " + spiSize);
726             }
727 
728             // Encode all Transform.
729             for (int i = 0; i < allTransforms.length; i++) {
730                 // The last transform has the isLast flag set to true.
731                 allTransforms[i].encodeToByteBuffer(i == allTransforms.length - 1, byteBuffer);
732             }
733         }
734 
getProposalLength()735         protected int getProposalLength() {
736             int len = PROPOSAL_HEADER_LEN + spiSize;
737 
738             Transform[] allTransforms = getSaProposal().getAllTransforms();
739             for (Transform t : allTransforms) len += t.getTransformLength();
740             return len;
741         }
742 
743         @Override
744         @NonNull
toString()745         public String toString() {
746             return "Proposal(" + number + ") " + getSaProposal().toString();
747         }
748 
749         /** Package private method for releasing SPI resource in this unselected Proposal. */
releaseSpiResourceIfExists()750         abstract void releaseSpiResourceIfExists();
751 
752         /** Package private method for getting SaProposal */
getSaProposal()753         abstract SaProposal getSaProposal();
754     }
755 
756     /** This class represents a Proposal for IKE SA negotiation. */
757     public static final class IkeProposal extends Proposal {
758         private IkeSecurityParameterIndex mIkeSpiResource;
759 
760         public final IkeSaProposal saProposal;
761 
762         /**
763          * Construct IkeProposal from a decoded inbound message for IKE negotiation.
764          *
765          * <p>Package private
766          */
IkeProposal( byte number, byte spiSize, long spi, IkeSaProposal saProposal, boolean hasUnrecognizedTransform)767         IkeProposal(
768                 byte number,
769                 byte spiSize,
770                 long spi,
771                 IkeSaProposal saProposal,
772                 boolean hasUnrecognizedTransform) {
773             super(number, PROTOCOL_ID_IKE, spiSize, spi, hasUnrecognizedTransform);
774             this.saProposal = saProposal;
775         }
776 
777         /** Construct IkeProposal for an outbound message for IKE negotiation. */
IkeProposal( byte number, byte spiSize, IkeSecurityParameterIndex ikeSpiResource, IkeSaProposal saProposal)778         private IkeProposal(
779                 byte number,
780                 byte spiSize,
781                 IkeSecurityParameterIndex ikeSpiResource,
782                 IkeSaProposal saProposal) {
783             super(
784                     number,
785                     PROTOCOL_ID_IKE,
786                     spiSize,
787                     ikeSpiResource == null ? SPI_NOT_INCLUDED : ikeSpiResource.getSpi(),
788                     false /* hasUnrecognizedTransform */);
789             mIkeSpiResource = ikeSpiResource;
790             this.saProposal = saProposal;
791         }
792 
793         /**
794          * Construct IkeProposal for an outbound message for IKE negotiation.
795          *
796          * <p>Package private
797          */
798         @VisibleForTesting
createIkeProposal( byte number, byte spiSize, IkeSaProposal saProposal, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)799         static IkeProposal createIkeProposal(
800                 byte number,
801                 byte spiSize,
802                 IkeSaProposal saProposal,
803                 IkeSpiGenerator ikeSpiGenerator,
804                 InetAddress localAddress)
805                 throws IOException {
806             // IKE_INIT uses SPI_LEN_NOT_INCLUDED, while rekeys use SPI_LEN_IKE
807             IkeSecurityParameterIndex spiResource =
808                     (spiSize == SPI_LEN_NOT_INCLUDED
809                             ? null
810                             : ikeSpiGenerator.allocateSpi(localAddress));
811             return new IkeProposal(number, spiSize, spiResource, saProposal);
812         }
813 
814         /** Package private method for releasing SPI resource in this unselected Proposal. */
releaseSpiResourceIfExists()815         void releaseSpiResourceIfExists() {
816             // mIkeSpiResource is null when doing IKE initial exchanges.
817             if (mIkeSpiResource == null) return;
818             mIkeSpiResource.close();
819             mIkeSpiResource = null;
820         }
821 
822         /**
823          * Package private method for allocating SPI resource for a validated remotely generated IKE
824          * SA proposal.
825          */
allocateResourceForRemoteIkeSpi( IkeSpiGenerator ikeSpiGenerator, InetAddress remoteAddress)826         void allocateResourceForRemoteIkeSpi(
827                 IkeSpiGenerator ikeSpiGenerator, InetAddress remoteAddress) throws IOException {
828             mIkeSpiResource = ikeSpiGenerator.allocateSpi(remoteAddress, spi);
829         }
830 
831         @Override
getSaProposal()832         public SaProposal getSaProposal() {
833             return saProposal;
834         }
835 
836         /**
837          * Get the IKE SPI resource.
838          *
839          * @return the IKE SPI resource or null for IKE initial exchanges.
840          */
getIkeSpiResource()841         public IkeSecurityParameterIndex getIkeSpiResource() {
842             return mIkeSpiResource;
843         }
844     }
845 
846     /** This class represents a Proposal for Child SA negotiation. */
847     public static final class ChildProposal extends Proposal {
848         private SecurityParameterIndex mChildSpiResource;
849 
850         public final ChildSaProposal saProposal;
851 
852         /**
853          * Construct ChildProposal from a decoded inbound message for Child SA negotiation.
854          *
855          * <p>Package private
856          */
ChildProposal( byte number, long spi, ChildSaProposal saProposal, boolean hasUnrecognizedTransform)857         ChildProposal(
858                 byte number,
859                 long spi,
860                 ChildSaProposal saProposal,
861                 boolean hasUnrecognizedTransform) {
862             super(
863                     number,
864                     PROTOCOL_ID_ESP,
865                     SPI_LEN_IPSEC,
866                     spi,
867                     hasUnrecognizedTransform);
868             this.saProposal = saProposal;
869         }
870 
871         /** Construct ChildProposal for an outbound message for Child SA negotiation. */
ChildProposal( byte number, SecurityParameterIndex childSpiResource, ChildSaProposal saProposal)872         private ChildProposal(
873                 byte number, SecurityParameterIndex childSpiResource, ChildSaProposal saProposal) {
874             super(
875                     number,
876                     PROTOCOL_ID_ESP,
877                     SPI_LEN_IPSEC,
878                     (long) childSpiResource.getSpi(),
879                     false /* hasUnrecognizedTransform */);
880             mChildSpiResource = childSpiResource;
881             this.saProposal = saProposal;
882         }
883 
884         /**
885          * Construct ChildProposal for an outbound message for Child SA negotiation.
886          *
887          * <p>Package private
888          */
889         @VisibleForTesting
createChildProposal( byte number, ChildSaProposal saProposal, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress)890         static ChildProposal createChildProposal(
891                 byte number,
892                 ChildSaProposal saProposal,
893                 IpSecSpiGenerator ipSecSpiGenerator,
894                 InetAddress localAddress)
895                 throws SpiUnavailableException, ResourceUnavailableException {
896             return new ChildProposal(
897                     number, ipSecSpiGenerator.allocateSpi(localAddress), saProposal);
898         }
899 
900         /** Package private method for releasing SPI resource in this unselected Proposal. */
releaseSpiResourceIfExists()901         void releaseSpiResourceIfExists() {
902             if (mChildSpiResource ==  null) return;
903 
904             mChildSpiResource.close();
905             mChildSpiResource = null;
906         }
907 
908         /**
909          * Package private method for allocating SPI resource for a validated remotely generated
910          * Child SA proposal.
911          */
allocateResourceForRemoteChildSpi( IpSecSpiGenerator ipSecSpiGenerator, InetAddress remoteAddress)912         void allocateResourceForRemoteChildSpi(
913                 IpSecSpiGenerator ipSecSpiGenerator, InetAddress remoteAddress)
914                 throws ResourceUnavailableException, SpiUnavailableException {
915             mChildSpiResource = ipSecSpiGenerator.allocateSpi(remoteAddress, (int) spi);
916         }
917 
918         @Override
getSaProposal()919         public SaProposal getSaProposal() {
920             return saProposal;
921         }
922 
923         /**
924          * Get the IPsec SPI resource.
925          *
926          * @return the IPsec SPI resource.
927          */
getChildSpiResource()928         public SecurityParameterIndex getChildSpiResource() {
929             return mChildSpiResource;
930         }
931     }
932 
933     @VisibleForTesting
934     interface AttributeDecoder {
decodeAttributes(int length, ByteBuffer inputBuffer)935         List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
936                 throws IkeProtocolException;
937     }
938 
939     /**
940      * Transform is an abstract base class that represents the common information for all Transform
941      * types. It may contain one or more {@link Attribute}.
942      *
943      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
944      *     Exchange Protocol Version 2 (IKEv2)</a>
945      *     <p>Transforms with unrecognized Transform ID or containing unrecognized Attribute Type
946      *     shall be ignored when processing received SA payload.
947      */
948     public abstract static class Transform {
949 
950         @Retention(RetentionPolicy.SOURCE)
951         @IntDef({
952             TRANSFORM_TYPE_ENCR,
953             TRANSFORM_TYPE_PRF,
954             TRANSFORM_TYPE_INTEG,
955             TRANSFORM_TYPE_DH,
956             TRANSFORM_TYPE_ESN
957         })
958         public @interface TransformType {}
959 
960         public static final int TRANSFORM_TYPE_ENCR = 1;
961         public static final int TRANSFORM_TYPE_PRF = 2;
962         public static final int TRANSFORM_TYPE_INTEG = 3;
963         public static final int TRANSFORM_TYPE_DH = 4;
964         public static final int TRANSFORM_TYPE_ESN = 5;
965 
966         private static final byte LAST_TRANSFORM = 0;
967         private static final byte NOT_LAST_TRANSFORM = 3;
968 
969         // Length of reserved field of a Transform.
970         private static final int TRANSFORM_RESERVED_FIELD_LEN = 1;
971 
972         // Length of the Transform that with no Attribute.
973         protected static final int BASIC_TRANSFORM_LEN = 8;
974 
975         // TODO: Add constants for supported algorithms
976 
977         private static AttributeDecoder sAttributeDecoder = new AttributeDecoderImpl();
978 
979         // Only supported type falls into {@link TransformType}
980         public final int type;
981         public final int id;
982         public final boolean isSupported;
983 
984         /** Construct an instance of Transform for building an outbound packet. */
Transform(int type, int id)985         protected Transform(int type, int id) {
986             this.type = type;
987             this.id = id;
988             if (!isSupportedTransformId(id)) {
989                 throw new IllegalArgumentException(
990                         "Unsupported " + getTransformTypeString() + " Algorithm ID: " + id);
991             }
992             this.isSupported = true;
993         }
994 
995         /** Construct an instance of Transform for decoding an inbound packet. */
Transform(int type, int id, List<Attribute> attributeList)996         protected Transform(int type, int id, List<Attribute> attributeList) {
997             this.type = type;
998             this.id = id;
999             this.isSupported =
1000                     isSupportedTransformId(id) && !hasUnrecognizedAttribute(attributeList);
1001         }
1002 
1003         @VisibleForTesting
readFrom(ByteBuffer inputBuffer)1004         static Transform readFrom(ByteBuffer inputBuffer) throws IkeProtocolException {
1005             byte isLast = inputBuffer.get();
1006             if (isLast != LAST_TRANSFORM && isLast != NOT_LAST_TRANSFORM) {
1007                 throw new InvalidSyntaxException(
1008                         "Invalid value of Last Transform Substructure: " + isLast);
1009             }
1010 
1011             // Skip RESERVED byte
1012             inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
1013 
1014             int length = Short.toUnsignedInt(inputBuffer.getShort());
1015             int type = Byte.toUnsignedInt(inputBuffer.get());
1016 
1017             // Skip RESERVED byte
1018             inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
1019 
1020             int id = Short.toUnsignedInt(inputBuffer.getShort());
1021 
1022             // Decode attributes
1023             List<Attribute> attributeList = sAttributeDecoder.decodeAttributes(length, inputBuffer);
1024 
1025             validateAttributeUniqueness(attributeList);
1026 
1027             switch (type) {
1028                 case TRANSFORM_TYPE_ENCR:
1029                     return new EncryptionTransform(id, attributeList);
1030                 case TRANSFORM_TYPE_PRF:
1031                     return new PrfTransform(id, attributeList);
1032                 case TRANSFORM_TYPE_INTEG:
1033                     return new IntegrityTransform(id, attributeList);
1034                 case TRANSFORM_TYPE_DH:
1035                     return new DhGroupTransform(id, attributeList);
1036                 case TRANSFORM_TYPE_ESN:
1037                     return new EsnTransform(id, attributeList);
1038                 default:
1039                     return new UnrecognizedTransform(type, id, attributeList);
1040             }
1041         }
1042 
1043         private static class AttributeDecoderImpl implements AttributeDecoder {
1044             @Override
decodeAttributes(int length, ByteBuffer inputBuffer)1045             public List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
1046                     throws IkeProtocolException {
1047                 List<Attribute> list = new LinkedList<>();
1048                 int parsedLength = BASIC_TRANSFORM_LEN;
1049                 while (parsedLength < length) {
1050                     Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
1051                     parsedLength += pair.second; // Increase parsedLength by the Atrribute length
1052                     list.add(pair.first);
1053                 }
1054                 // TODO: Validate that parsedLength equals to length.
1055                 return list;
1056             }
1057         }
1058 
1059         /** Package private method to set AttributeDecoder for testing purpose */
1060         @VisibleForTesting
setAttributeDecoder(AttributeDecoder decoder)1061         static void setAttributeDecoder(AttributeDecoder decoder) {
1062             sAttributeDecoder = decoder;
1063         }
1064 
1065         /** Package private method to reset AttributeDecoder */
1066         @VisibleForTesting
resetAttributeDecoder()1067         static void resetAttributeDecoder() {
1068             sAttributeDecoder = new AttributeDecoderImpl();
1069         }
1070 
1071         // Throw InvalidSyntaxException if there are multiple Attributes of the same type
validateAttributeUniqueness(List<Attribute> attributeList)1072         private static void validateAttributeUniqueness(List<Attribute> attributeList)
1073                 throws IkeProtocolException {
1074             Set<Integer> foundTypes = new ArraySet<>();
1075             for (Attribute attr : attributeList) {
1076                 if (!foundTypes.add(attr.type)) {
1077                     throw new InvalidSyntaxException(
1078                             "There are multiple Attributes of the same type. ");
1079                 }
1080             }
1081         }
1082 
1083         // Check if there is Attribute with unrecognized type.
hasUnrecognizedAttribute(List<Attribute> attributeList)1084         protected abstract boolean hasUnrecognizedAttribute(List<Attribute> attributeList);
1085 
1086         // Check if this Transform ID is supported.
isSupportedTransformId(int id)1087         protected abstract boolean isSupportedTransformId(int id);
1088 
1089         // Encode Transform to a ByteBuffer.
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1090         protected abstract void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer);
1091 
1092         // Get entire Transform length.
getTransformLength()1093         protected abstract int getTransformLength();
1094 
encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1095         protected void encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1096             byte isLastIndicator = isLast ? LAST_TRANSFORM : NOT_LAST_TRANSFORM;
1097             byteBuffer
1098                     .put(isLastIndicator)
1099                     .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
1100                     .putShort((short) getTransformLength())
1101                     .put((byte) type)
1102                     .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
1103                     .putShort((short) id);
1104         }
1105 
1106         /**
1107          * Get Tranform Type as a String.
1108          *
1109          * @return Tranform Type as a String.
1110          */
getTransformTypeString()1111         public abstract String getTransformTypeString();
1112 
1113         // TODO: Add abstract getTransformIdString() to return specific algorithm/dhGroup name
1114     }
1115 
1116     /**
1117      * EncryptionTransform represents an encryption algorithm. It may contain an Atrribute
1118      * specifying the key length.
1119      *
1120      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1121      *     Exchange Protocol Version 2 (IKEv2)</a>
1122      */
1123     public static final class EncryptionTransform extends Transform {
1124         public static final int KEY_LEN_UNSPECIFIED = 0;
1125 
1126         // When using encryption algorithm with variable-length keys, mSpecifiedKeyLength MUST be
1127         // set and a KeyLengthAttribute MUST be attached. Otherwise, mSpecifiedKeyLength MUST NOT be
1128         // set and KeyLengthAttribute MUST NOT be attached.
1129         private final int mSpecifiedKeyLength;
1130 
1131         /**
1132          * Contruct an instance of EncryptionTransform with fixed key length for building an
1133          * outbound packet.
1134          *
1135          * @param id the IKE standard Transform ID.
1136          */
EncryptionTransform(@ncryptionAlgorithm int id)1137         public EncryptionTransform(@EncryptionAlgorithm int id) {
1138             this(id, KEY_LEN_UNSPECIFIED);
1139         }
1140 
1141         /**
1142          * Contruct an instance of EncryptionTransform with variable key length for building an
1143          * outbound packet.
1144          *
1145          * @param id the IKE standard Transform ID.
1146          * @param specifiedKeyLength the specified key length of this encryption algorithm.
1147          */
EncryptionTransform(@ncryptionAlgorithm int id, int specifiedKeyLength)1148         public EncryptionTransform(@EncryptionAlgorithm int id, int specifiedKeyLength) {
1149             super(Transform.TRANSFORM_TYPE_ENCR, id);
1150 
1151             mSpecifiedKeyLength = specifiedKeyLength;
1152             try {
1153                 validateKeyLength();
1154             } catch (InvalidSyntaxException e) {
1155                 throw new IllegalArgumentException(e);
1156             }
1157         }
1158 
1159         /**
1160          * Contruct an instance of EncryptionTransform for decoding an inbound packet.
1161          *
1162          * @param id the IKE standard Transform ID.
1163          * @param attributeList the decoded list of Attribute.
1164          * @throws InvalidSyntaxException for syntax error.
1165          */
EncryptionTransform(int id, List<Attribute> attributeList)1166         protected EncryptionTransform(int id, List<Attribute> attributeList)
1167                 throws InvalidSyntaxException {
1168             super(Transform.TRANSFORM_TYPE_ENCR, id, attributeList);
1169             if (!isSupported) {
1170                 mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
1171             } else {
1172                 if (attributeList.size() == 0) {
1173                     mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
1174                 } else {
1175                     KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
1176                     mSpecifiedKeyLength = attr.keyLength;
1177                 }
1178                 validateKeyLength();
1179             }
1180         }
1181 
1182         /**
1183          * Get the specified key length.
1184          *
1185          * @return the specified key length.
1186          */
getSpecifiedKeyLength()1187         public int getSpecifiedKeyLength() {
1188             return mSpecifiedKeyLength;
1189         }
1190 
1191         @Override
hashCode()1192         public int hashCode() {
1193             return Objects.hash(type, id, mSpecifiedKeyLength);
1194         }
1195 
1196         @Override
equals(Object o)1197         public boolean equals(Object o) {
1198             if (!(o instanceof EncryptionTransform)) return false;
1199 
1200             EncryptionTransform other = (EncryptionTransform) o;
1201             return (type == other.type
1202                     && id == other.id
1203                     && mSpecifiedKeyLength == other.mSpecifiedKeyLength);
1204         }
1205 
1206         @Override
isSupportedTransformId(int id)1207         protected boolean isSupportedTransformId(int id) {
1208             return SaProposal.isSupportedEncryptionAlgorithm(id);
1209         }
1210 
1211         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1212         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1213             for (Attribute attr : attributeList) {
1214                 if (attr instanceof UnrecognizedAttribute) {
1215                     return true;
1216                 }
1217             }
1218             return false;
1219         }
1220 
getKeyLengthAttribute(List<Attribute> attributeList)1221         private KeyLengthAttribute getKeyLengthAttribute(List<Attribute> attributeList) {
1222             for (Attribute attr : attributeList) {
1223                 if (attr.type == Attribute.ATTRIBUTE_TYPE_KEY_LENGTH) {
1224                     return (KeyLengthAttribute) attr;
1225                 }
1226             }
1227             throw new IllegalArgumentException("Cannot find Attribute with Key Length type");
1228         }
1229 
validateKeyLength()1230         private void validateKeyLength() throws InvalidSyntaxException {
1231             switch (id) {
1232                 case SaProposal.ENCRYPTION_ALGORITHM_3DES:
1233                     if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1234                         throw new InvalidSyntaxException(
1235                                 "Must not set Key Length value for this "
1236                                         + getTransformTypeString()
1237                                         + " Algorithm ID: "
1238                                         + id);
1239                     }
1240                     return;
1241                 case SaProposal.ENCRYPTION_ALGORITHM_AES_CBC:
1242                     /* fall through */
1243                 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8:
1244                     /* fall through */
1245                 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
1246                     /* fall through */
1247                 case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
1248                     if (mSpecifiedKeyLength == KEY_LEN_UNSPECIFIED) {
1249                         throw new InvalidSyntaxException(
1250                                 "Must set Key Length value for this "
1251                                         + getTransformTypeString()
1252                                         + " Algorithm ID: "
1253                                         + id);
1254                     }
1255                     if (mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_128
1256                             && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_192
1257                             && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_256) {
1258                         throw new InvalidSyntaxException(
1259                                 "Invalid key length for this "
1260                                         + getTransformTypeString()
1261                                         + " Algorithm ID: "
1262                                         + id);
1263                     }
1264                     return;
1265                 default:
1266                     // Won't hit here.
1267                     throw new IllegalArgumentException(
1268                             "Unrecognized Encryption Algorithm ID: " + id);
1269             }
1270         }
1271 
1272         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1273         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1274             encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1275 
1276             if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1277                 new KeyLengthAttribute(mSpecifiedKeyLength).encodeToByteBuffer(byteBuffer);
1278             }
1279         }
1280 
1281         @Override
getTransformLength()1282         protected int getTransformLength() {
1283             int len = BASIC_TRANSFORM_LEN;
1284 
1285             if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
1286                 len += new KeyLengthAttribute(mSpecifiedKeyLength).getAttributeLength();
1287             }
1288 
1289             return len;
1290         }
1291 
1292         @Override
getTransformTypeString()1293         public String getTransformTypeString() {
1294             return "Encryption Algorithm";
1295         }
1296 
1297         @Override
1298         @NonNull
toString()1299         public String toString() {
1300             if (isSupported) {
1301                 return SaProposal.getEncryptionAlgorithmString(id)
1302                         + "("
1303                         + getSpecifiedKeyLength()
1304                         + ")";
1305             } else {
1306                 return "ENCR(" + id + ")";
1307             }
1308         }
1309     }
1310 
1311     /**
1312      * PrfTransform represents an pseudorandom function.
1313      *
1314      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1315      *     Exchange Protocol Version 2 (IKEv2)</a>
1316      */
1317     public static final class PrfTransform extends Transform {
1318         /**
1319          * Contruct an instance of PrfTransform for building an outbound packet.
1320          *
1321          * @param id the IKE standard Transform ID.
1322          */
PrfTransform(@seudorandomFunction int id)1323         public PrfTransform(@PseudorandomFunction int id) {
1324             super(Transform.TRANSFORM_TYPE_PRF, id);
1325         }
1326 
1327         /**
1328          * Contruct an instance of PrfTransform for decoding an inbound packet.
1329          *
1330          * @param id the IKE standard Transform ID.
1331          * @param attributeList the decoded list of Attribute.
1332          * @throws InvalidSyntaxException for syntax error.
1333          */
PrfTransform(int id, List<Attribute> attributeList)1334         protected PrfTransform(int id, List<Attribute> attributeList)
1335                 throws InvalidSyntaxException {
1336             super(Transform.TRANSFORM_TYPE_PRF, id, attributeList);
1337         }
1338 
1339         @Override
hashCode()1340         public int hashCode() {
1341             return Objects.hash(type, id);
1342         }
1343 
1344         @Override
equals(Object o)1345         public boolean equals(Object o) {
1346             if (!(o instanceof PrfTransform)) return false;
1347 
1348             PrfTransform other = (PrfTransform) o;
1349             return (type == other.type && id == other.id);
1350         }
1351 
1352         @Override
isSupportedTransformId(int id)1353         protected boolean isSupportedTransformId(int id) {
1354             return SaProposal.isSupportedPseudorandomFunction(id);
1355         }
1356 
1357         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1358         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1359             return !attributeList.isEmpty();
1360         }
1361 
1362         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1363         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1364             encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1365         }
1366 
1367         @Override
getTransformLength()1368         protected int getTransformLength() {
1369             return BASIC_TRANSFORM_LEN;
1370         }
1371 
1372         @Override
getTransformTypeString()1373         public String getTransformTypeString() {
1374             return "Pseudorandom Function";
1375         }
1376 
1377         @Override
1378         @NonNull
toString()1379         public String toString() {
1380             if (isSupported) {
1381                 return SaProposal.getPseudorandomFunctionString(id);
1382             } else {
1383                 return "PRF(" + id + ")";
1384             }
1385         }
1386     }
1387 
1388     /**
1389      * IntegrityTransform represents an integrity algorithm.
1390      *
1391      * <p>Proposing integrity algorithm for ESP SA is optional. Omitting the IntegrityTransform is
1392      * equivalent to including it with a value of NONE. When multiple integrity algorithms are
1393      * provided, choosing any of them are acceptable.
1394      *
1395      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1396      *     Exchange Protocol Version 2 (IKEv2)</a>
1397      */
1398     public static final class IntegrityTransform extends Transform {
1399         /**
1400          * Contruct an instance of IntegrityTransform for building an outbound packet.
1401          *
1402          * @param id the IKE standard Transform ID.
1403          */
IntegrityTransform(@ntegrityAlgorithm int id)1404         public IntegrityTransform(@IntegrityAlgorithm int id) {
1405             super(Transform.TRANSFORM_TYPE_INTEG, id);
1406         }
1407 
1408         /**
1409          * Contruct an instance of IntegrityTransform for decoding an inbound packet.
1410          *
1411          * @param id the IKE standard Transform ID.
1412          * @param attributeList the decoded list of Attribute.
1413          * @throws InvalidSyntaxException for syntax error.
1414          */
IntegrityTransform(int id, List<Attribute> attributeList)1415         protected IntegrityTransform(int id, List<Attribute> attributeList)
1416                 throws InvalidSyntaxException {
1417             super(Transform.TRANSFORM_TYPE_INTEG, id, attributeList);
1418         }
1419 
1420         @Override
hashCode()1421         public int hashCode() {
1422             return Objects.hash(type, id);
1423         }
1424 
1425         @Override
equals(Object o)1426         public boolean equals(Object o) {
1427             if (!(o instanceof IntegrityTransform)) return false;
1428 
1429             IntegrityTransform other = (IntegrityTransform) o;
1430             return (type == other.type && id == other.id);
1431         }
1432 
1433         @Override
isSupportedTransformId(int id)1434         protected boolean isSupportedTransformId(int id) {
1435             return SaProposal.isSupportedIntegrityAlgorithm(id);
1436         }
1437 
1438         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1439         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1440             return !attributeList.isEmpty();
1441         }
1442 
1443         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1444         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1445             encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1446         }
1447 
1448         @Override
getTransformLength()1449         protected int getTransformLength() {
1450             return BASIC_TRANSFORM_LEN;
1451         }
1452 
1453         @Override
getTransformTypeString()1454         public String getTransformTypeString() {
1455             return "Integrity Algorithm";
1456         }
1457 
1458         @Override
1459         @NonNull
toString()1460         public String toString() {
1461             if (isSupported) {
1462                 return SaProposal.getIntegrityAlgorithmString(id);
1463             } else {
1464                 return "AUTH(" + id + ")";
1465             }
1466         }
1467     }
1468 
1469     /**
1470      * DhGroupTransform represents a Diffie-Hellman Group
1471      *
1472      * <p>Proposing DH group for non-first Child SA is optional. Omitting the DhGroupTransform is
1473      * equivalent to including it with a value of NONE. When multiple DH groups are provided,
1474      * choosing any of them are acceptable.
1475      *
1476      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1477      *     Exchange Protocol Version 2 (IKEv2)</a>
1478      */
1479     public static final class DhGroupTransform extends Transform {
1480         /**
1481          * Contruct an instance of DhGroupTransform for building an outbound packet.
1482          *
1483          * @param id the IKE standard Transform ID.
1484          */
DhGroupTransform(@hGroup int id)1485         public DhGroupTransform(@DhGroup int id) {
1486             super(Transform.TRANSFORM_TYPE_DH, id);
1487         }
1488 
1489         /**
1490          * Contruct an instance of DhGroupTransform for decoding an inbound packet.
1491          *
1492          * @param id the IKE standard Transform ID.
1493          * @param attributeList the decoded list of Attribute.
1494          * @throws InvalidSyntaxException for syntax error.
1495          */
DhGroupTransform(int id, List<Attribute> attributeList)1496         protected DhGroupTransform(int id, List<Attribute> attributeList)
1497                 throws InvalidSyntaxException {
1498             super(Transform.TRANSFORM_TYPE_DH, id, attributeList);
1499         }
1500 
1501         @Override
hashCode()1502         public int hashCode() {
1503             return Objects.hash(type, id);
1504         }
1505 
1506         @Override
equals(Object o)1507         public boolean equals(Object o) {
1508             if (!(o instanceof DhGroupTransform)) return false;
1509 
1510             DhGroupTransform other = (DhGroupTransform) o;
1511             return (type == other.type && id == other.id);
1512         }
1513 
1514         @Override
isSupportedTransformId(int id)1515         protected boolean isSupportedTransformId(int id) {
1516             return SaProposal.isSupportedDhGroup(id);
1517         }
1518 
1519         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1520         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1521             return !attributeList.isEmpty();
1522         }
1523 
1524         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1525         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1526             encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1527         }
1528 
1529         @Override
getTransformLength()1530         protected int getTransformLength() {
1531             return BASIC_TRANSFORM_LEN;
1532         }
1533 
1534         @Override
getTransformTypeString()1535         public String getTransformTypeString() {
1536             return "Diffie-Hellman Group";
1537         }
1538 
1539         @Override
1540         @NonNull
toString()1541         public String toString() {
1542             if (isSupported) {
1543                 return SaProposal.getDhGroupString(id);
1544             } else {
1545                 return "DH(" + id + ")";
1546             }
1547         }
1548     }
1549 
1550     /**
1551      * EsnTransform represents ESN policy that indicates if IPsec SA uses tranditional 32-bit
1552      * sequence numbers or extended(64-bit) sequence numbers.
1553      *
1554      * <p>Currently IKE library only supports negotiating IPsec SA that do not use extended sequence
1555      * numbers. The Transform ID of EsnTransform in outbound packets is not user configurable.
1556      *
1557      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
1558      *     Exchange Protocol Version 2 (IKEv2)</a>
1559      */
1560     public static final class EsnTransform extends Transform {
1561         @Retention(RetentionPolicy.SOURCE)
1562         @IntDef({ESN_POLICY_NO_EXTENDED, ESN_POLICY_EXTENDED})
1563         public @interface EsnPolicy {}
1564 
1565         public static final int ESN_POLICY_NO_EXTENDED = 0;
1566         public static final int ESN_POLICY_EXTENDED = 1;
1567 
1568         /**
1569          * Construct an instance of EsnTransform indicates using no-extended sequence numbers for
1570          * building an outbound packet.
1571          */
EsnTransform()1572         public EsnTransform() {
1573             super(Transform.TRANSFORM_TYPE_ESN, ESN_POLICY_NO_EXTENDED);
1574         }
1575 
1576         /**
1577          * Contruct an instance of EsnTransform for decoding an inbound packet.
1578          *
1579          * @param id the IKE standard Transform ID.
1580          * @param attributeList the decoded list of Attribute.
1581          * @throws InvalidSyntaxException for syntax error.
1582          */
EsnTransform(int id, List<Attribute> attributeList)1583         protected EsnTransform(int id, List<Attribute> attributeList)
1584                 throws InvalidSyntaxException {
1585             super(Transform.TRANSFORM_TYPE_ESN, id, attributeList);
1586         }
1587 
1588         @Override
hashCode()1589         public int hashCode() {
1590             return Objects.hash(type, id);
1591         }
1592 
1593         @Override
equals(Object o)1594         public boolean equals(Object o) {
1595             if (!(o instanceof EsnTransform)) return false;
1596 
1597             EsnTransform other = (EsnTransform) o;
1598             return (type == other.type && id == other.id);
1599         }
1600 
1601         @Override
isSupportedTransformId(int id)1602         protected boolean isSupportedTransformId(int id) {
1603             return (id == ESN_POLICY_NO_EXTENDED || id == ESN_POLICY_EXTENDED);
1604         }
1605 
1606         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1607         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1608             return !attributeList.isEmpty();
1609         }
1610 
1611         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1612         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1613             encodeBasicTransformToByteBuffer(isLast, byteBuffer);
1614         }
1615 
1616         @Override
getTransformLength()1617         protected int getTransformLength() {
1618             return BASIC_TRANSFORM_LEN;
1619         }
1620 
1621         @Override
getTransformTypeString()1622         public String getTransformTypeString() {
1623             return "Extended Sequence Numbers";
1624         }
1625 
1626         @Override
1627         @NonNull
toString()1628         public String toString() {
1629             if (id == ESN_POLICY_NO_EXTENDED) {
1630                 return "ESN_No_Extended";
1631             }
1632             return "ESN_Extended";
1633         }
1634     }
1635 
1636     /**
1637      * UnrecognizedTransform represents a Transform with unrecognized Transform Type.
1638      *
1639      * <p>Proposals containing an UnrecognizedTransform should be ignored.
1640      */
1641     protected static final class UnrecognizedTransform extends Transform {
UnrecognizedTransform(int type, int id, List<Attribute> attributeList)1642         protected UnrecognizedTransform(int type, int id, List<Attribute> attributeList) {
1643             super(type, id, attributeList);
1644         }
1645 
1646         @Override
isSupportedTransformId(int id)1647         protected boolean isSupportedTransformId(int id) {
1648             return false;
1649         }
1650 
1651         @Override
hasUnrecognizedAttribute(List<Attribute> attributeList)1652         protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
1653             return !attributeList.isEmpty();
1654         }
1655 
1656         @Override
encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer)1657         protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
1658             throw new UnsupportedOperationException(
1659                     "It is not supported to encode a Transform with" + getTransformTypeString());
1660         }
1661 
1662         @Override
getTransformLength()1663         protected int getTransformLength() {
1664             throw new UnsupportedOperationException(
1665                     "It is not supported to get length of a Transform with "
1666                             + getTransformTypeString());
1667         }
1668 
1669         /**
1670          * Return Tranform Type of Unrecognized Transform as a String.
1671          *
1672          * @return Tranform Type of Unrecognized Transform as a String.
1673          */
1674         @Override
getTransformTypeString()1675         public String getTransformTypeString() {
1676             return "Unrecognized Transform Type.";
1677         }
1678     }
1679 
1680     /**
1681      * Attribute is an abtract base class for completing the specification of some {@link
1682      * Transform}.
1683      *
1684      * <p>Attribute is either in Type/Value format or Type/Length/Value format. For TV format,
1685      * Attribute length is always 4 bytes containing value for 2 bytes. While for TLV format,
1686      * Attribute length is determined by length field.
1687      *
1688      * <p>Currently only Key Length type is supported
1689      *
1690      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.5">RFC 7296, Internet Key
1691      *     Exchange Protocol Version 2 (IKEv2)</a>
1692      */
1693     public abstract static class Attribute {
1694         @Retention(RetentionPolicy.SOURCE)
1695         @IntDef({ATTRIBUTE_TYPE_KEY_LENGTH})
1696         public @interface AttributeType {}
1697 
1698         // Support only one Attribute type: Key Length. Should use Type/Value format.
1699         public static final int ATTRIBUTE_TYPE_KEY_LENGTH = 14;
1700 
1701         // Mask to extract the left most AF bit to indicate Attribute Format.
1702         private static final int ATTRIBUTE_FORMAT_MASK = 0x8000;
1703         // Mask to extract 15 bits after the AF bit to indicate Attribute Type.
1704         private static final int ATTRIBUTE_TYPE_MASK = 0x7fff;
1705 
1706         // Package private mask to indicate that Type-Value (TV) Attribute Format is used.
1707         static final int ATTRIBUTE_FORMAT_TV = ATTRIBUTE_FORMAT_MASK;
1708 
1709         // Package private
1710         static final int TV_ATTRIBUTE_VALUE_LEN = 2;
1711         static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
1712         static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
1713 
1714         // Only Key Length type belongs to AttributeType
1715         public final int type;
1716 
1717         /** Construct an instance of an Attribute when decoding message. */
Attribute(int type)1718         protected Attribute(int type) {
1719             this.type = type;
1720         }
1721 
1722         @VisibleForTesting
readFrom(ByteBuffer inputBuffer)1723         static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer)
1724                 throws IkeProtocolException {
1725             short formatAndType = inputBuffer.getShort();
1726             int format = formatAndType & ATTRIBUTE_FORMAT_MASK;
1727             int type = formatAndType & ATTRIBUTE_TYPE_MASK;
1728 
1729             int length = 0;
1730             byte[] value = new byte[0];
1731             if (format == ATTRIBUTE_FORMAT_TV) {
1732                 // Type/Value format
1733                 length = TV_ATTRIBUTE_TOTAL_LEN;
1734                 value = new byte[TV_ATTRIBUTE_VALUE_LEN];
1735             } else {
1736                 // Type/Length/Value format
1737                 if (type == ATTRIBUTE_TYPE_KEY_LENGTH) {
1738                     throw new InvalidSyntaxException("Wrong format in Transform Attribute");
1739                 }
1740 
1741                 length = Short.toUnsignedInt(inputBuffer.getShort());
1742                 int valueLen = length - TVL_ATTRIBUTE_HEADER_LEN;
1743                 // IkeMessage will catch exception if valueLen is negative.
1744                 value = new byte[valueLen];
1745             }
1746 
1747             inputBuffer.get(value);
1748 
1749             switch (type) {
1750                 case ATTRIBUTE_TYPE_KEY_LENGTH:
1751                     return new Pair(new KeyLengthAttribute(value), length);
1752                 default:
1753                     return new Pair(new UnrecognizedAttribute(type, value), length);
1754             }
1755         }
1756 
1757         // Encode Attribute to a ByteBuffer.
encodeToByteBuffer(ByteBuffer byteBuffer)1758         protected abstract void encodeToByteBuffer(ByteBuffer byteBuffer);
1759 
1760         // Get entire Attribute length.
getAttributeLength()1761         protected abstract int getAttributeLength();
1762     }
1763 
1764     /** KeyLengthAttribute represents a Key Length type Attribute */
1765     public static final class KeyLengthAttribute extends Attribute {
1766         public final int keyLength;
1767 
KeyLengthAttribute(byte[] value)1768         protected KeyLengthAttribute(byte[] value) {
1769             this(Short.toUnsignedInt(ByteBuffer.wrap(value).getShort()));
1770         }
1771 
KeyLengthAttribute(int keyLength)1772         protected KeyLengthAttribute(int keyLength) {
1773             super(ATTRIBUTE_TYPE_KEY_LENGTH);
1774             this.keyLength = keyLength;
1775         }
1776 
1777         @Override
encodeToByteBuffer(ByteBuffer byteBuffer)1778         protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
1779             byteBuffer
1780                     .putShort((short) (ATTRIBUTE_FORMAT_TV | ATTRIBUTE_TYPE_KEY_LENGTH))
1781                     .putShort((short) keyLength);
1782         }
1783 
1784         @Override
getAttributeLength()1785         protected int getAttributeLength() {
1786             return TV_ATTRIBUTE_TOTAL_LEN;
1787         }
1788     }
1789 
1790     /**
1791      * UnrecognizedAttribute represents a Attribute with unrecoginzed Attribute Type.
1792      *
1793      * <p>Transforms containing UnrecognizedAttribute should be ignored.
1794      */
1795     protected static final class UnrecognizedAttribute extends Attribute {
UnrecognizedAttribute(int type, byte[] value)1796         protected UnrecognizedAttribute(int type, byte[] value) {
1797             super(type);
1798         }
1799 
1800         @Override
encodeToByteBuffer(ByteBuffer byteBuffer)1801         protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
1802             throw new UnsupportedOperationException(
1803                     "It is not supported to encode an unrecognized Attribute.");
1804         }
1805 
1806         @Override
getAttributeLength()1807         protected int getAttributeLength() {
1808             throw new UnsupportedOperationException(
1809                     "It is not supported to get length of an unrecognized Attribute.");
1810         }
1811     }
1812 
1813     /**
1814      * Encode SA payload to ByteBUffer.
1815      *
1816      * @param nextPayload type of payload that follows this payload.
1817      * @param byteBuffer destination ByteBuffer that stores encoded payload.
1818      */
1819     @Override
encodeToByteBuffer(@ayloadType int nextPayload, ByteBuffer byteBuffer)1820     protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
1821         encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
1822 
1823         for (int i = 0; i < proposalList.size(); i++) {
1824             // The last proposal has the isLast flag set to true.
1825             proposalList.get(i).encodeToByteBuffer(i == proposalList.size() - 1, byteBuffer);
1826         }
1827     }
1828 
1829     /**
1830      * Get entire payload length.
1831      *
1832      * @return entire payload length.
1833      */
1834     @Override
getPayloadLength()1835     protected int getPayloadLength() {
1836         int len = GENERIC_HEADER_LENGTH;
1837 
1838         for (Proposal p : proposalList) len += p.getProposalLength();
1839 
1840         return len;
1841     }
1842 
1843     /**
1844      * Return the payload type as a String.
1845      *
1846      * @return the payload type as a String.
1847      */
1848     @Override
getTypeString()1849     public String getTypeString() {
1850         return "SA";
1851     }
1852 
1853     @Override
1854     @NonNull
toString()1855     public String toString() {
1856         StringBuilder sb = new StringBuilder();
1857         if (isSaResponse) {
1858             sb.append("SA Response: ");
1859         } else {
1860             sb.append("SA Request: ");
1861         }
1862 
1863         int len = proposalList.size();
1864         for (int i = 0; i < len; i++) {
1865             sb.append(proposalList.get(i).toString());
1866             if (i < len - 1) sb.append(", ");
1867         }
1868 
1869         return sb.toString();
1870     }
1871 }
1872