/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.conscrypt; import java.io.IOException; import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; import javax.security.cert.CertificateException; /** * Implementation of the class OpenSSLSessionImpl * based on OpenSSL. */ public class OpenSSLSessionImpl implements SSLSession { private long creationTime = 0; long lastAccessedTime = 0; final X509Certificate[] localCertificates; final X509Certificate[] peerCertificates; private boolean isValid = true; private final Map values = new HashMap(); private volatile javax.security.cert.X509Certificate[] peerCertificateChain; protected long sslSessionNativePointer; private String peerHost; private int peerPort = -1; private String cipherSuite; private String protocol; private AbstractSessionContext sessionContext; private byte[] id; /** * Class constructor creates an SSL session context given the appropriate * SSL parameters. */ protected OpenSSLSessionImpl(long sslSessionNativePointer, X509Certificate[] localCertificates, X509Certificate[] peerCertificates, String peerHost, int peerPort, AbstractSessionContext sessionContext) { this.sslSessionNativePointer = sslSessionNativePointer; this.localCertificates = localCertificates; this.peerCertificates = peerCertificates; this.peerHost = peerHost; this.peerPort = peerPort; this.sessionContext = sessionContext; } /** * Constructs a session from a byte[] containing DER data. This * allows loading the saved session. * @throws IOException */ OpenSSLSessionImpl(byte[] derData, String peerHost, int peerPort, X509Certificate[] peerCertificates, AbstractSessionContext sessionContext) throws IOException { this(NativeCrypto.d2i_SSL_SESSION(derData), null, peerCertificates, peerHost, peerPort, sessionContext); // TODO move this check into native code so we can throw an error with more information if (this.sslSessionNativePointer == 0) { throw new IOException("Invalid session data"); } } /** * Gets the identifier of the actual SSL session * @return array of sessions' identifiers. */ @Override public byte[] getId() { if (id == null) { resetId(); } return id; } /** * Reset the id field to the current value found in the native * SSL_SESSION. It can change during the lifetime of the session * because while a session is created during initial handshake, * with handshake_cutthrough, the SSL_do_handshake may return * before we have read the session ticket from the server side and * therefore have computed no id based on the SHA of the ticket. */ void resetId() { id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer); } /** * Get the session object in DER format. This allows saving the session * data or sharing it with other processes. */ byte[] getEncoded() { return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer); } /** * Gets the creation time of the SSL session. * @return the session's creation time in milliseconds since the epoch */ @Override public long getCreationTime() { if (creationTime == 0) { creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer); } return creationTime; } /** * Returns the last time this concrete SSL session was accessed. Accessing * here is to mean that a new connection with the same SSL context data was * established. * * @return the session's last access time in milliseconds since the epoch */ @Override public long getLastAccessedTime() { return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime; } /** * Returns the largest buffer size for the application's data bound to this * concrete SSL session. * @return the largest buffer size */ @Override public int getApplicationBufferSize() { return SSLRecordProtocol.MAX_DATA_LENGTH; } /** * Returns the largest SSL/TLS packet size one can expect for this concrete * SSL session. * @return the largest packet size */ @Override public int getPacketBufferSize() { return SSLRecordProtocol.MAX_SSL_PACKET_SIZE; } /** * Returns the principal (subject) of this concrete SSL session used in the * handshaking phase of the connection. * @return a X509 certificate or null if no principal was defined */ @Override public Principal getLocalPrincipal() { if (localCertificates != null && localCertificates.length > 0) { return localCertificates[0].getSubjectX500Principal(); } else { return null; } } /** * Returns the certificate(s) of the principal (subject) of this concrete SSL * session used in the handshaking phase of the connection. The OpenSSL * native method supports only RSA certificates. * @return an array of certificates (the local one first and then eventually * that of the certification authority) or null if no certificate * were used during the handshaking phase. */ @Override public Certificate[] getLocalCertificates() { return localCertificates; } /** * Returns the certificate(s) of the peer in this SSL session * used in the handshaking phase of the connection. * Please notice hat this method is superseded by * getPeerCertificates(). * @return an array of X509 certificates (the peer's one first and then * eventually that of the certification authority) or null if no * certificate were used during the SSL connection. * @throws SSLPeerUnverifiedException if either a non-X.509 certificate * was used (i.e. Kerberos certificates) or the peer could not * be verified. */ @Override public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { checkPeerCertificatesPresent(); javax.security.cert.X509Certificate[] result = peerCertificateChain; if (result == null) { // single-check idiom peerCertificateChain = result = createPeerCertificateChain(); } return result; } /** * Provide a value to initialize the volatile peerCertificateChain * field based on the native SSL_SESSION */ private javax.security.cert.X509Certificate[] createPeerCertificateChain() throws SSLPeerUnverifiedException { try { javax.security.cert.X509Certificate[] chain = new javax.security.cert.X509Certificate[peerCertificates.length]; for (int i = 0; i < peerCertificates.length; i++) { byte[] encoded = peerCertificates[i].getEncoded(); chain[i] = javax.security.cert.X509Certificate.getInstance(encoded); } return chain; } catch (CertificateEncodingException e) { SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); exception.initCause(exception); throw exception; } catch (CertificateException e) { SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); exception.initCause(exception); throw exception; } } /** * Return the identity of the peer in this SSL session * determined via certificate(s). * @return an array of X509 certificates (the peer's one first and then * eventually that of the certification authority) or null if no * certificate were used during the SSL connection. * @throws SSLPeerUnverifiedException if either a non-X.509 certificate * was used (i.e. Kerberos certificates) or the peer could not * be verified. */ @Override public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { checkPeerCertificatesPresent(); return peerCertificates; } /** * Throw SSLPeerUnverifiedException on null or empty peerCertificates array */ private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { if (peerCertificates == null || peerCertificates.length == 0) { throw new SSLPeerUnverifiedException("No peer certificates"); } } /** * The identity of the principal that was used by the peer during the SSL * handshake phase is returned by this method. * @return a X500Principal of the last certificate for X509-based * cipher suites. * @throws SSLPeerUnverifiedException if either a non-X.509 certificate * was used (i.e. Kerberos certificates) or the peer does not exist. * */ @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { checkPeerCertificatesPresent(); return peerCertificates[0].getSubjectX500Principal(); } /** * The peer's host name used in this SSL session is returned. It is the host * name of the client for the server; and that of the server for the client. * It is not a reliable way to get a fully qualified host name: it is mainly * used internally to implement links for a temporary cache of SSL sessions. * * @return the host name of the peer, or {@code null} if no information is * available. */ @Override public String getPeerHost() { return peerHost; } /** * Returns the peer's port number for the actual SSL session. It is the port * number of the client for the server; and that of the server for the * client. It is not a reliable way to get a peer's port number: it is * mainly used internally to implement links for a temporary cache of SSL * sessions. * * @return the peer's port number, or {@code -1} if no one is available. */ @Override public int getPeerPort() { return peerPort; } /** * Returns a string identifier of the crypto tools used in the actual SSL * session. For example AES_256_WITH_MD5. */ @Override public String getCipherSuite() { if (cipherSuite == null) { String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer); cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name); if (cipherSuite == null) { cipherSuite = name; } } return cipherSuite; } /** * Returns the standard version name of the SSL protocol used in all * connections pertaining to this SSL session. */ @Override public String getProtocol() { if (protocol == null) { protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer); } return protocol; } /** * Returns the context to which the actual SSL session is bound. A SSL * context consists of (1) a possible delegate, (2) a provider and (3) a * protocol. * @return the SSL context used for this session, or null if it is * unavailable. */ @Override public SSLSessionContext getSessionContext() { return sessionContext; } /** * Returns a boolean flag signaling whether a SSL session is valid * and available for resuming or joining or not. * * @return true if this session may be resumed. */ @Override public boolean isValid() { if (!isValid) { return false; } // The session has't yet been invalidated -- check whether it timed out. SSLSessionContext context = sessionContext; if (context == null) { // Session not associated with a context -- no way to tell what its timeout should be. return true; } int timeoutSeconds = context.getSessionTimeout(); if (timeoutSeconds == 0) { // Infinite timeout -- session still valid return true; } long creationTimestampMillis = getCreationTime(); long ageSeconds = (System.currentTimeMillis() - creationTimestampMillis) / 1000; // NOTE: The age might be negative if something was/is wrong with the system clock. We time // out such sessions to be safe. if ((ageSeconds >= timeoutSeconds) || (ageSeconds < 0)) { // Session timed out -- no longer valid isValid = false; return false; } // Session still valid return true; } /** * It invalidates a SSL session forbidding any resumption. */ @Override public void invalidate() { isValid = false; sessionContext = null; } /** * Returns the object which is bound to the the input parameter name. * This name is a sort of link to the data of the SSL session's application * layer, if any exists. * * @param name the name of the binding to find. * @return the value bound to that name, or null if the binding does not * exist. * @throws IllegalArgumentException if the argument is null. */ @Override public Object getValue(String name) { if (name == null) { throw new IllegalArgumentException("name == null"); } return values.get(name); } /** * Returns an array with the names (sort of links) of all the data * objects of the application layer bound into the SSL session. * * @return a non-null (possibly empty) array of names of the data objects * bound to this SSL session. */ @Override public String[] getValueNames() { return values.keySet().toArray(new String[values.size()]); } /** * A link (name) with the specified value object of the SSL session's * application layer data is created or replaced. If the new (or existing) * value object implements the SSLSessionBindingListener * interface, that object will be notified in due course. * * @param name the name of the link (no null are * accepted!) * @param value data object that shall be bound to * name. * @throws IllegalArgumentException if one or both argument(s) is null. */ @Override public void putValue(String name, Object value) { if (name == null || value == null) { throw new IllegalArgumentException("name == null || value == null"); } Object old = values.put(name, value); if (value instanceof SSLSessionBindingListener) { ((SSLSessionBindingListener) value) .valueBound(new SSLSessionBindingEvent(this, name)); } if (old instanceof SSLSessionBindingListener) { ((SSLSessionBindingListener) old) .valueUnbound(new SSLSessionBindingEvent(this, name)); } } /** * Removes a link (name) with the specified value object of the SSL * session's application layer data. * *

If the value object implements the SSLSessionBindingListener * interface, the object will receive a valueUnbound notification. * * @param name the name of the link (no null are * accepted!) * @throws IllegalArgumentException if the argument is null. */ @Override public void removeValue(String name) { if (name == null) { throw new IllegalArgumentException("name == null"); } Object old = values.remove(name); if (old instanceof SSLSessionBindingListener) { SSLSessionBindingListener listener = (SSLSessionBindingListener) old; listener.valueUnbound(new SSLSessionBindingEvent(this, name)); } } @Override protected void finalize() throws Throwable { try { NativeCrypto.SSL_SESSION_free(sslSessionNativePointer); } finally { super.finalize(); } } }