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 171 // Skip reserved field 172 inputBuffer.getShort(); 173 174 int dataSize = payloadBody.length - KE_HEADER_LEN; 175 176 // If DH group is recognized, check if dataSize matches the DH group type 177 if (PUBLIC_KEY_LEN_MAP.contains(dhGroup) && dataSize != PUBLIC_KEY_LEN_MAP.get(dhGroup)) { 178 throw new InvalidSyntaxException( 179 "Expecting data size to be " 180 + PUBLIC_KEY_LEN_MAP.get(dhGroup) 181 + " but found " 182 + dataSize); 183 } 184 185 keyExchangeData = new byte[dataSize]; 186 inputBuffer.get(keyExchangeData); 187 } 188 189 /** Constructor for building an outbound KE payload. */ IkeKePayload(int dhGroup, byte[] keyExchangeData, PrivateKey localPrivateKey)190 private IkeKePayload(int dhGroup, byte[] keyExchangeData, PrivateKey localPrivateKey) { 191 super(PAYLOAD_TYPE_KE, true /* critical */); 192 this.dhGroup = dhGroup; 193 this.isOutbound = true; 194 this.keyExchangeData = keyExchangeData; 195 this.localPrivateKey = localPrivateKey; 196 } 197 198 /** 199 * Construct an instance of IkeKePayload for building an outbound packet. 200 * 201 * <p>Generate a DH key pair. Cache the private key and send out the public key as 202 * keyExchangeData. 203 * 204 * <p>Critical bit in this payload must not be set as instructed in RFC 7296. 205 * 206 * @param dh DH group for this KE payload 207 * @param randomnessFactory the randomness factory 208 * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange 209 * Protocol Version 2 (IKEv2), Critical. 210 */ createOutboundKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)211 public static IkeKePayload createOutboundKePayload( 212 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 213 switch (dh) { 214 case SaProposal.DH_GROUP_1024_BIT_MODP: // fall through 215 case SaProposal.DH_GROUP_1536_BIT_MODP: // fall through 216 case SaProposal.DH_GROUP_2048_BIT_MODP: // fall through 217 case SaProposal.DH_GROUP_3072_BIT_MODP: // fall through 218 case SaProposal.DH_GROUP_4096_BIT_MODP: // fall through 219 return createOutboundModpKePayload(dh, randomnessFactory); 220 case SaProposal.DH_GROUP_CURVE_25519: 221 return createOutboundCurveKePayload(dh, randomnessFactory); 222 default: 223 throw new IllegalArgumentException("Unsupported DH group: " + dh); 224 } 225 } 226 createOutboundModpKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)227 private static IkeKePayload createOutboundModpKePayload( 228 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 229 BigInteger prime = MODP_PRIME_MAP.get(dh); 230 int keySize = PUBLIC_KEY_LEN_MAP.get(dh); 231 232 try { 233 BigInteger baseGen = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); 234 DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen); 235 236 KeyPairGenerator dhKeyPairGen = 237 KeyPairGenerator.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 238 239 SecureRandom random = randomnessFactory.getRandom(); 240 random = random == null ? new SecureRandom() : random; 241 dhKeyPairGen.initialize(dhParams, random); 242 243 KeyPair keyPair = dhKeyPairGen.generateKeyPair(); 244 245 PrivateKey localPrivateKey = (DHPrivateKey) keyPair.getPrivate(); 246 DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic(); 247 248 // Zero-pad the public key without the sign bit 249 byte[] keyExchangeData = 250 BigIntegerUtils.bigIntegerToUnsignedByteArray(publicKey.getY(), keySize); 251 252 return new IkeKePayload(dh, keyExchangeData, localPrivateKey); 253 } catch (NoSuchAlgorithmException e) { 254 throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM_MODP, e); 255 } catch (InvalidAlgorithmParameterException e) { 256 throw new IllegalArgumentException("Failed to initialize key generator", e); 257 } 258 } 259 createOutboundCurveKePayload( @aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)260 private static IkeKePayload createOutboundCurveKePayload( 261 @SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 262 try { 263 KeyPairGenerator dhKeyPairGen = 264 KeyPairGenerator.getInstance( 265 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 266 KeyPair keyPair = dhKeyPairGen.generateKeyPair(); 267 268 PrivateKey privateKey = keyPair.getPrivate(); 269 PublicKey publicKey = keyPair.getPublic(); 270 byte[] x509EncodedPubKeyBytes = publicKey.getEncoded(); 271 byte[] keyExchangeData = 272 Arrays.copyOfRange( 273 x509EncodedPubKeyBytes, 274 CURVE_25519_X509_PUB_KEY_HEADER.length, 275 x509EncodedPubKeyBytes.length); 276 277 return new IkeKePayload(dh, keyExchangeData, privateKey); 278 } catch (NoSuchAlgorithmException | NoSuchProviderException e) { 279 throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM_CURVE, e); 280 } 281 } 282 283 /** 284 * Encode KE payload to ByteBuffer. 285 * 286 * @param nextPayload type of payload that follows this payload. 287 * @param byteBuffer destination ByteBuffer that stores encoded payload. 288 */ 289 @Override encodeToByteBuffer(@ayloadType int nextPayload, ByteBuffer byteBuffer)290 protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) { 291 encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer); 292 byteBuffer 293 .putShort((short) dhGroup) 294 .putShort((short) KE_HEADER_RESERVED) 295 .put(keyExchangeData); 296 } 297 298 /** 299 * Get entire payload length. 300 * 301 * @return entire payload length. 302 */ 303 @Override getPayloadLength()304 protected int getPayloadLength() { 305 return GENERIC_HEADER_LENGTH + KE_HEADER_LEN + keyExchangeData.length; 306 } 307 308 /** 309 * Calculate the shared secret. 310 * 311 * @param privateKey the local private key. 312 * @param remotePublicKey the public key from remote server. 313 * @param dhGroup the DH group. 314 * @throws GeneralSecurityException if the remote public key is invalid. 315 */ getSharedKey(PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)316 public static byte[] getSharedKey(PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 317 throws GeneralSecurityException { 318 switch (dhGroup) { 319 case SaProposal.DH_GROUP_1024_BIT_MODP: // fall through 320 case SaProposal.DH_GROUP_1536_BIT_MODP: // fall through 321 case SaProposal.DH_GROUP_2048_BIT_MODP: // fall through 322 case SaProposal.DH_GROUP_3072_BIT_MODP: // fall through 323 case SaProposal.DH_GROUP_4096_BIT_MODP: // fall through 324 return getModpSharedKey(privateKey, remotePublicKey, dhGroup); 325 case SaProposal.DH_GROUP_CURVE_25519: 326 return getCurveSharedKey(privateKey, remotePublicKey, dhGroup); 327 default: 328 throw new IllegalArgumentException("Invalid DH group: " + dhGroup); 329 } 330 } 331 getModpSharedKey( PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)332 private static byte[] getModpSharedKey( 333 PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 334 throws GeneralSecurityException { 335 KeyAgreement dhKeyAgreement; 336 KeyFactory dhKeyFactory; 337 try { 338 dhKeyAgreement = KeyAgreement.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 339 dhKeyFactory = KeyFactory.getInstance(KEY_EXCHANGE_ALGORITHM_MODP); 340 341 // Apply local private key. 342 dhKeyAgreement.init(privateKey); 343 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 344 throw new IllegalArgumentException("Failed to construct or initialize KeyAgreement", e); 345 } 346 347 // Build public key. 348 BigInteger publicKeyValue = BigIntegerUtils.unsignedByteArrayToBigInteger(remotePublicKey); 349 BigInteger primeValue = MODP_PRIME_MAP.get(dhGroup); 350 BigInteger baseGenValue = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); 351 DHPublicKeySpec publicKeySpec = 352 new DHPublicKeySpec(publicKeyValue, primeValue, baseGenValue); 353 354 // Validate and apply public key. Validation includes range check as instructed by RFC6989 355 // section 2.1 356 DHPublicKey publicKey = (DHPublicKey) dhKeyFactory.generatePublic(publicKeySpec); 357 358 dhKeyAgreement.doPhase(publicKey, true /* Last phase */); 359 return dhKeyAgreement.generateSecret(); 360 } 361 getCurveSharedKey( PrivateKey privateKey, byte[] remotePublicKey, int dhGroup)362 private static byte[] getCurveSharedKey( 363 PrivateKey privateKey, byte[] remotePublicKey, int dhGroup) 364 throws GeneralSecurityException { 365 KeyAgreement keyAgreement; 366 KeyFactory keyFactory; 367 try { 368 keyAgreement = 369 KeyAgreement.getInstance( 370 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 371 keyFactory = 372 KeyFactory.getInstance( 373 KEY_EXCHANGE_ALGORITHM_CURVE, KEY_EXCHANGE_CURVE_PROVIDER); 374 375 // Apply local private key. 376 keyAgreement.init(privateKey); 377 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 378 throw new IllegalArgumentException("Failed to construct or initialize KeyAgreement", e); 379 } 380 381 final byte[] x509EncodedPubKeyBytes = 382 new byte 383 [CURVE_25519_X509_PUB_KEY_HEADER.length 384 + DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN]; 385 System.arraycopy( 386 CURVE_25519_X509_PUB_KEY_HEADER, 387 0, 388 x509EncodedPubKeyBytes, 389 0, 390 CURVE_25519_X509_PUB_KEY_HEADER.length); 391 System.arraycopy( 392 remotePublicKey, 393 0, 394 x509EncodedPubKeyBytes, 395 CURVE_25519_X509_PUB_KEY_HEADER.length, 396 DH_GROUP_CURVE_25519_PUBLIC_KEY_LEN); 397 398 PublicKey publicKey = 399 keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPubKeyBytes)); 400 keyAgreement.doPhase(publicKey, true /* Last phase */); 401 return keyAgreement.generateSecret(); 402 } 403 404 /** 405 * Return the payload type as a String. 406 * 407 * @return the payload type as a String. 408 */ 409 @Override getTypeString()410 public String getTypeString() { 411 return "KE"; 412 } 413 } 414