• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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