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