1 /* 2 * Copyright (C) 2007 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 org.conscrypt; 18 19 import java.io.IOException; 20 import java.security.Principal; 21 import java.security.cert.Certificate; 22 import java.security.cert.CertificateEncodingException; 23 import java.security.cert.X509Certificate; 24 import java.util.HashMap; 25 import java.util.Map; 26 import javax.net.ssl.SSLPeerUnverifiedException; 27 import javax.net.ssl.SSLSession; 28 import javax.net.ssl.SSLSessionBindingEvent; 29 import javax.net.ssl.SSLSessionBindingListener; 30 import javax.net.ssl.SSLSessionContext; 31 import javax.security.cert.CertificateException; 32 33 /** 34 * Implementation of the class OpenSSLSessionImpl 35 * based on OpenSSL. 36 */ 37 public class OpenSSLSessionImpl implements SSLSession { 38 39 private long creationTime = 0; 40 long lastAccessedTime = 0; 41 final X509Certificate[] localCertificates; 42 final X509Certificate[] peerCertificates; 43 44 private boolean isValid = true; 45 private final Map<String, Object> values = new HashMap<String, Object>(); 46 private volatile javax.security.cert.X509Certificate[] peerCertificateChain; 47 protected long sslSessionNativePointer; 48 private String peerHost; 49 private int peerPort = -1; 50 private String cipherSuite; 51 private String protocol; 52 private AbstractSessionContext sessionContext; 53 private byte[] id; 54 55 /** 56 * Class constructor creates an SSL session context given the appropriate 57 * SSL parameters. 58 */ 59 protected OpenSSLSessionImpl(long sslSessionNativePointer, X509Certificate[] localCertificates, 60 X509Certificate[] peerCertificates, String peerHost, int peerPort, 61 AbstractSessionContext sessionContext) { 62 this.sslSessionNativePointer = sslSessionNativePointer; 63 this.localCertificates = localCertificates; 64 this.peerCertificates = peerCertificates; 65 this.peerHost = peerHost; 66 this.peerPort = peerPort; 67 this.sessionContext = sessionContext; 68 } 69 70 /** 71 * Constructs a session from a byte[] containing an SSL session serialized with DER encoding. 72 * This allows loading of a previously saved OpenSSLSessionImpl. 73 * 74 * @throws IOException if the serialized session data can not be parsed 75 */ 76 OpenSSLSessionImpl(byte[] derData, String peerHost, int peerPort, 77 X509Certificate[] peerCertificates, AbstractSessionContext sessionContext) 78 throws IOException { 79 this(NativeCrypto.d2i_SSL_SESSION(derData), null, peerCertificates, peerHost, peerPort, 80 sessionContext); 81 } 82 83 /** 84 * Gets the identifier of the actual SSL session 85 * @return array of sessions' identifiers. 86 */ 87 @Override 88 public byte[] getId() { 89 if (id == null) { 90 resetId(); 91 } 92 return id; 93 } 94 95 /** 96 * Reset the id field to the current value found in the native 97 * SSL_SESSION. It can change during the lifetime of the session 98 * because while a session is created during initial handshake, 99 * with handshake_cutthrough, the SSL_do_handshake may return 100 * before we have read the session ticket from the server side and 101 * therefore have computed no id based on the SHA of the ticket. 102 */ 103 void resetId() { 104 id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer); 105 } 106 107 /** 108 * Get the session object in DER format. This allows saving the session 109 * data or sharing it with other processes. 110 */ 111 byte[] getEncoded() { 112 return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer); 113 } 114 115 /** 116 * Gets the creation time of the SSL session. 117 * @return the session's creation time in milliseconds since the epoch 118 */ 119 @Override 120 public long getCreationTime() { 121 if (creationTime == 0) { 122 creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer); 123 } 124 return creationTime; 125 } 126 127 /** 128 * Returns the last time this concrete SSL session was accessed. Accessing 129 * here is to mean that a new connection with the same SSL context data was 130 * established. 131 * 132 * @return the session's last access time in milliseconds since the epoch 133 */ 134 @Override 135 public long getLastAccessedTime() { 136 return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime; 137 } 138 139 /** 140 * Returns the largest buffer size for the application's data bound to this 141 * concrete SSL session. 142 * @return the largest buffer size 143 */ 144 @Override 145 public int getApplicationBufferSize() { 146 return SSLRecordProtocol.MAX_DATA_LENGTH; 147 } 148 149 /** 150 * Returns the largest SSL/TLS packet size one can expect for this concrete 151 * SSL session. 152 * @return the largest packet size 153 */ 154 @Override 155 public int getPacketBufferSize() { 156 return SSLRecordProtocol.MAX_SSL_PACKET_SIZE; 157 } 158 159 /** 160 * Returns the principal (subject) of this concrete SSL session used in the 161 * handshaking phase of the connection. 162 * @return a X509 certificate or null if no principal was defined 163 */ 164 @Override 165 public Principal getLocalPrincipal() { 166 if (localCertificates != null && localCertificates.length > 0) { 167 return localCertificates[0].getSubjectX500Principal(); 168 } else { 169 return null; 170 } 171 } 172 173 /** 174 * Returns the certificate(s) of the principal (subject) of this concrete SSL 175 * session used in the handshaking phase of the connection. The OpenSSL 176 * native method supports only RSA certificates. 177 * @return an array of certificates (the local one first and then eventually 178 * that of the certification authority) or null if no certificate 179 * were used during the handshaking phase. 180 */ 181 @Override 182 public Certificate[] getLocalCertificates() { 183 return localCertificates; 184 } 185 186 /** 187 * Returns the certificate(s) of the peer in this SSL session 188 * used in the handshaking phase of the connection. 189 * Please notice hat this method is superseded by 190 * <code>getPeerCertificates()</code>. 191 * @return an array of X509 certificates (the peer's one first and then 192 * eventually that of the certification authority) or null if no 193 * certificate were used during the SSL connection. 194 * @throws SSLPeerUnverifiedException if either a non-X.509 certificate 195 * was used (i.e. Kerberos certificates) or the peer could not 196 * be verified. 197 */ 198 @Override 199 public javax.security.cert.X509Certificate[] getPeerCertificateChain() 200 throws SSLPeerUnverifiedException { 201 checkPeerCertificatesPresent(); 202 javax.security.cert.X509Certificate[] result = peerCertificateChain; 203 if (result == null) { 204 // single-check idiom 205 peerCertificateChain = result = createPeerCertificateChain(); 206 } 207 return result; 208 } 209 210 /** 211 * Provide a value to initialize the volatile peerCertificateChain 212 * field based on the native SSL_SESSION 213 */ 214 private javax.security.cert.X509Certificate[] createPeerCertificateChain() 215 throws SSLPeerUnverifiedException { 216 try { 217 javax.security.cert.X509Certificate[] chain 218 = new javax.security.cert.X509Certificate[peerCertificates.length]; 219 220 for (int i = 0; i < peerCertificates.length; i++) { 221 byte[] encoded = peerCertificates[i].getEncoded(); 222 chain[i] = javax.security.cert.X509Certificate.getInstance(encoded); 223 } 224 return chain; 225 } catch (CertificateEncodingException e) { 226 SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); 227 exception.initCause(exception); 228 throw exception; 229 } catch (CertificateException e) { 230 SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); 231 exception.initCause(exception); 232 throw exception; 233 } 234 } 235 236 /** 237 * Return the identity of the peer in this SSL session 238 * determined via certificate(s). 239 * @return an array of X509 certificates (the peer's one first and then 240 * eventually that of the certification authority) or null if no 241 * certificate were used during the SSL connection. 242 * @throws SSLPeerUnverifiedException if either a non-X.509 certificate 243 * was used (i.e. Kerberos certificates) or the peer could not 244 * be verified. 245 */ 246 @Override 247 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { 248 checkPeerCertificatesPresent(); 249 return peerCertificates; 250 } 251 252 /** 253 * Throw SSLPeerUnverifiedException on null or empty peerCertificates array 254 */ 255 private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { 256 if (peerCertificates == null || peerCertificates.length == 0) { 257 throw new SSLPeerUnverifiedException("No peer certificates"); 258 } 259 } 260 261 /** 262 * The identity of the principal that was used by the peer during the SSL 263 * handshake phase is returned by this method. 264 * @return a X500Principal of the last certificate for X509-based 265 * cipher suites. 266 * @throws SSLPeerUnverifiedException if either a non-X.509 certificate 267 * was used (i.e. Kerberos certificates) or the peer does not exist. 268 * 269 */ 270 @Override 271 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 272 checkPeerCertificatesPresent(); 273 return peerCertificates[0].getSubjectX500Principal(); 274 } 275 276 /** 277 * The peer's host name used in this SSL session is returned. It is the host 278 * name of the client for the server; and that of the server for the client. 279 * It is not a reliable way to get a fully qualified host name: it is mainly 280 * used internally to implement links for a temporary cache of SSL sessions. 281 * 282 * @return the host name of the peer, or {@code null} if no information is 283 * available. 284 */ 285 @Override 286 public String getPeerHost() { 287 return peerHost; 288 } 289 290 /** 291 * Returns the peer's port number for the actual SSL session. It is the port 292 * number of the client for the server; and that of the server for the 293 * client. It is not a reliable way to get a peer's port number: it is 294 * mainly used internally to implement links for a temporary cache of SSL 295 * sessions. 296 * 297 * @return the peer's port number, or {@code -1} if no one is available. 298 */ 299 @Override 300 public int getPeerPort() { 301 return peerPort; 302 } 303 304 /** 305 * Returns a string identifier of the crypto tools used in the actual SSL 306 * session. For example AES_256_WITH_MD5. 307 */ 308 @Override 309 public String getCipherSuite() { 310 if (cipherSuite == null) { 311 String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer); 312 cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name); 313 if (cipherSuite == null) { 314 cipherSuite = name; 315 } 316 } 317 return cipherSuite; 318 } 319 320 /** 321 * Returns the standard version name of the SSL protocol used in all 322 * connections pertaining to this SSL session. 323 */ 324 @Override 325 public String getProtocol() { 326 if (protocol == null) { 327 protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer); 328 } 329 return protocol; 330 } 331 332 /** 333 * Returns the context to which the actual SSL session is bound. A SSL 334 * context consists of (1) a possible delegate, (2) a provider and (3) a 335 * protocol. 336 * @return the SSL context used for this session, or null if it is 337 * unavailable. 338 */ 339 @Override 340 public SSLSessionContext getSessionContext() { 341 return sessionContext; 342 } 343 344 /** 345 * Returns a boolean flag signaling whether a SSL session is valid 346 * and available for resuming or joining or not. 347 * 348 * @return true if this session may be resumed. 349 */ 350 @Override 351 public boolean isValid() { 352 if (!isValid) { 353 return false; 354 } 355 // The session has't yet been invalidated -- check whether it timed out. 356 357 SSLSessionContext context = sessionContext; 358 if (context == null) { 359 // Session not associated with a context -- no way to tell what its timeout should be. 360 return true; 361 } 362 363 int timeoutSeconds = context.getSessionTimeout(); 364 if (timeoutSeconds == 0) { 365 // Infinite timeout -- session still valid 366 return true; 367 } 368 369 long creationTimestampMillis = getCreationTime(); 370 long ageSeconds = (System.currentTimeMillis() - creationTimestampMillis) / 1000; 371 // NOTE: The age might be negative if something was/is wrong with the system clock. We time 372 // out such sessions to be safe. 373 if ((ageSeconds >= timeoutSeconds) || (ageSeconds < 0)) { 374 // Session timed out -- no longer valid 375 isValid = false; 376 return false; 377 } 378 379 // Session still valid 380 return true; 381 } 382 383 /** 384 * It invalidates a SSL session forbidding any resumption. 385 */ 386 @Override 387 public void invalidate() { 388 isValid = false; 389 sessionContext = null; 390 } 391 392 /** 393 * Returns the object which is bound to the the input parameter name. 394 * This name is a sort of link to the data of the SSL session's application 395 * layer, if any exists. 396 * 397 * @param name the name of the binding to find. 398 * @return the value bound to that name, or null if the binding does not 399 * exist. 400 * @throws IllegalArgumentException if the argument is null. 401 */ 402 @Override 403 public Object getValue(String name) { 404 if (name == null) { 405 throw new IllegalArgumentException("name == null"); 406 } 407 return values.get(name); 408 } 409 410 /** 411 * Returns an array with the names (sort of links) of all the data 412 * objects of the application layer bound into the SSL session. 413 * 414 * @return a non-null (possibly empty) array of names of the data objects 415 * bound to this SSL session. 416 */ 417 @Override 418 public String[] getValueNames() { 419 return values.keySet().toArray(new String[values.size()]); 420 } 421 422 /** 423 * A link (name) with the specified value object of the SSL session's 424 * application layer data is created or replaced. If the new (or existing) 425 * value object implements the <code>SSLSessionBindingListener</code> 426 * interface, that object will be notified in due course. 427 * 428 * @param name the name of the link (no null are 429 * accepted!) 430 * @param value data object that shall be bound to 431 * name. 432 * @throws IllegalArgumentException if one or both argument(s) is null. 433 */ 434 @Override 435 public void putValue(String name, Object value) { 436 if (name == null || value == null) { 437 throw new IllegalArgumentException("name == null || value == null"); 438 } 439 Object old = values.put(name, value); 440 if (value instanceof SSLSessionBindingListener) { 441 ((SSLSessionBindingListener) value) 442 .valueBound(new SSLSessionBindingEvent(this, name)); 443 } 444 if (old instanceof SSLSessionBindingListener) { 445 ((SSLSessionBindingListener) old) 446 .valueUnbound(new SSLSessionBindingEvent(this, name)); 447 } 448 } 449 450 /** 451 * Removes a link (name) with the specified value object of the SSL 452 * session's application layer data. 453 * 454 * <p>If the value object implements the <code>SSLSessionBindingListener</code> 455 * interface, the object will receive a <code>valueUnbound</code> notification. 456 * 457 * @param name the name of the link (no null are 458 * accepted!) 459 * @throws IllegalArgumentException if the argument is null. 460 */ 461 @Override 462 public void removeValue(String name) { 463 if (name == null) { 464 throw new IllegalArgumentException("name == null"); 465 } 466 Object old = values.remove(name); 467 if (old instanceof SSLSessionBindingListener) { 468 SSLSessionBindingListener listener = (SSLSessionBindingListener) old; 469 listener.valueUnbound(new SSLSessionBindingEvent(this, name)); 470 } 471 } 472 473 /** 474 * Returns the name requested by the SNI extension. 475 */ 476 public String getRequestedServerName() { 477 return NativeCrypto.get_SSL_SESSION_tlsext_hostname(sslSessionNativePointer); 478 } 479 480 @Override 481 protected void finalize() throws Throwable { 482 try { 483 // The constructor can throw an exception if this object is constructed from invalid 484 // saved session data. 485 if (sslSessionNativePointer != 0) { 486 NativeCrypto.SSL_SESSION_free(sslSessionNativePointer); 487 } 488 } finally { 489 super.finalize(); 490 } 491 } 492 } 493