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