1 /*
2  * Copyright (C) 2019 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.crypto;
18 
19 import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_CMAC;
20 import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
21 
22 import android.net.ipsec.ike.SaProposal;
23 
24 import com.android.internal.net.crypto.KeyGenerationUtils;
25 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
26 
27 import java.nio.ByteBuffer;
28 import java.security.GeneralSecurityException;
29 import java.util.Arrays;
30 
31 import javax.crypto.Cipher;
32 import javax.crypto.Mac;
33 
34 /**
35  * IkeMacPrf represents a negotiated pseudorandom function.
36  *
37  * <p>Pseudorandom function is usually used for IKE SA authentication and generating keying
38  * materials.
39  *
40  * <p>For pseudorandom functions based on integrity algorithms, all operations will be done by a
41  * {@link Mac}. For pseudorandom functions based on encryption algorithms, all operations will be
42  * done by a {@link Cipher}.
43  *
44  * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key Exchange
45  *     Protocol Version 2 (IKEv2)</a>
46  */
47 public class IkeMacPrf extends IkeMac {
48     private static final int PSEUDORANDOM_FUNCTION_AES128_XCBC_KEY_LEN = 16;
49 
IkeMacPrf( @aProposal.PseudorandomFunction int algorithmId, int keyLength, String algorithmName, boolean isJceSupported)50     private IkeMacPrf(
51             @SaProposal.PseudorandomFunction int algorithmId,
52             int keyLength,
53             String algorithmName,
54             boolean isJceSupported) {
55         super(algorithmId, keyLength, algorithmName, isJceSupported);
56     }
57 
58     /**
59      * Construct an instance of IkeMacPrf.
60      *
61      * @param prfTransform the valid negotiated PrfTransform.
62      * @return an instance of IkeMacPrf.
63      */
create(PrfTransform prfTransform)64     public static IkeMacPrf create(PrfTransform prfTransform) {
65         int algorithmId = prfTransform.id;
66 
67         int keyLength = 0;
68         String algorithmName = "";
69         boolean isJceSupported = true;
70 
71         switch (algorithmId) {
72             case SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1:
73                 keyLength = 20;
74                 algorithmName = "HmacSHA1";
75                 break;
76             case SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC:
77                 keyLength = 16;
78                 isJceSupported = false;
79                 algorithmName = ALGO_NAME_JCE_UNSUPPORTED;
80                 break;
81             case SaProposal.PSEUDORANDOM_FUNCTION_AES128_CMAC:
82                 keyLength = 16;
83                 algorithmName = "AESCMAC";
84                 break;
85             case SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256:
86                 keyLength = 32;
87                 algorithmName = "HmacSHA256";
88                 break;
89             case SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384:
90                 keyLength = 48;
91                 algorithmName = "HmacSHA384";
92                 break;
93             case SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512:
94                 keyLength = 64;
95                 algorithmName = "HmacSHA512";
96                 break;
97             default:
98                 throw new IllegalArgumentException("Unrecognized PRF ID: " + algorithmId);
99         }
100 
101         return new IkeMacPrf(algorithmId, keyLength, algorithmName, isJceSupported);
102     }
103 
104     @Override
signBytes(byte[] keyBytes, byte[] dataToSign)105     public byte[] signBytes(byte[] keyBytes, byte[] dataToSign) {
106         if (getAlgorithmId() == PSEUDORANDOM_FUNCTION_AES128_XCBC) {
107             try {
108                 keyBytes = modifyAesXCbcKeyIfNeeded(keyBytes);
109                 return new AesXCbcImpl().mac(keyBytes, dataToSign, false /*needTruncation*/);
110             } catch (GeneralSecurityException | IllegalStateException e) {
111                 throw new IllegalArgumentException("Failed to generate MAC: ", e);
112             }
113         } else if (getAlgorithmId() == PSEUDORANDOM_FUNCTION_AES128_CMAC) {
114             keyBytes = modifyAesCmacKeyIfNeeded(keyBytes);
115         }
116 
117         return super.signBytes(keyBytes, dataToSign);
118     }
119 
modifyAesXCbcKeyIfNeeded(byte[] keyBytes)120     private byte[] modifyAesXCbcKeyIfNeeded(byte[] keyBytes) throws GeneralSecurityException {
121         // As per RFC 4434:
122         // The key for AES-XCBC-PRF-128 is created as follows:
123         //
124         // 1. If the key is exactly 128 bits long, use it as-is.
125         //
126         // 2. If the key has fewer than 128 bits, lengthen it to exactly 128 bits by padding it on
127         // the right with zero bits.
128         //
129         // 3. If the key is 129 bits or longer, shorten it to exactly 128 bits by performing the
130         // steps in AES-XCBC-PRF-128 (that is, the algorithm described in this document). In that
131         // re-application of this algorithm, the key is 128 zero bits; the message is the too-long
132         // current key.
133         if (keyBytes.length < 16) {
134             keyBytes = Arrays.copyOf(keyBytes, 16);
135         } else if (keyBytes.length > 16) {
136             keyBytes = new AesXCbcImpl().mac(new byte[16], keyBytes, false /*needTruncation*/);
137         }
138 
139         return keyBytes;
140     }
141 
modifyAesCmacKeyIfNeeded(byte[] keyBytes)142     private byte[] modifyAesCmacKeyIfNeeded(byte[] keyBytes) {
143         // As per RFC 4615:
144         // The key for AES-CMAC-PRF-128 is created as follows:
145         //
146         // 1. If the key, VK, is exactly 128 bits, then we use it as-is.
147         //
148         // 2. If it is longer or shorter than 128 bits, then we derive the key, K, by applying the
149         // AES-CMAC algorithm using the 128-bit all-zero string as the key and VK as the input
150         // message.
151         if (keyBytes.length != 16) {
152             keyBytes = signBytes(new byte[16], keyBytes);
153         }
154         return keyBytes;
155     }
156 
157     /**
158      * Generates SKEYSEED based on the nonces and shared DH secret.
159      *
160      * @param nonceInit the IKE initiator nonce.
161      * @param nonceResp the IKE responder nonce.
162      * @param sharedDhKey the DH shared key.
163      * @return the byte array of SKEYSEED.
164      */
generateSKeySeed(byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey)165     public byte[] generateSKeySeed(byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey) {
166         ByteBuffer keyBuffer = null;
167         if (getAlgorithmId() == SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC
168                 || getAlgorithmId() == SaProposal.PSEUDORANDOM_FUNCTION_AES128_CMAC) {
169             keyBuffer = ByteBuffer.allocate(getKeyLength());
170             // When generating initial keys, use 8 bytes each from initiator and responder nonces as
171             // per RFC 7296
172             keyBuffer
173                     .put(Arrays.copyOfRange(nonceInit, 0, 8))
174                     .put(Arrays.copyOfRange(nonceResp, 0, 8));
175         } else {
176             keyBuffer = ByteBuffer.allocate(nonceInit.length + nonceResp.length);
177             keyBuffer.put(nonceInit).put(nonceResp);
178         }
179 
180         return signBytes(keyBuffer.array(), sharedDhKey);
181     }
182 
183     /**
184      * Generates a rekey SKEYSEED based on the nonces and shared DH secret.
185      *
186      * @param skD the secret for deriving new keys
187      * @param nonceInit the IKE initiator nonce.
188      * @param nonceResp the IKE responder nonce.
189      * @param sharedDhKey the DH shared key.
190      * @return the byte array of SKEYSEED.
191      */
generateRekeyedSKeySeed( byte[] skD, byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey)192     public byte[] generateRekeyedSKeySeed(
193             byte[] skD, byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey) {
194         ByteBuffer dataToSign =
195                 ByteBuffer.allocate(sharedDhKey.length + nonceInit.length + nonceResp.length);
196         dataToSign.put(sharedDhKey).put(nonceInit).put(nonceResp);
197 
198         return signBytes(skD, dataToSign.array());
199     }
200 
201     /**
202      * Derives keying materials from IKE/Child SA negotiation.
203      *
204      * <p>prf+(K, S) outputs a pseudorandom stream by using negotiated PRF iteratively. In this way
205      * it can generate long enough keying material containing all the keys for this IKE/Child SA.
206      *
207      * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296 Internet Key
208      *     Exchange Protocol Version 2 (IKEv2) 2.13. Generating Keying Material </a>
209      * @param keyBytes the key to sign data. SKEYSEED is used for generating KEYMAT for IKE SA. SK_d
210      *     is used for generating KEYMAT for Child SA.
211      * @param dataToSign the data to be signed.
212      * @param keyMaterialLen the length of keying materials.
213      * @return the byte array of keying materials
214      */
generateKeyMat(byte[] keyBytes, byte[] dataToSign, int keyMaterialLen)215     public byte[] generateKeyMat(byte[] keyBytes, byte[] dataToSign, int keyMaterialLen) {
216         return KeyGenerationUtils.prfPlus(this, keyBytes, dataToSign, keyMaterialLen);
217     }
218 
219     /**
220      * Returns algorithm type as a String.
221      *
222      * @return the algorithm type as a String.
223      */
224     @Override
getTypeString()225     public String getTypeString() {
226         return "Pseudorandom Function";
227     }
228 }
229