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.AccessControlContext; 21 import java.security.AccessController; 22 import java.security.Principal; 23 import java.security.cert.Certificate; 24 import java.security.cert.CertificateEncodingException; 25 import java.security.cert.X509Certificate; 26 import java.util.Map; 27 import java.util.Vector; 28 import java.util.Set; 29 import javax.net.ssl.SSLPeerUnverifiedException; 30 import javax.net.ssl.SSLPermission; 31 import javax.net.ssl.SSLSession; 32 import javax.net.ssl.SSLSessionBindingEvent; 33 import javax.net.ssl.SSLSessionBindingListener; 34 import javax.net.ssl.SSLSessionContext; 35 import javax.security.cert.CertificateException; 36 import libcore.base.Objects; 37 import org.apache.harmony.luni.util.TwoKeyHashMap; 38 39 /** 40 * Implementation of the class OpenSSLSessionImpl 41 * based on OpenSSL. 42 */ 43 public 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 throw new SSLPeerUnverifiedException(e.getMessage()); 238 } catch (CertificateException e) { 239 throw new SSLPeerUnverifiedException(e.getMessage()); 240 } 241 } 242 243 /** 244 * Return the identitity of the peer in this SSL session 245 * determined via certificate(s). 246 * @return an array of X509 certificates (the peer's one first and then 247 * eventually that of the certification authority) or null if no 248 * certificate were used during the SSL connection. 249 * @throws <code>SSLPeerUnverifiedException</code> if either a not X509 250 * certificate was used (i.e. Kerberos certificates) or the peer 251 * could not be verified. 252 */ 253 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { 254 checkPeerCertificatesPresent(); 255 return peerCertificates; 256 } 257 258 /** 259 * Throw SSLPeerUnverifiedException on null or empty peerCertificates array 260 */ 261 private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { 262 if (peerCertificates == null || peerCertificates.length == 0) { 263 throw new SSLPeerUnverifiedException("No peer certificates"); 264 } 265 } 266 267 /** 268 * The identity of the principal that was used by the peer during the SSL 269 * handshake phase is returned by this method. 270 * @return a X500Principal of the last certificate for X509-based 271 * cipher suites. 272 * @throws <code>SSLPeerUnverifiedException</code> if either a not X509 273 * certificate was used (i.e. Kerberos certificates) or the 274 * peer does not exist. 275 * 276 */ 277 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 278 checkPeerCertificatesPresent(); 279 return peerCertificates[0].getSubjectX500Principal(); 280 } 281 282 /** 283 * The peer's host name used in this SSL session is returned. It is the host 284 * name of the client for the server; and that of the server for the client. 285 * It is not a reliable way to get a fully qualified host name: it is mainly 286 * used internally to implement links for a temporary cache of SSL sessions. 287 * 288 * @return the host name of the peer, or null if no information is 289 * available. 290 * 291 */ 292 public String getPeerHost() { 293 return peerHost; 294 } 295 296 /** 297 * Returns the peer's port number for the actual SSL session. It is the port 298 * number of the client for the server; and that of the server for the 299 * client. It is not a reliable way to get a peer's port number: it is 300 * mainly used internally to implement links for a temporary cache of SSL 301 * sessions. 302 * @return the peer's port number, or -1 if no one is available. 303 * 304 */ 305 public int getPeerPort() { 306 return peerPort; 307 } 308 309 /** 310 * Returns a string identifier of the crypto tools used in the actual SSL 311 * session. For example AES_256_WITH_MD5. 312 * 313 * @return an identifier for all the cryptographic algorithms used in the 314 * actual SSL session. 315 */ 316 public String getCipherSuite() { 317 if (cipherSuite == null) { 318 String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer); 319 cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name); 320 if (cipherSuite == null) { 321 cipherSuite = name; 322 } 323 } 324 return cipherSuite; 325 } 326 327 /** 328 * Returns the standard version name of the SSL protocol used in all 329 * connections pertaining to this SSL session. 330 * 331 * @return the standard version name of the SSL protocol used in all 332 * connections pertaining to this SSL session. 333 * 334 */ 335 public String getProtocol() { 336 if (protocol == null) { 337 protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer); 338 } 339 return protocol; 340 } 341 342 /** 343 * Returns the compression method name used in all connections 344 * pertaining to this SSL session. 345 * 346 * @return the compresison method used in all connections 347 * pertaining to this SSL session. 348 * 349 */ 350 public String getCompressionMethod() { 351 if (compressionMethod == null) { 352 compressionMethod 353 = NativeCrypto.SSL_SESSION_compress_meth(sessionContext.sslCtxNativePointer, 354 sslSessionNativePointer); 355 } 356 return compressionMethod; 357 } 358 359 /** 360 * Returns the context to which the actual SSL session is bound. A SSL 361 * context consists of (1) a possible delegate, (2) a provider and (3) a 362 * protocol. If the security manager is activated and one tries to access 363 * the SSL context an exception may be thrown if a 364 * <code>SSLPermission("getSSLSessionContext")</code> 365 * permission is not set. 366 * @return the SSL context used for this session, or null if it is 367 * unavailable. 368 */ 369 public SSLSessionContext getSessionContext() { 370 SecurityManager sm = System.getSecurityManager(); 371 if (sm != null) { 372 sm.checkPermission(new SSLPermission("getSSLSessionContext")); 373 } 374 return sessionContext; 375 } 376 377 /** 378 * Returns a boolean flag signaling whether a SSL session is valid 379 * and available for resuming or joining or not. 380 * 381 * @return true if this session may be resumed. 382 */ 383 public boolean isValid() { 384 SSLSessionContext context = sessionContext; 385 if (isValid 386 && context != null 387 && context.getSessionTimeout() != 0 388 && getCreationTime() + (context.getSessionTimeout() * 1000) 389 < System.currentTimeMillis()) { 390 isValid = false; 391 } 392 return isValid; 393 } 394 395 /** 396 * It invalidates a SSL session forbidding any resumption. 397 */ 398 public void invalidate() { 399 isValid = false; 400 sessionContext = null; 401 } 402 403 /** 404 * Returns the object which is bound to the the input parameter name. 405 * This name is a sort of link to the data of the SSL session's application 406 * layer, if any exists. The search for this link is monitored, as a matter 407 * of security, by the full machinery of the <code>AccessController</code> 408 * class. 409 * 410 * @param name the name of the binding to find. 411 * @return the value bound to that name, or null if the binding does not 412 * exist. 413 * @throws <code>IllegalArgumentException</code> if the argument is null. 414 */ 415 public Object getValue(String name) { 416 if (name == null) { 417 throw new IllegalArgumentException("Parameter is null"); 418 } 419 return values.get(name, AccessController.getContext()); 420 } 421 422 /** 423 * Returns an array with the names (sort of links) of all the data 424 * objects of the application layer bound into the SSL session. The search 425 * for this link is monitored, as a matter of security, by the full 426 * machinery of the <code>AccessController</code> class. 427 * 428 * @return a non-null (possibly empty) array of names of the data objects 429 * bound to this SSL session. 430 */ 431 public String[] getValueNames() { 432 Vector<String> v = new Vector<String>(); 433 AccessControlContext current = AccessController.getContext(); 434 Set<Map.Entry<String, Object>> set = values.entrySet(); 435 for (Map.Entry<String, Object> o : set) { 436 TwoKeyHashMap.Entry<String, AccessControlContext, Object> entry 437 = (TwoKeyHashMap.Entry<String, AccessControlContext, Object>) o; 438 AccessControlContext cont = entry.getKey2(); 439 if (Objects.equal(current, cont)) { 440 v.add(entry.getKey1()); 441 } 442 } 443 return v.toArray(new String[v.size()]); 444 } 445 446 /** 447 * A link (name) with the specified value object of the SSL session's 448 * application layer data is created or replaced. If the new (or existing) 449 * value object implements the <code>SSLSessionBindingListener</code> 450 * interface, that object will be notified in due course. These links-to 451 * -data bounds are monitored, as a matter of security, by the full 452 * machinery of the <code>AccessController</code> class. 453 * 454 * @param name the name of the link (no null are 455 * accepted!) 456 * @param value data object that shall be bound to 457 * name. 458 * @throws <code>IllegalArgumentException</code> if one or both 459 * argument(s) is null. 460 */ 461 public void putValue(String name, Object value) { 462 if (name == null || value == null) { 463 throw new IllegalArgumentException("Parameter is null"); 464 } 465 Object old = values.put(name, AccessController.getContext(), value); 466 if (value instanceof SSLSessionBindingListener) { 467 ((SSLSessionBindingListener) value) 468 .valueBound(new SSLSessionBindingEvent(this, name)); 469 } 470 if (old instanceof SSLSessionBindingListener) { 471 ((SSLSessionBindingListener) old) 472 .valueUnbound(new SSLSessionBindingEvent(this, name)); 473 } 474 } 475 476 /** 477 * Removes a link (name) with the specified value object of the SSL 478 * session's application layer data. 479 * 480 * <p>If the value object implements the <code>SSLSessionBindingListener</code> 481 * interface, the object will receive a <code>valueUnbound</code> notification. 482 * 483 * <p>These links-to -data bounds are 484 * monitored, as a matter of security, by the full machinery of the 485 * <code>AccessController</code> class. 486 * 487 * @param name the name of the link (no null are 488 * accepted!) 489 * @throws <code>IllegalArgumentException</code> if the argument is null. 490 */ 491 public void removeValue(String name) { 492 if (name == null) { 493 throw new IllegalArgumentException("Parameter is null"); 494 } 495 Object old = values.remove(name, AccessController.getContext()); 496 if (old instanceof SSLSessionBindingListener) { 497 SSLSessionBindingListener listener = (SSLSessionBindingListener) old; 498 listener.valueUnbound(new SSLSessionBindingEvent(this, name)); 499 } 500 } 501 502 @Override protected void finalize() throws Throwable { 503 try { 504 NativeCrypto.SSL_SESSION_free(sslSessionNativePointer); 505 } finally { 506 super.finalize(); 507 } 508 } 509 } 510