• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.eap.crypto;
18 
19 import static com.android.internal.net.eap.EapAuthenticator.LOG;
20 import static com.android.internal.net.eap.statemachine.EapMethodStateMachine.MIN_EMSK_LEN_BYTES;
21 import static com.android.internal.net.eap.statemachine.EapMethodStateMachine.MIN_MSK_LEN_BYTES;
22 
23 import android.annotation.IntDef;
24 import android.net.ssl.SSLEngines;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.net.eap.EapResult.EapError;
28 import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
29 
30 import java.io.IOException;
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.nio.BufferOverflowException;
34 import java.nio.ByteBuffer;
35 import java.security.GeneralSecurityException;
36 import java.security.KeyStore;
37 import java.security.Provider;
38 import java.security.ProviderException;
39 import java.security.SecureRandom;
40 import java.security.Security;
41 import java.security.cert.X509Certificate;
42 import java.util.Arrays;
43 
44 import javax.net.ssl.SSLContext;
45 import javax.net.ssl.SSLEngine;
46 import javax.net.ssl.SSLEngineResult;
47 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
48 import javax.net.ssl.SSLEngineResult.Status;
49 import javax.net.ssl.SSLException;
50 import javax.net.ssl.SSLSession;
51 import javax.net.ssl.TrustManager;
52 import javax.net.ssl.TrustManagerFactory;
53 import javax.net.ssl.X509TrustManager;
54 
55 /**
56  * TlsSession provides the TLS handshake and encryption/decryption functionality for EAP-TTLS.
57  *
58  * <p>The primary return mechanism of TlsSession is via {@link TlsResult TlsResult}, which contains
59  * an outbound message and the status of the operation.
60  *
61  * <p>The handshake is initiated via the {@link #startHandshake() startHandshake} method which wraps
62  * the first outbound message. Any handshake message that follows is then processed via {@link
63  * #processHandshakeData(byte[]) processHandshakeData} which will eventually produce a TlsResult.
64  *
65  * <p>Once a handshake is complete, data can be encrypted via {@link #processOutgoingData(byte[])
66  * processOutgoingData} which will produce a TlsResult with the encrypted message. Decryption is
67  * similar and is handled via {@link #processIncomingData(byte[]) processIncomingData} which
68  * produces a TlsResult with the decrypted application data.
69  */
70 public class TlsSession {
71     private static final String TAG = TlsSession.class.getSimpleName();
72 
73     @Retention(RetentionPolicy.SOURCE)
74     @IntDef({
75         TLS_STATUS_TUNNEL_ESTABLISHED,
76         TLS_STATUS_SUCCESS,
77         TLS_STATUS_FAILURE,
78         TLS_STATUS_CLOSED
79     })
80     public @interface TlsStatus {}
81 
82     public static final int TLS_STATUS_TUNNEL_ESTABLISHED = 0;
83     public static final int TLS_STATUS_SUCCESS = 1;
84     public static final int TLS_STATUS_FAILURE = 2;
85     public static final int TLS_STATUS_CLOSED = 3;
86 
87     // TODO(b/163135610): Support for TLS 1.3 in EAP-TTLS
88     private static final String[] ENABLED_TLS_PROTOCOLS = {"TLSv1.2"};
89     // The trust management algorithm, keystore type and the trust manager provider are equivalent
90     // to those used in the IKEv2 library
91     private static final String CERT_PATH_ALGO_PKIX = "PKIX";
92     private static final String KEY_STORE_TYPE_PKCS12 = "PKCS12";
93     private static final Provider TRUST_MANAGER_PROVIDER = Security.getProvider("HarmonyJSSE");
94 
95     // Label for key generation (RFC 5281#8)
96     private static final String TTLS_EXPORTER_LABEL = "ttls keying material";
97     // 128 bytes of keying material. First 64 bytes represent the MSK and the second 64 bytes
98     // represent the EMSK (RFC5281#8)
99     private static final int TTLS_KEYING_MATERIAL_LEN = 128;
100 
101     private final SSLContext mSslContext;
102     private final SSLSession mSslSession;
103     private final SSLEngine mSslEngine;
104     private final SecureRandom mSecureRandom;
105 
106     // this is kept as an outer variable as the finished state is returned exclusively by
107     // wrap/unwrap so it is important to keep track of the handshake status separately
108     @VisibleForTesting HandshakeStatus mHandshakeStatus;
109     @VisibleForTesting boolean mHandshakeComplete = false;
110     private TrustManager[] mTrustManagers;
111 
112     private ByteBuffer mApplicationData;
113     private ByteBuffer mPacketData;
114 
115     // Package-private
TlsSession(X509Certificate serverCaCert, SecureRandom secureRandom)116     TlsSession(X509Certificate serverCaCert, SecureRandom secureRandom)
117             throws GeneralSecurityException, IOException {
118         mSecureRandom = secureRandom;
119         initTrustManagers(serverCaCert);
120         mSslContext = SSLContext.getInstance("TLSv1.2");
121         mSslContext.init(null, mTrustManagers, secureRandom);
122         mSslEngine = mSslContext.createSSLEngine();
123         mSslEngine.setEnabledProtocols(ENABLED_TLS_PROTOCOLS);
124         mSslEngine.setUseClientMode(true);
125         mSslSession = mSslEngine.getSession();
126         mApplicationData = ByteBuffer.allocate(mSslSession.getApplicationBufferSize());
127         mPacketData = ByteBuffer.allocate(mSslSession.getPacketBufferSize());
128     }
129 
130     @VisibleForTesting
TlsSession( SSLContext sslContext, SSLEngine sslEngine, SSLSession sslSession, SecureRandom secureRandom)131     public TlsSession(
132             SSLContext sslContext,
133             SSLEngine sslEngine,
134             SSLSession sslSession,
135             SecureRandom secureRandom) {
136         mSslContext = sslContext;
137         mSslEngine = sslEngine;
138         mSecureRandom = secureRandom;
139         mSslSession = sslSession;
140         mApplicationData = ByteBuffer.allocate(mSslSession.getApplicationBufferSize());
141         mPacketData = ByteBuffer.allocate(mSslSession.getPacketBufferSize());
142     }
143 
144     /**
145      * Creates the trust manager instance needed to instantiate the SSLContext
146      *
147      * @param serverCaCert the CA certificate for validating the received server certificate(s). If
148      *     no certificate is provided, any root CA in the system's truststore is considered
149      *     acceptable.
150      * @throws GeneralSecurityException if the trust manager cannot be initialized
151      * @throws IOException if there is an I/O issue with keystore data
152      */
initTrustManagers(X509Certificate serverCaCert)153     private void initTrustManagers(X509Certificate serverCaCert)
154             throws GeneralSecurityException, IOException {
155         // TODO(b/160798904): Pass TrustManager through EAP authenticator in EAP-TTLS
156 
157         KeyStore keyStore = null;
158 
159         if (serverCaCert != null) {
160             keyStore = KeyStore.getInstance(KEY_STORE_TYPE_PKCS12);
161             keyStore.load(null);
162             String alias =
163                     serverCaCert.getSubjectX500Principal().getName() + serverCaCert.hashCode();
164             keyStore.setCertificateEntry(alias, serverCaCert);
165         }
166 
167         TrustManagerFactory tmFactory =
168                 TrustManagerFactory.getInstance(CERT_PATH_ALGO_PKIX, TRUST_MANAGER_PROVIDER);
169         tmFactory.init(keyStore);
170 
171         mTrustManagers = tmFactory.getTrustManagers();
172         for (TrustManager tm : mTrustManagers) {
173             if (tm instanceof X509TrustManager) {
174                 return;
175             }
176         }
177 
178         throw new ProviderException(
179                 "X509TrustManager is not supported by provider " + TRUST_MANAGER_PROVIDER);
180     }
181 
182     /**
183      * Initializes the TLS handshake by wrapping the first ClientHello message
184      *
185      * <p>Note that no handshaking occurred during the writing of this code. The underlying
186      * implementation of handshake used here is the elbow bump.
187      *
188      * @return a tls result containing outbound data the and status of operation
189      */
startHandshake()190     public TlsResult startHandshake() {
191         clearAndGrowApplicationBufferIfNeeded();
192         clearAndGrowPacketBufferIfNeeded();
193 
194         SSLEngineResult result;
195         try {
196             // A wrap implicitly begins the handshake. This will produce the ClientHello
197             // message.
198             result = mSslEngine.wrap(mApplicationData, mPacketData);
199         } catch (SSLException e) {
200             LOG.e(TAG, "Failed to initiate handshake", e);
201             return new TlsResult(TLS_STATUS_FAILURE);
202         }
203         mHandshakeStatus = result.getHandshakeStatus();
204 
205         return new TlsResult(getByteArrayFromBuffer(mPacketData), TLS_STATUS_SUCCESS);
206     }
207 
208     /**
209      * Processes an incoming handshake message and updates the handshake status accordingly
210      *
211      * <p>Note that Conscrypt's SSLEngine only returns FINISHED once. In TLS 1.2, this is returned
212      * after a wrap call. However, this wrap occurs AFTER the handshake is complete on both the
213      * server and client side. As a result, the wrap would simply encrypt the entire buffer (of
214      * zeroes) and produce garbage data. Instead, an EAP-identity within an EAP-MESSAGE AVP is
215      * passed and encrypted as this is the first message sent after the handshake. If the EAP
216      * identity is not passed and the garbage data packet is simply dropped, all subsequent packets
217      * will have incorrect sequence numbers and fail message authentication.
218      *
219      * <p>The AVP, which contains an EAP-identity response, can safely be passed for each
220      * wrap/unwrap as it is ignored if the handshake is still in progress. Consumption and
221      * production during the handshake occur within the packet buffers.
222      *
223      * <p>Note that due to the ongoing COVID-19 pandemic, increased sanitization measures are being
224      * employed in-between processHandshakeData calls in order to keep the buffers clean (RFC-EB)
225      *
226      * @param handshakeData the message to process
227      * @param avp an avp containing an EAP-identity response
228      * @return a {@link TlsResult} containing an outbound message and status of operation
229      */
processHandshakeData(byte[] handshakeData, byte[] avp)230     public TlsResult processHandshakeData(byte[] handshakeData, byte[] avp) {
231         clearAndGrowApplicationBufferIfNeeded();
232         clearAndGrowPacketBufferIfNeeded();
233 
234         try {
235             // The application buffer size is guaranteed to be larger than that of the AVP as the
236             // handshaking messages contain substantially more data
237             mApplicationData.put(avp);
238             mPacketData.put(handshakeData);
239         } catch (BufferOverflowException e) {
240             // The connection will be closed because the buffer was just allocated to the desired
241             // size.
242             LOG.e(
243                     TAG,
244                     "Buffer overflow while attempting to process handshake message. Attempting to"
245                             + " close connection.",
246                     e);
247             return closeConnection();
248         }
249         mApplicationData.flip();
250         mPacketData.flip();
251 
252         TlsResult tlsResult = new TlsResult(TLS_STATUS_FAILURE);
253 
254         processingLoop:
255         while (true) {
256             switch (mHandshakeStatus) {
257                 case NEED_UNWRAP:
258                     tlsResult = doUnwrap();
259                     continue;
260                 case NEED_TASK:
261                     mSslEngine.getDelegatedTask().run();
262                     mHandshakeStatus = mSslEngine.getHandshakeStatus();
263                     continue;
264                 case NEED_WRAP:
265                     mPacketData.clear();
266                     tlsResult = doWrap();
267                     if (mHandshakeStatus == HandshakeStatus.FINISHED) {
268                         mHandshakeComplete = true;
269                         mHandshakeStatus = mSslEngine.getHandshakeStatus();
270                     }
271                     break processingLoop;
272                 default:
273                     // If the status is NOT_HANDSHAKING, this is unexpected, and is treated as a
274                     // failure. FINISHED can never be reached here because it is handled in
275                     // NEED_WRAP/NEED_UNWRAP
276                     break processingLoop;
277             }
278         }
279 
280         return tlsResult;
281     }
282 
283     /**
284      * Decrypts incoming data during a TLS session
285      *
286      * @param data the data to decrypt
287      * @return a tls result containing the decrypted data and status of operation
288      */
processIncomingData(byte[] data)289     public TlsResult processIncomingData(byte[] data) {
290         clearAndGrowApplicationBufferIfNeeded();
291         mPacketData = ByteBuffer.wrap(data);
292         return doUnwrap();
293     }
294 
295     /**
296      * Encrypts outbound data during a TLS session
297      *
298      * @param data the data to encrypt
299      * @return a tls result containing the encrypted data and status of operation
300      */
processOutgoingData(byte[] data)301     public TlsResult processOutgoingData(byte[] data) {
302         clearAndGrowPacketBufferIfNeeded();
303         mApplicationData = ByteBuffer.wrap(data);
304         return doWrap();
305     }
306 
307     /**
308      * Unwraps data during a TLS session either during a handshake or for decryption purposes.
309      *
310      * @param applicationData a destination buffer with decrypted or processed data
311      * @param packetData a bytebuffer containing inbound data from the server
312      * @return a tls result containing the unwrapped message and status of operation
313      */
doUnwrap()314     private TlsResult doUnwrap() {
315         SSLEngineResult result;
316         try {
317             result = mSslEngine.unwrap(mPacketData, mApplicationData);
318         } catch (SSLException e) {
319             LOG.e(TAG, "Encountered an issue while unwrapping data. Connection will be closed.", e);
320             return closeConnection();
321         }
322 
323         mHandshakeStatus = result.getHandshakeStatus();
324         if (result.getStatus() != Status.OK) {
325             return closeConnection();
326         }
327 
328         return new TlsResult(getByteArrayFromBuffer(mApplicationData), TLS_STATUS_SUCCESS);
329     }
330 
331     /**
332      * Wraps data during a TLS session either during a handshake or for encryption purposes.
333      *
334      * @param applicationData a bytebuffer containing data to encrypt or process
335      * @param packetData a destination buffer for outbound data
336      * @return a tls result containing the wrapped message and status of operation
337      */
doWrap()338     private TlsResult doWrap() {
339         SSLEngineResult result;
340         try {
341             result = mSslEngine.wrap(mApplicationData, mPacketData);
342         } catch (SSLException e) {
343             LOG.e(TAG, "Encountered an issue while wrapping data. Connection will be closed.", e);
344             return closeConnection();
345         }
346 
347         mHandshakeStatus = result.getHandshakeStatus();
348         if (result.getStatus() != Status.OK) {
349             return closeConnection();
350         }
351 
352         return new TlsResult(
353                 getByteArrayFromBuffer(mPacketData),
354                 (mHandshakeStatus == HandshakeStatus.FINISHED)
355                         ? TLS_STATUS_TUNNEL_ESTABLISHED
356                         : TLS_STATUS_SUCCESS);
357     }
358 
359     /**
360      * Attempts to close the TLS tunnel.
361      *
362      * <p>Once a session has been closed, it cannot be reopened.
363      *
364      * @return a tls result with the status of the operation as well as a potential closing message
365      */
closeConnection()366     public TlsResult closeConnection() {
367         try {
368             mSslEngine.closeInbound();
369         } catch (SSLException e) {
370             LOG.e(TAG, "Error occurred when trying to close inbound.", e);
371         }
372         mSslEngine.closeOutbound();
373 
374         mHandshakeStatus = mSslEngine.getHandshakeStatus();
375 
376         if (mHandshakeStatus != HandshakeStatus.NEED_WRAP) {
377             return new TlsResult(TLS_STATUS_CLOSED);
378         }
379 
380         clearAndGrowPacketBufferIfNeeded();
381         clearAndGrowApplicationBufferIfNeeded();
382 
383         SSLEngineResult result;
384         while (mHandshakeStatus == HandshakeStatus.NEED_WRAP) {
385             try {
386                 // the wrap is handled internally in order to preserve data in the buffers as they
387                 // are cleared in the beginning of the closeConnection call
388                 result = mSslEngine.wrap(mApplicationData, mPacketData);
389             } catch (SSLException e) {
390                 LOG.e(
391                         TAG,
392                         "Wrap operation failed whilst attempting to flush out data during a close.",
393                         e);
394                 return new TlsResult(TLS_STATUS_FAILURE);
395             }
396 
397             mHandshakeStatus = result.getHandshakeStatus();
398             if (result.getStatus() == Status.BUFFER_OVERFLOW
399                     || result.getStatus() == Status.BUFFER_UNDERFLOW) {
400                 // an overflow or underflow at this point should not occur. if one does, terminate
401                 LOG.e(
402                         TAG,
403                         "Experienced an overflow or underflow while trying to close the TLS"
404                                 + " connection.");
405                 return new TlsResult(TLS_STATUS_FAILURE);
406             }
407         }
408 
409         return new TlsResult(getByteArrayFromBuffer(mPacketData), TLS_STATUS_CLOSED);
410     }
411 
412     /**
413      * Generates the keying material required in EAP-TTLS (RFC5281#8)
414      *
415      * @return EapTtlsKeyingMaterial containing the MSK and EMSK
416      */
generateKeyingMaterial()417     public EapTtlsKeyingMaterial generateKeyingMaterial() {
418         if (!mHandshakeComplete) {
419             EapInvalidRequestException invalidRequestException =
420                     new EapInvalidRequestException(
421                             "Keying material can only be generated once the handshake is"
422                                     + " complete.");
423             return new EapTtlsKeyingMaterial(new EapError(invalidRequestException));
424         }
425 
426         try {
427             // As per RFC5281#8 (and RFC5705#4), generation of keying material in EAP-TTLS does not
428             // require a context.
429             ByteBuffer keyingMaterial =
430                     ByteBuffer.wrap(
431                             SSLEngines.exportKeyingMaterial(
432                                     mSslEngine,
433                                     TTLS_EXPORTER_LABEL,
434                                     null /* context */,
435                                     TTLS_KEYING_MATERIAL_LEN));
436 
437             byte[] msk = new byte[MIN_MSK_LEN_BYTES];
438             byte[] emsk = new byte[MIN_EMSK_LEN_BYTES];
439             keyingMaterial.get(msk);
440             keyingMaterial.get(emsk);
441 
442             return new EapTtlsKeyingMaterial(msk, emsk);
443         } catch (SSLException e) {
444             LOG.e(TAG, "Failed to generate EAP-TTLS keying material", e);
445             return new EapTtlsKeyingMaterial(new EapError(e));
446         }
447     }
448 
449     /**
450      * Verifies whether the packet data buffer is in need of additional memory and reallocates if
451      * necessary
452      */
clearAndGrowPacketBufferIfNeeded()453     private void clearAndGrowPacketBufferIfNeeded() {
454         mPacketData.clear();
455         if (mPacketData.capacity() < mSslSession.getPacketBufferSize()) {
456             mPacketData = ByteBuffer.allocate(mSslSession.getPacketBufferSize());
457         }
458     }
459 
460     /**
461      * Verifies whether the application data buffer is in need of additional memory and reallocates
462      * if necessary
463      */
clearAndGrowApplicationBufferIfNeeded()464     private void clearAndGrowApplicationBufferIfNeeded() {
465         mApplicationData.clear();
466         if (mApplicationData.capacity() < mSslSession.getApplicationBufferSize()) {
467             mApplicationData = ByteBuffer.allocate(mSslSession.getApplicationBufferSize());
468         }
469     }
470 
471     /**
472      * Retrieves a byte array from a given byte buffer
473      *
474      * @param buffer the byte buffer to get the array from
475      * @return a byte array
476      */
477     @VisibleForTesting
getByteArrayFromBuffer(ByteBuffer buffer)478     public static byte[] getByteArrayFromBuffer(ByteBuffer buffer) {
479         return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
480     }
481 
482     /**
483      * TlsResult encapsulates the results of a TlsSession operation.
484      *
485      * <p>It contains the status result of the TLS session and the data that accompanies it
486      */
487     public class TlsResult {
488         public final byte[] data;
489         public final @TlsStatus int status;
490 
TlsResult(byte[] data, @TlsStatus int status)491         public TlsResult(byte[] data, @TlsStatus int status) {
492             this.data = data;
493             this.status = status;
494         }
495 
TlsResult(@lsStatus int status)496         public TlsResult(@TlsStatus int status) {
497             this(new byte[0], status);
498         }
499     }
500 
501     /** EapTtlsKeyingMaterial encapsulates the result of keying material generation in EAP-TTLS */
502     public class EapTtlsKeyingMaterial {
503         public final byte[] msk;
504         public final byte[] emsk;
505         public final EapError eapError;
506 
EapTtlsKeyingMaterial(byte[] msk, byte[] emsk)507         public EapTtlsKeyingMaterial(byte[] msk, byte[] emsk) {
508             this.msk = msk;
509             this.emsk = emsk;
510             this.eapError = null;
511         }
512 
EapTtlsKeyingMaterial(EapError eapError)513         public EapTtlsKeyingMaterial(EapError eapError) {
514             this.msk = null;
515             this.emsk = null;
516             this.eapError = eapError;
517         }
518 
isSuccessful()519         public boolean isSuccessful() {
520             return eapError == null;
521         }
522     }
523 }
524