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