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