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 android.annotation.Nullable; 20 import android.net.ipsec.ike.SaProposal; 21 import android.net.ipsec.ike.exceptions.IkeProtocolException; 22 23 import com.android.internal.net.ipsec.ike.IkeDhParams; 24 import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; 25 import com.android.internal.net.ipsec.ike.utils.RandomnessFactory; 26 import com.android.internal.net.utils.BigIntegerUtils; 27 28 import java.math.BigInteger; 29 import java.nio.ByteBuffer; 30 import java.security.GeneralSecurityException; 31 import java.security.InvalidAlgorithmParameterException; 32 import java.security.InvalidKeyException; 33 import java.security.KeyFactory; 34 import java.security.KeyPair; 35 import java.security.KeyPairGenerator; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.ProviderException; 38 import java.security.SecureRandom; 39 import java.security.spec.InvalidKeySpecException; 40 41 import javax.crypto.KeyAgreement; 42 import javax.crypto.interfaces.DHPrivateKey; 43 import javax.crypto.interfaces.DHPublicKey; 44 import javax.crypto.spec.DHParameterSpec; 45 import javax.crypto.spec.DHPrivateKeySpec; 46 import javax.crypto.spec.DHPublicKeySpec; 47 48 /** 49 * IkeKePayload represents a Key Exchange payload 50 * 51 * <p>This class provides methods for generating Diffie-Hellman value and doing Diffie-Hellman 52 * exhchange. Upper layer should ignore IkeKePayload with unsupported DH group type. 53 * 54 * @see <a href="https://tools.ietf.org/html/rfc7296#page-89">RFC 7296, Internet Key Exchange 55 * Protocol Version 2 (IKEv2)</a> 56 */ 57 public final class IkeKePayload extends IkePayload { 58 private static final int KE_HEADER_LEN = 4; 59 private static final int KE_HEADER_RESERVED = 0; 60 61 // Key exchange data length in octets 62 private static final int DH_GROUP_1024_BIT_MODP_DATA_LEN = 128; 63 private static final int DH_GROUP_2048_BIT_MODP_DATA_LEN = 256; 64 private static final int DH_GROUP_3072_BIT_MODP_DATA_LEN = 384; 65 private static final int DH_GROUP_4096_BIT_MODP_DATA_LEN = 512; 66 67 // Algorithm name of Diffie-Hellman 68 private static final String KEY_EXCHANGE_ALGORITHM = "DH"; 69 70 // TODO: Create a library initializer that checks if Provider supports DH algorithm. 71 72 /** Supported dhGroup falls into {@link DhGroup} */ 73 public final int dhGroup; 74 75 /** Public DH key for the recipient to calculate shared key. */ 76 public final byte[] keyExchangeData; 77 78 /** Flag indicates if this is an outbound payload. */ 79 public final boolean isOutbound; 80 81 /** 82 * localPrivateKey caches the locally generated private key when building an outbound KE 83 * payload. It will not be sent out. It is only used to calculate DH shared key when IKE library 84 * receives a public key from the remote server. 85 * 86 * <p>localPrivateKey of a inbound payload will be set to null. Caller MUST ensure its an 87 * outbound payload before using localPrivateKey. 88 */ 89 @Nullable public final DHPrivateKeySpec localPrivateKey; 90 91 /** 92 * Construct an instance of IkeKePayload in the context of IkePayloadFactory 93 * 94 * @param critical indicates if this payload is critical. Ignored in supported payload as 95 * instructed by the RFC 7296. 96 * @param payloadBody payload body in byte array 97 * @throws IkeProtocolException if there is any error 98 * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange 99 * Protocol Version 2 (IKEv2), Critical. 100 */ IkeKePayload(boolean critical, byte[] payloadBody)101 IkeKePayload(boolean critical, byte[] payloadBody) throws IkeProtocolException { 102 super(PAYLOAD_TYPE_KE, critical); 103 104 isOutbound = false; 105 localPrivateKey = null; 106 107 ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody); 108 109 dhGroup = Short.toUnsignedInt(inputBuffer.getShort()); 110 // Skip reserved field 111 inputBuffer.getShort(); 112 113 int dataSize = payloadBody.length - KE_HEADER_LEN; 114 // Check if dataSize matches the DH group type 115 boolean isValidSyntax = true; 116 switch (dhGroup) { 117 case SaProposal.DH_GROUP_1024_BIT_MODP: 118 isValidSyntax = DH_GROUP_1024_BIT_MODP_DATA_LEN == dataSize; 119 break; 120 case SaProposal.DH_GROUP_2048_BIT_MODP: 121 isValidSyntax = DH_GROUP_2048_BIT_MODP_DATA_LEN == dataSize; 122 break; 123 case SaProposal.DH_GROUP_3072_BIT_MODP: 124 isValidSyntax = DH_GROUP_3072_BIT_MODP_DATA_LEN == dataSize; 125 break; 126 case SaProposal.DH_GROUP_4096_BIT_MODP: 127 isValidSyntax = DH_GROUP_4096_BIT_MODP_DATA_LEN == dataSize; 128 break; 129 default: 130 // For unsupported DH group, we cannot check its syntax. Upper layer will ingore 131 // this payload. 132 } 133 if (!isValidSyntax) { 134 throw new InvalidSyntaxException("Invalid KE payload length for provided DH group."); 135 } 136 137 keyExchangeData = new byte[dataSize]; 138 inputBuffer.get(keyExchangeData); 139 } 140 141 /** 142 * Construct an instance of IkeKePayload for building an outbound packet. 143 * 144 * <p>Generate a DH key pair. Cache the private key and send out the public key as 145 * keyExchangeData. 146 * 147 * <p>Critical bit in this payload must not be set as instructed in RFC 7296. 148 * 149 * @param dh DH group for this KE payload 150 * @param randomnessFactory the randomness factory 151 * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange 152 * Protocol Version 2 (IKEv2), Critical. 153 */ IkeKePayload(@aProposal.DhGroup int dh, RandomnessFactory randomnessFactory)154 public IkeKePayload(@SaProposal.DhGroup int dh, RandomnessFactory randomnessFactory) { 155 super(PAYLOAD_TYPE_KE, false); 156 157 dhGroup = dh; 158 isOutbound = true; 159 160 BigInteger prime = BigInteger.ZERO; 161 int keySize = 0; 162 switch (dhGroup) { 163 case SaProposal.DH_GROUP_1024_BIT_MODP: 164 prime = 165 BigIntegerUtils.unsignedHexStringToBigInteger( 166 IkeDhParams.PRIME_1024_BIT_MODP); 167 keySize = DH_GROUP_1024_BIT_MODP_DATA_LEN; 168 break; 169 case SaProposal.DH_GROUP_2048_BIT_MODP: 170 prime = 171 BigIntegerUtils.unsignedHexStringToBigInteger( 172 IkeDhParams.PRIME_2048_BIT_MODP); 173 keySize = DH_GROUP_2048_BIT_MODP_DATA_LEN; 174 break; 175 case SaProposal.DH_GROUP_3072_BIT_MODP: 176 prime = 177 BigIntegerUtils.unsignedHexStringToBigInteger( 178 IkeDhParams.PRIME_3072_BIT_MODP); 179 keySize = DH_GROUP_3072_BIT_MODP_DATA_LEN; 180 break; 181 case SaProposal.DH_GROUP_4096_BIT_MODP: 182 prime = 183 BigIntegerUtils.unsignedHexStringToBigInteger( 184 IkeDhParams.PRIME_4096_BIT_MODP); 185 keySize = DH_GROUP_4096_BIT_MODP_DATA_LEN; 186 break; 187 default: 188 throw new IllegalArgumentException("DH group not supported: " + dh); 189 } 190 191 try { 192 BigInteger baseGen = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP); 193 DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen); 194 195 KeyPairGenerator dhKeyPairGen = KeyPairGenerator.getInstance(KEY_EXCHANGE_ALGORITHM); 196 197 SecureRandom random = randomnessFactory.getRandom(); 198 random = random == null ? new SecureRandom() : random; 199 dhKeyPairGen.initialize(dhParams, random); 200 201 KeyPair keyPair = dhKeyPairGen.generateKeyPair(); 202 203 DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate(); 204 DHPrivateKeySpec dhPrivateKeyspec = 205 new DHPrivateKeySpec(privateKey.getX(), prime, baseGen); 206 DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic(); 207 208 // Zero-pad the public key without the sign bit 209 keyExchangeData = 210 BigIntegerUtils.bigIntegerToUnsignedByteArray(publicKey.getY(), keySize); 211 localPrivateKey = dhPrivateKeyspec; 212 } catch (NoSuchAlgorithmException e) { 213 throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM, e); 214 } catch (InvalidAlgorithmParameterException e) { 215 throw new IllegalArgumentException("Failed to initialize key generator", e); 216 } 217 } 218 219 /** 220 * Encode KE payload to ByteBuffer. 221 * 222 * @param nextPayload type of payload that follows this payload. 223 * @param byteBuffer destination ByteBuffer that stores encoded payload. 224 */ 225 @Override encodeToByteBuffer(@ayloadType int nextPayload, ByteBuffer byteBuffer)226 protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) { 227 encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer); 228 byteBuffer 229 .putShort((short) dhGroup) 230 .putShort((short) KE_HEADER_RESERVED) 231 .put(keyExchangeData); 232 } 233 234 /** 235 * Get entire payload length. 236 * 237 * @return entire payload length. 238 */ 239 @Override getPayloadLength()240 protected int getPayloadLength() { 241 return GENERIC_HEADER_LENGTH + KE_HEADER_LEN + keyExchangeData.length; 242 } 243 244 /** 245 * Calculate the shared secret. 246 * 247 * @param privateKeySpec contains the local private key, DH prime and DH base generator. 248 * @param remotePublicKey the public key from remote server. 249 * @throws GeneralSecurityException if the remote public key is invalid. 250 */ getSharedKey(DHPrivateKeySpec privateKeySpec, byte[] remotePublicKey)251 public static byte[] getSharedKey(DHPrivateKeySpec privateKeySpec, byte[] remotePublicKey) 252 throws GeneralSecurityException { 253 KeyAgreement dhKeyAgreement; 254 KeyFactory dhKeyFactory; 255 try { 256 // Apply local private key. 257 dhKeyAgreement = KeyAgreement.getInstance(KEY_EXCHANGE_ALGORITHM); 258 dhKeyFactory = KeyFactory.getInstance(KEY_EXCHANGE_ALGORITHM); 259 DHPrivateKey privateKey = (DHPrivateKey) dhKeyFactory.generatePrivate(privateKeySpec); 260 dhKeyAgreement.init(privateKey); 261 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException e) { 262 throw new IllegalArgumentException("Failed to generate DH private key", e); 263 } 264 265 // Build public key. 266 BigInteger publicKeyValue = BigIntegerUtils.unsignedByteArrayToBigInteger(remotePublicKey); 267 BigInteger primeValue = privateKeySpec.getP(); 268 BigInteger baseGenValue = privateKeySpec.getG(); 269 DHPublicKeySpec publicKeySpec = 270 new DHPublicKeySpec(publicKeyValue, primeValue, baseGenValue); 271 272 // Validate and apply public key. Validation includes range check as instructed by RFC6989 273 // section 2.1 274 DHPublicKey publicKey = (DHPublicKey) dhKeyFactory.generatePublic(publicKeySpec); 275 276 dhKeyAgreement.doPhase(publicKey, true /* Last phase */); 277 return dhKeyAgreement.generateSecret(); 278 } 279 280 /** 281 * Return the payload type as a String. 282 * 283 * @return the payload type as a String. 284 */ 285 @Override getTypeString()286 public String getTypeString() { 287 return "KE"; 288 } 289 } 290