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.SaProposal.DH_GROUP_1024_BIT_MODP; 20 import static android.net.ipsec.ike.SaProposal.DH_GROUP_1536_BIT_MODP; 21 import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP; 22 import static android.net.ipsec.ike.SaProposal.DH_GROUP_3072_BIT_MODP; 23 import static android.net.ipsec.ike.SaProposal.DH_GROUP_4096_BIT_MODP; 24 import static android.net.ipsec.ike.SaProposal.DH_GROUP_CURVE_25519; 25 26 import static com.android.internal.net.utils.BigIntegerUtils.unsignedHexStringToBigInteger; 27 28 import android.annotation.Nullable; 29 import android.net.ipsec.ike.SaProposal; 30 import android.net.ipsec.ike.exceptions.IkeProtocolException; 31 import android.net.ipsec.ike.exceptions.InvalidSyntaxException; 32 import android.util.SparseArray; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.net.ipsec.ike.IkeDhParams; 36 import com.android.internal.net.ipsec.ike.utils.RandomnessFactory; 37 import com.android.internal.net.utils.BigIntegerUtils; 38 39 import java.math.BigInteger; 40 import java.nio.ByteBuffer; 41 import java.security.GeneralSecurityException; 42 import java.security.InvalidAlgorithmParameterException; 43 import java.security.InvalidKeyException; 44 import java.security.KeyFactory; 45 import java.security.KeyPair; 46 import java.security.KeyPairGenerator; 47 import java.security.NoSuchAlgorithmException; 48 import java.security.NoSuchProviderException; 49 import java.security.PrivateKey; 50 import java.security.ProviderException; 51 import java.security.PublicKey; 52 import java.security.SecureRandom; 53 import java.security.spec.X509EncodedKeySpec; 54 import java.util.Arrays; 55 56 import javax.crypto.KeyAgreement; 57 import javax.crypto.interfaces.DHPrivateKey; 58 import javax.crypto.interfaces.DHPublicKey; 59 import javax.crypto.spec.DHParameterSpec; 60 import javax.crypto.spec.DHPublicKeySpec; 61 62 /** 63 * IkeKePayload represents a Key Exchange payload 64 * 65 * <p>This class provides methods for generating Diffie-Hellman value and doing Diffie-Hellman 66 * exhchange. Upper layer should ignore IkeKePayload with unsupported DH group type. 67 * 68 * @see <a href="https://tools.ietf.org/html/rfc7296#page-89">RFC 7296, Internet Key Exchange 69 * Protocol Version 2 (IKEv2)</a> 70 */ 71 public final class IkeKePayload extends IkePayload { 72 private static final int KE_HEADER_LEN = 4; 73 private static final int KE_HEADER_RESERVED = 0; 74 75 // Key exchange data length in octets 76 private static final int DH_GROUP_1024_BIT_MODP_PUBLIC_KEY_LEN = 128; 77 private static final int DH_GROUP_1536_BIT_MODP_PUBLIC_KEY_LEN = 192; 78 private static final int DH_GROUP_2048_BIT_MODP_PUBLIC_KEY_LEN = 256; 79 private static final int DH_GROUP_3072_BIT_MODP_PUBLIC_KEY_LEN = 384; 80 private static final int DH_GROUP_4096_BIT_MODP_PUBLIC_KEY_LEN = 512; 81 private static final int DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN = 32; 82 83 private static final SparseArray<Integer> PUBLIC_KEY_LEN_MAP = new SparseArray<>(); 84 85 static { PUBLIC_KEY_LEN_MAP.put(DH_GROUP_1024_BIT_MODP, DH_GROUP_1024_BIT_MODP_PUBLIC_KEY_LEN)86 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_1024_BIT_MODP, DH_GROUP_1024_BIT_MODP_PUBLIC_KEY_LEN); PUBLIC_KEY_LEN_MAP.put(DH_GROUP_1536_BIT_MODP, DH_GROUP_1536_BIT_MODP_PUBLIC_KEY_LEN)87 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_1536_BIT_MODP, DH_GROUP_1536_BIT_MODP_PUBLIC_KEY_LEN); PUBLIC_KEY_LEN_MAP.put(DH_GROUP_2048_BIT_MODP, DH_GROUP_2048_BIT_MODP_PUBLIC_KEY_LEN)88 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_2048_BIT_MODP, DH_GROUP_2048_BIT_MODP_PUBLIC_KEY_LEN); PUBLIC_KEY_LEN_MAP.put(DH_GROUP_3072_BIT_MODP, DH_GROUP_3072_BIT_MODP_PUBLIC_KEY_LEN)89 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_3072_BIT_MODP, DH_GROUP_3072_BIT_MODP_PUBLIC_KEY_LEN); PUBLIC_KEY_LEN_MAP.put(DH_GROUP_4096_BIT_MODP, DH_GROUP_4096_BIT_MODP_PUBLIC_KEY_LEN)90 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_4096_BIT_MODP, DH_GROUP_4096_BIT_MODP_PUBLIC_KEY_LEN); PUBLIC_KEY_LEN_MAP.put(DH_GROUP_CURVE_25519, DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN)91 PUBLIC_KEY_LEN_MAP.put(DH_GROUP_CURVE_25519, DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN); 92 } 93 94 private static final SparseArray<BigInteger> MODP_PRIME_MAP = new SparseArray<>(); 95 96 static { MODP_PRIME_MAP.put( DH_GROUP_1024_BIT_MODP, unsignedHexStringToBigInteger(IkeDhParams.PRIME_1024_BIT_MODP))97 MODP_PRIME_MAP.put( 98 DH_GROUP_1024_BIT_MODP, 99 unsignedHexStringToBigInteger(IkeDhParams.PRIME_1024_BIT_MODP)); MODP_PRIME_MAP.put( DH_GROUP_1536_BIT_MODP, unsignedHexStringToBigInteger(IkeDhParams.PRIME_1536_BIT_MODP))100 MODP_PRIME_MAP.put( 101 DH_GROUP_1536_BIT_MODP, 102 unsignedHexStringToBigInteger(IkeDhParams.PRIME_1536_BIT_MODP)); MODP_PRIME_MAP.put( DH_GROUP_2048_BIT_MODP, unsignedHexStringToBigInteger(IkeDhParams.PRIME_2048_BIT_MODP))103 MODP_PRIME_MAP.put( 104 DH_GROUP_2048_BIT_MODP, 105 unsignedHexStringToBigInteger(IkeDhParams.PRIME_2048_BIT_MODP)); MODP_PRIME_MAP.put( DH_GROUP_3072_BIT_MODP, unsignedHexStringToBigInteger(IkeDhParams.PRIME_3072_BIT_MODP))106 MODP_PRIME_MAP.put( 107 DH_GROUP_3072_BIT_MODP, 108 unsignedHexStringToBigInteger(IkeDhParams.PRIME_3072_BIT_MODP)); MODP_PRIME_MAP.put( DH_GROUP_4096_BIT_MODP, unsignedHexStringToBigInteger(IkeDhParams.PRIME_4096_BIT_MODP))109 MODP_PRIME_MAP.put( 110 DH_GROUP_4096_BIT_MODP, 111 unsignedHexStringToBigInteger(IkeDhParams.PRIME_4096_BIT_MODP)); 112 } 113 114 // Invariable header of an X509 format Curve 25519 public key defined in RFC8410 115 private static final byte[] CURVE_25519_X509_PUB_KEY_HEADER = { 116 (byte) 0x30, (byte) 0x2a, (byte) 0x30, (byte) 0x05, 117 (byte) 0x06, (byte) 0x03, (byte) 0x2b, (byte) 0x65, 118 (byte) 0x6e, (byte) 0x03, (byte) 0x21, (byte) 0x00 119 }; 120 121 // Algorithm name of Diffie-Hellman 122 private static final String KEY_EXCHANGE_ALGORITHM_MODP = "DH"; 123 124 // Currently java does not support "ECDH", thus using AndroidOpenSSL (Conscrypt) provided "XDH" 125 // who has the same key exchange flow. 126 private static final String KEY_EXCHANGE_ALGORITHM_CURVE = "XDH"; 127 private static final String KEY_EXCHANGE_CURVE_PROVIDER = "AndroidOpenSSL"; 128 129 // TODO: Create a library initializer that checks if Provider supports DH algorithm. 130 131 /** Supported dhGroup falls into {@link DhGroup} */ 132 public final int dhGroup; 133 134 /** Public DH key for the recipient to calculate shared key. */ 135 public final byte[] keyExchangeData; 136 137 /** Flag indicates if this is an outbound payload. */ 138 public final boolean isOutbound; 139 140 /** 141 * localPrivateKey caches the locally generated private key when building an outbound KE 142 * payload. It will not be sent out. It is only used to calculate DH shared key when IKE library 143 * receives a public key from the remote server. 144 * 145 * <p>localPrivateKey of a inbound payload will be set to null. Caller MUST ensure its an 146 * outbound payload before using localPrivateKey. 147 */ 148 @Nullable public final PrivateKey localPrivateKey; 149 150 /** 151 * Construct an instance of IkeKePayload in the context of IkePayloadFactory 152 * 153 * @param critical indicates if this payload is critical. Ignored in supported payload as 154 * instructed by the RFC 7296. 155 * @param payloadBody payload body in byte array 156 * @throws IkeProtocolException if there is any error 157 * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange 158 * Protocol Version 2 (IKEv2), Critical. 159 */ 160 @VisibleForTesting IkeKePayload(boolean critical, byte[] payloadBody)161 public IkeKePayload(boolean critical, byte[] payloadBody) throws IkeProtocolException { 162 super(PAYLOAD_TYPE_KE, critical); 163 164 isOutbound = false; 165 localPrivateKey = null; 166 167 ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody); 168 169 dhGroup = Short.toUnsignedInt(inputBuffer.getShort()); 170 if (!PUBLIC_KEY_LEN_MAP.contains(dhGroup)) { 171 throw new IllegalArgumentException("Invalid DH group " + dhGroup); 172 } 173 174 // Skip reserved field 175 inputBuffer.getShort(); 176 177 int dataSize = payloadBody.length - KE_HEADER_LEN; 178 179 // Check if dataSize matches the DH group type 180 if (dataSize != PUBLIC_KEY_LEN_MAP.get(dhGroup)) { 181 throw new InvalidSyntaxException("Invalid KE payload length for provided DH group."); 182 } 183 184 keyExchangeData = new byte[dataSize]; 185 inputBuffer.get(keyExchangeData); 186 } 187 188 /** Constructor for building an outbound KE payload. */ IkeKePayload(int dhGroup, byte[] keyExchangeData, PrivateKey localPrivateKey)189 private IkeKePayload(int dhGroup, byte[] keyExchangeData, PrivateKey localPrivateKey) { 190 super(PAYLOAD_TYPE_KE, true /* critical */); 191 this.dhGroup = dhGroup; 192 this.isOutbound = true; 193 this.keyExchangeData = keyExchangeData; 194 this.localPrivateKey = localPrivateKey; 195 } 196 197 /** 198 * Construct an instance of IkeKePayload for building an outbound packet. 199 * 200 * <p>Generate a DH key pair. Cache the private key and send out the public key as 201 * keyExchangeData. 202 * 203 * <p>Critical bit in this payload must not be set as instructed in RFC 7296. 204 * 205 * @param dh DH group for this KE payload 206 * @param randomnessFactory the randomness factory 207 * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange 208 * Protocol Version 2 (IKEv2), Critical. 209 */ createOutboundKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)210 public static IkeKePayload createOutboundKePayload( 211 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 212 switch (dh) { 213 case SaProposal.DH_GROUP_1024_BIT_MODP: // fall through 214 case SaProposal.DH_GROUP_1536_BIT_MODP: // fall through 215 case SaProposal.DH_GROUP_2048_BIT_MODP: // fall through 216 case SaProposal.DH_GROUP_3072_BIT_MODP: // fall through 217 case SaProposal.DH_GROUP_4096_BIT_MODP: // fall through 218 return createOutboundModpKePayload(dh, randomnessFactory); 219 case SaProposal.DH_GROUP_CURVE_25519: 220 return createOutboundCurveKePayload(dh, randomnessFactory); 221 default: 222 throw new IllegalArgumentException("Unsupported DH group: " + dh); 223 } 224 } 225 createOutboundModpKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)226 private static IkeKePayload createOutboundModpKePayload( 227 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 228 BigInteger prime = MODP_PRIME_MAP.get(dh); 229 int keySize = PUBLIC_KEY_LEN_MAP.get(dh); 230 231 try { 232 BigInteger baseGen = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); 233 DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen); 234 235 KeyPairGenerator dhKeyPairGen = 236 KeyPairGenerator.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 237 238 SecureRandom random = randomnessFactory.getRandom(); 239 random = random == null ? new SecureRandom() : random; 240 dhKeyPairGen.initialize(dhParams, random); 241 242 KeyPair keyPair = dhKeyPairGen.generateKeyPair(); 243 244 PrivateKey localPrivateKey = (DHPrivateKey) keyPair.getPrivate(); 245 DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic(); 246 247 // Zero-pad the public key without the sign bit 248 byte[] keyExchangeData = 249 BigIntegerUtils.bigIntegerToUnsignedByteArray(publicKey.getY(), keySize); 250 251 return new IkeKePayload(dh, keyExchangeData, localPrivateKey); 252 } catch (NoSuchAlgorithmException e) { 253 throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM_MODP, e); 254 } catch (InvalidAlgorithmParameterException e) { 255 throw new IllegalArgumentException("Failed to initialize key generator", e); 256 } 257 } 258 createOutboundCurveKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)259 private static IkeKePayload createOutboundCurveKePayload( 260 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 261 try { 262 KeyPairGenerator dhKeyPairGen = 263 KeyPairGenerator.getInstance( 264 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 265 KeyPair keyPair = dhKeyPairGen.generateKeyPair(); 266 267 PrivateKey privateKey = keyPair.getPrivate(); 268 PublicKey publicKey = keyPair.getPublic(); 269 byte[] x509EncodedPubKeyBytes = publicKey.getEncoded(); 270 byte[] keyExchangeData = 271 Arrays.copyOfRange( 272 x509EncodedPubKeyBytes, 273 CURVE_25519_X509_PUB_KEY_HEADER.length, 274 x509EncodedPubKeyBytes.length); 275 276 return new IkeKePayload(dh, keyExchangeData, privateKey); 277 } catch (NoSuchAlgorithmException | NoSuchProviderException e) { 278 throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM_CURVE, e); 279 } 280 } 281 282 /** 283 * Encode KE payload to ByteBuffer. 284 * 285 * @param nextPayload type of payload that follows this payload. 286 * @param byteBuffer destination ByteBuffer that stores encoded payload. 287 */ 288 @Override encodeToByteBuffer(@ayloadType int nextPayload, ByteBuffer byteBuffer)289 protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) { 290 encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer); 291 byteBuffer 292 .putShort((short) dhGroup) 293 .putShort((short) KE_HEADER_RESERVED) 294 .put(keyExchangeData); 295 } 296 297 /** 298 * Get entire payload length. 299 * 300 * @return entire payload length. 301 */ 302 @Override getPayloadLength()303 protected int getPayloadLength() { 304 return GENERIC_HEADER_LENGTH + KE_HEADER_LEN + keyExchangeData.length; 305 } 306 307 /** 308 * Calculate the shared secret. 309 * 310 * @param privateKey the local private key. 311 * @param remotePublicKey the public key from remote server. 312 * @param dhGroup the DH group. 313 * @throws GeneralSecurityException if the remote public key is invalid. 314 */ getSharedKey(PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)315 public static byte[] getSharedKey(PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 316 throws GeneralSecurityException { 317 switch (dhGroup) { 318 case SaProposal.DH_GROUP_1024_BIT_MODP: // fall through 319 case SaProposal.DH_GROUP_1536_BIT_MODP: // fall through 320 case SaProposal.DH_GROUP_2048_BIT_MODP: // fall through 321 case SaProposal.DH_GROUP_3072_BIT_MODP: // fall through 322 case SaProposal.DH_GROUP_4096_BIT_MODP: // fall through 323 return getModpSharedKey(privateKey, remotePublicKey, dhGroup); 324 case SaProposal.DH_GROUP_CURVE_25519: 325 return getCurveSharedKey(privateKey, remotePublicKey, dhGroup); 326 default: 327 throw new IllegalArgumentException("Invalid DH group: " + dhGroup); 328 } 329 } 330 getModpSharedKey( PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)331 private static byte[] getModpSharedKey( 332 PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 333 throws GeneralSecurityException { 334 KeyAgreement dhKeyAgreement; 335 KeyFactory dhKeyFactory; 336 try { 337 dhKeyAgreement = KeyAgreement.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 338 dhKeyFactory = KeyFactory.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 339 340 // Apply local private key. 341 dhKeyAgreement.init(privateKey); 342 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 343 throw new IllegalArgumentException("Failed to construct or initialize KeyAgreement", e); 344 } 345 346 // Build public key. 347 BigInteger publicKeyValue = BigIntegerUtils.unsignedByteArrayToBigInteger(remotePublicKey); 348 BigInteger primeValue = MODP_PRIME_MAP.get(dhGroup); 349 BigInteger baseGenValue = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); 350 DHPublicKeySpec publicKeySpec = 351 new DHPublicKeySpec(publicKeyValue, primeValue, baseGenValue); 352 353 // Validate and apply public key. Validation includes range check as instructed by RFC6989 354 // section 2.1 355 DHPublicKey publicKey = (DHPublicKey) dhKeyFactory.generatePublic(publicKeySpec); 356 357 dhKeyAgreement.doPhase(publicKey, true /* Last phase */); 358 return dhKeyAgreement.generateSecret(); 359 } 360 getCurveSharedKey( PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)361 private static byte[] getCurveSharedKey( 362 PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 363 throws GeneralSecurityException { 364 KeyAgreement keyAgreement; 365 KeyFactory keyFactory; 366 try { 367 keyAgreement = 368 KeyAgreement.getInstance( 369 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 370 keyFactory = 371 KeyFactory.getInstance( 372 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 373 374 // Apply local private key. 375 keyAgreement.init(privateKey); 376 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 377 throw new IllegalArgumentException("Failed to construct or initialize KeyAgreement", e); 378 } 379 380 final byte[] x509EncodedPubKeyBytes = 381 new byte 382 [CURVE_25519_X509_PUB_KEY_HEADER.length 383 + DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN]; 384 System.arraycopy( 385 CURVE_25519_X509_PUB_KEY_HEADER, 386 0, 387 x509EncodedPubKeyBytes, 388 0, 389 CURVE_25519_X509_PUB_KEY_HEADER.length); 390 System.arraycopy( 391 remotePublicKey, 392 0, 393 x509EncodedPubKeyBytes, 394 CURVE_25519_X509_PUB_KEY_HEADER.length, 395 DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN); 396 397 PublicKey publicKey = 398 keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPubKeyBytes)); 399 keyAgreement.doPhase(publicKey, true /* Last phase */); 400 return keyAgreement.generateSecret(); 401 } 402 403 /** 404 * Return the payload type as a String. 405 * 406 * @return the payload type as a String. 407 */ 408 @Override getTypeString()409 public String getTypeString() { 410 return "KE"; 411 } 412 } 413