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