| /* |
| * 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<String, Object> values = new HashMap<String, Object>(); |
| 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 |
| * <code>getPeerCertificates()</code>. |
| * @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 <code>SSLSessionBindingListener</code> |
| * 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. |
| * |
| * <p>If the value object implements the <code>SSLSessionBindingListener</code> |
| * interface, the object will receive a <code>valueUnbound</code> 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(); |
| } |
| } |
| } |