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