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