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