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