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