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