Home | History | Annotate | Download | only in conscrypt
      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.conscrypt;
     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 SSLPeerUnverifiedException if either a non-X.509 certificate
    197      *         was used (i.e. Kerberos certificates) or the peer could not
    198      *         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 SSLPeerUnverifiedException if either a non-X.509 certificate
    244      *         was used (i.e. Kerberos certificates) or the peer could not
    245      *         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 SSLPeerUnverifiedException if either a non-X.509 certificate
    267      *         was used (i.e. Kerberos certificates) or the peer does not exist.
    268      *
    269      */
    270     public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
    271         checkPeerCertificatesPresent();
    272         return peerCertificates[0].getSubjectX500Principal();
    273     }
    274 
    275     /**
    276      * The peer's host name used in this SSL session is returned. It is the host
    277      * name of the client for the server; and that of the server for the client.
    278      * It is not a reliable way to get a fully qualified host name: it is mainly
    279      * used internally to implement links for a temporary cache of SSL sessions.
    280      *
    281      * @return the host name of the peer, or null if no information is
    282      *         available.
    283      *
    284      */
    285     public String getPeerHost() {
    286         return peerHost;
    287     }
    288 
    289     /**
    290      * Returns the peer's port number for the actual SSL session. It is the port
    291      * number of the client for the server; and that of the server for the
    292      * client. It is not a reliable way to get a peer's port number: it is
    293      * mainly used internally to implement links for a temporary cache of SSL
    294      * sessions.
    295      * @return the peer's port number, or -1 if no one is available.
    296      *
    297      */
    298     public int getPeerPort() {
    299         return peerPort;
    300     }
    301 
    302     /**
    303      * Returns a string identifier of the crypto tools used in the actual SSL
    304      * session. For example AES_256_WITH_MD5.
    305      */
    306     public String getCipherSuite() {
    307         if (cipherSuite == null) {
    308             String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
    309             cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
    310             if (cipherSuite == null) {
    311                 cipherSuite = name;
    312             }
    313         }
    314         return cipherSuite;
    315     }
    316 
    317     /**
    318      * Returns the standard version name of the SSL protocol used in all
    319      * connections pertaining to this SSL session.
    320      */
    321     public String getProtocol() {
    322         if (protocol == null) {
    323             protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
    324         }
    325         return protocol;
    326     }
    327 
    328     /**
    329      * Returns the context to which the actual SSL session is bound. A SSL
    330      * context consists of (1) a possible delegate, (2) a provider and (3) a
    331      * protocol.
    332      * @return the SSL context used for this session, or null if it is
    333      * unavailable.
    334      */
    335     public SSLSessionContext getSessionContext() {
    336         return sessionContext;
    337     }
    338 
    339     /**
    340      * Returns a boolean flag signaling whether a SSL session is valid
    341      * and available for resuming or joining or not.
    342      *
    343      * @return true if this session may be resumed.
    344      */
    345     public boolean isValid() {
    346         SSLSessionContext context = sessionContext;
    347         if (isValid
    348                 && context != null
    349                 && context.getSessionTimeout() != 0
    350                 && getCreationTime() + (context.getSessionTimeout() * 1000)
    351                     < System.currentTimeMillis()) {
    352             isValid = false;
    353         }
    354         return isValid;
    355     }
    356 
    357     /**
    358      * It invalidates a SSL session forbidding any resumption.
    359      */
    360     public void invalidate() {
    361         isValid = false;
    362         sessionContext = null;
    363     }
    364 
    365     /**
    366      * Returns the object which is bound to the the input parameter name.
    367      * This name is a sort of link to the data of the SSL session's application
    368      * layer, if any exists.
    369      *
    370      * @param name the name of the binding to find.
    371      * @return the value bound to that name, or null if the binding does not
    372      *         exist.
    373      * @throws IllegalArgumentException if the argument is null.
    374      */
    375     public Object getValue(String name) {
    376         if (name == null) {
    377             throw new IllegalArgumentException("name == null");
    378         }
    379         return values.get(name);
    380     }
    381 
    382     /**
    383      * Returns an array with the names (sort of links) of all the data
    384      * objects of the application layer bound into the SSL session.
    385      *
    386      * @return a non-null (possibly empty) array of names of the data objects
    387      *         bound to this SSL session.
    388      */
    389     public String[] getValueNames() {
    390         return values.keySet().toArray(new String[values.size()]);
    391     }
    392 
    393     /**
    394      * A link (name) with the specified value object of the SSL session's
    395      * application layer data is created or replaced. If the new (or existing)
    396      * value object implements the <code>SSLSessionBindingListener</code>
    397      * interface, that object will be notified in due course.
    398      *
    399      * @param name the name of the link (no null are
    400      *            accepted!)
    401      * @param value data object that shall be bound to
    402      *            name.
    403      * @throws IllegalArgumentException if one or both argument(s) is null.
    404      */
    405     public void putValue(String name, Object value) {
    406         if (name == null || value == null) {
    407             throw new IllegalArgumentException("name == null || value == null");
    408         }
    409         Object old = values.put(name, value);
    410         if (value instanceof SSLSessionBindingListener) {
    411             ((SSLSessionBindingListener) value)
    412                     .valueBound(new SSLSessionBindingEvent(this, name));
    413         }
    414         if (old instanceof SSLSessionBindingListener) {
    415             ((SSLSessionBindingListener) old)
    416                     .valueUnbound(new SSLSessionBindingEvent(this, name));
    417         }
    418     }
    419 
    420     /**
    421      * Removes a link (name) with the specified value object of the SSL
    422      * session's application layer data.
    423      *
    424      * <p>If the value object implements the <code>SSLSessionBindingListener</code>
    425      * interface, the object will receive a <code>valueUnbound</code> notification.
    426      *
    427      * @param name the name of the link (no null are
    428      *            accepted!)
    429      * @throws IllegalArgumentException if the argument is null.
    430      */
    431     public void removeValue(String name) {
    432         if (name == null) {
    433             throw new IllegalArgumentException("name == null");
    434         }
    435         Object old = values.remove(name);
    436         if (old instanceof SSLSessionBindingListener) {
    437             SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
    438             listener.valueUnbound(new SSLSessionBindingEvent(this, name));
    439         }
    440     }
    441 
    442     @Override protected void finalize() throws Throwable {
    443         try {
    444             NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    445         } finally {
    446             super.finalize();
    447         }
    448     }
    449 }
    450