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