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