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