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 an SSL session serialized with DER encoding.
     72      * This allows loading of a previously saved OpenSSLSessionImpl.
     73      *
     74      * @throws IOException if the serialized session data can not be parsed
     75      */
     76     OpenSSLSessionImpl(byte[] derData, String peerHost, int peerPort,
     77             X509Certificate[] peerCertificates, AbstractSessionContext sessionContext)
     78             throws IOException {
     79         this(NativeCrypto.d2i_SSL_SESSION(derData), null, peerCertificates, peerHost, peerPort,
     80              sessionContext);
     81     }
     82 
     83     /**
     84      * Gets the identifier of the actual SSL session
     85      * @return array of sessions' identifiers.
     86      */
     87     @Override
     88     public byte[] getId() {
     89         if (id == null) {
     90             resetId();
     91         }
     92         return id;
     93     }
     94 
     95     /**
     96      * Reset the id field to the current value found in the native
     97      * SSL_SESSION. It can change during the lifetime of the session
     98      * because while a session is created during initial handshake,
     99      * with handshake_cutthrough, the SSL_do_handshake may return
    100      * before we have read the session ticket from the server side and
    101      * therefore have computed no id based on the SHA of the ticket.
    102      */
    103     void resetId() {
    104         id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
    105     }
    106 
    107     /**
    108      * Get the session object in DER format. This allows saving the session
    109      * data or sharing it with other processes.
    110      */
    111     byte[] getEncoded() {
    112         return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
    113     }
    114 
    115     /**
    116      * Gets the creation time of the SSL session.
    117      * @return the session's creation time in milliseconds since the epoch
    118      */
    119     @Override
    120     public long getCreationTime() {
    121         if (creationTime == 0) {
    122             creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
    123         }
    124         return creationTime;
    125     }
    126 
    127     /**
    128      * Returns the last time this concrete SSL session was accessed. Accessing
    129      * here is to mean that a new connection with the same SSL context data was
    130      * established.
    131      *
    132      * @return the session's last access time in milliseconds since the epoch
    133      */
    134     @Override
    135     public long getLastAccessedTime() {
    136         return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
    137     }
    138 
    139     /**
    140      * Returns the largest buffer size for the application's data bound to this
    141      * concrete SSL session.
    142      * @return the largest buffer size
    143      */
    144     @Override
    145     public int getApplicationBufferSize() {
    146         return SSLRecordProtocol.MAX_DATA_LENGTH;
    147     }
    148 
    149     /**
    150      * Returns the largest SSL/TLS packet size one can expect for this concrete
    151      * SSL session.
    152      * @return the largest packet size
    153      */
    154     @Override
    155     public int getPacketBufferSize() {
    156         return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
    157     }
    158 
    159     /**
    160      * Returns the principal (subject) of this concrete SSL session used in the
    161      * handshaking phase of the connection.
    162      * @return a X509 certificate or null if no principal was defined
    163      */
    164     @Override
    165     public Principal getLocalPrincipal() {
    166         if (localCertificates != null && localCertificates.length > 0) {
    167             return localCertificates[0].getSubjectX500Principal();
    168         } else {
    169             return null;
    170         }
    171     }
    172 
    173     /**
    174      * Returns the certificate(s) of the principal (subject) of this concrete SSL
    175      * session used in the handshaking phase of the connection. The OpenSSL
    176      * native method supports only RSA certificates.
    177      * @return an array of certificates (the local one first and then eventually
    178      *         that of the certification authority) or null if no certificate
    179      *         were used during the handshaking phase.
    180      */
    181     @Override
    182     public Certificate[] getLocalCertificates() {
    183         return localCertificates;
    184     }
    185 
    186     /**
    187      * Returns the certificate(s) of the peer in this SSL session
    188      * used in the handshaking phase of the connection.
    189      * Please notice hat this method is superseded by
    190      * <code>getPeerCertificates()</code>.
    191      * @return an array of X509 certificates (the peer's one first and then
    192      *         eventually that of the certification authority) or null if no
    193      *         certificate were used during the SSL connection.
    194      * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
    195      *         was used (i.e. Kerberos certificates) or the peer could not
    196      *         be verified.
    197      */
    198     @Override
    199     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
    200             throws SSLPeerUnverifiedException {
    201         checkPeerCertificatesPresent();
    202         javax.security.cert.X509Certificate[] result = peerCertificateChain;
    203         if (result == null) {
    204             // single-check idiom
    205             peerCertificateChain = result = createPeerCertificateChain();
    206         }
    207         return result;
    208     }
    209 
    210     /**
    211      * Provide a value to initialize the volatile peerCertificateChain
    212      * field based on the native SSL_SESSION
    213      */
    214     private javax.security.cert.X509Certificate[] createPeerCertificateChain()
    215             throws SSLPeerUnverifiedException {
    216         try {
    217             javax.security.cert.X509Certificate[] chain
    218                     = new javax.security.cert.X509Certificate[peerCertificates.length];
    219 
    220             for (int i = 0; i < peerCertificates.length; i++) {
    221                 byte[] encoded = peerCertificates[i].getEncoded();
    222                 chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
    223             }
    224             return chain;
    225         } catch (CertificateEncodingException e) {
    226             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    227             exception.initCause(exception);
    228             throw exception;
    229         } catch (CertificateException e) {
    230             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    231             exception.initCause(exception);
    232             throw exception;
    233         }
    234     }
    235 
    236     /**
    237      * Return the identity of the peer in this SSL session
    238      * determined via certificate(s).
    239      * @return an array of X509 certificates (the peer's one first and then
    240      *         eventually that of the certification authority) or null if no
    241      *         certificate were used during the SSL connection.
    242      * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
    243      *         was used (i.e. Kerberos certificates) or the peer could not
    244      *         be verified.
    245      */
    246     @Override
    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     @Override
    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 {@code null} if no information is
    283      *         available.
    284      */
    285     @Override
    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      *
    297      * @return the peer's port number, or {@code -1} if no one is available.
    298      */
    299     @Override
    300     public int getPeerPort() {
    301         return peerPort;
    302     }
    303 
    304     /**
    305      * Returns a string identifier of the crypto tools used in the actual SSL
    306      * session. For example AES_256_WITH_MD5.
    307      */
    308     @Override
    309     public String getCipherSuite() {
    310         if (cipherSuite == null) {
    311             String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
    312             cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
    313             if (cipherSuite == null) {
    314                 cipherSuite = name;
    315             }
    316         }
    317         return cipherSuite;
    318     }
    319 
    320     /**
    321      * Returns the standard version name of the SSL protocol used in all
    322      * connections pertaining to this SSL session.
    323      */
    324     @Override
    325     public String getProtocol() {
    326         if (protocol == null) {
    327             protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
    328         }
    329         return protocol;
    330     }
    331 
    332     /**
    333      * Returns the context to which the actual SSL session is bound. A SSL
    334      * context consists of (1) a possible delegate, (2) a provider and (3) a
    335      * protocol.
    336      * @return the SSL context used for this session, or null if it is
    337      * unavailable.
    338      */
    339     @Override
    340     public SSLSessionContext getSessionContext() {
    341         return sessionContext;
    342     }
    343 
    344     /**
    345      * Returns a boolean flag signaling whether a SSL session is valid
    346      * and available for resuming or joining or not.
    347      *
    348      * @return true if this session may be resumed.
    349      */
    350     @Override
    351     public boolean isValid() {
    352         if (!isValid) {
    353             return false;
    354         }
    355         // The session has't yet been invalidated -- check whether it timed out.
    356 
    357         SSLSessionContext context = sessionContext;
    358         if (context == null) {
    359             // Session not associated with a context -- no way to tell what its timeout should be.
    360             return true;
    361         }
    362 
    363         int timeoutSeconds = context.getSessionTimeout();
    364         if (timeoutSeconds == 0) {
    365             // Infinite timeout -- session still valid
    366             return true;
    367         }
    368 
    369         long creationTimestampMillis = getCreationTime();
    370         long ageSeconds = (System.currentTimeMillis() - creationTimestampMillis) / 1000;
    371         // NOTE: The age might be negative if something was/is wrong with the system clock. We time
    372         // out such sessions to be safe.
    373         if ((ageSeconds >= timeoutSeconds) || (ageSeconds < 0)) {
    374             // Session timed out -- no longer valid
    375             isValid = false;
    376             return false;
    377         }
    378 
    379         // Session still valid
    380         return true;
    381     }
    382 
    383     /**
    384      * It invalidates a SSL session forbidding any resumption.
    385      */
    386     @Override
    387     public void invalidate() {
    388         isValid = false;
    389         sessionContext = null;
    390     }
    391 
    392     /**
    393      * Returns the object which is bound to the the input parameter name.
    394      * This name is a sort of link to the data of the SSL session's application
    395      * layer, if any exists.
    396      *
    397      * @param name the name of the binding to find.
    398      * @return the value bound to that name, or null if the binding does not
    399      *         exist.
    400      * @throws IllegalArgumentException if the argument is null.
    401      */
    402     @Override
    403     public Object getValue(String name) {
    404         if (name == null) {
    405             throw new IllegalArgumentException("name == null");
    406         }
    407         return values.get(name);
    408     }
    409 
    410     /**
    411      * Returns an array with the names (sort of links) of all the data
    412      * objects of the application layer bound into the SSL session.
    413      *
    414      * @return a non-null (possibly empty) array of names of the data objects
    415      *         bound to this SSL session.
    416      */
    417     @Override
    418     public String[] getValueNames() {
    419         return values.keySet().toArray(new String[values.size()]);
    420     }
    421 
    422     /**
    423      * A link (name) with the specified value object of the SSL session's
    424      * application layer data is created or replaced. If the new (or existing)
    425      * value object implements the <code>SSLSessionBindingListener</code>
    426      * interface, that object will be notified in due course.
    427      *
    428      * @param name the name of the link (no null are
    429      *            accepted!)
    430      * @param value data object that shall be bound to
    431      *            name.
    432      * @throws IllegalArgumentException if one or both argument(s) is null.
    433      */
    434     @Override
    435     public void putValue(String name, Object value) {
    436         if (name == null || value == null) {
    437             throw new IllegalArgumentException("name == null || value == null");
    438         }
    439         Object old = values.put(name, value);
    440         if (value instanceof SSLSessionBindingListener) {
    441             ((SSLSessionBindingListener) value)
    442                     .valueBound(new SSLSessionBindingEvent(this, name));
    443         }
    444         if (old instanceof SSLSessionBindingListener) {
    445             ((SSLSessionBindingListener) old)
    446                     .valueUnbound(new SSLSessionBindingEvent(this, name));
    447         }
    448     }
    449 
    450     /**
    451      * Removes a link (name) with the specified value object of the SSL
    452      * session's application layer data.
    453      *
    454      * <p>If the value object implements the <code>SSLSessionBindingListener</code>
    455      * interface, the object will receive a <code>valueUnbound</code> notification.
    456      *
    457      * @param name the name of the link (no null are
    458      *            accepted!)
    459      * @throws IllegalArgumentException if the argument is null.
    460      */
    461     @Override
    462     public void removeValue(String name) {
    463         if (name == null) {
    464             throw new IllegalArgumentException("name == null");
    465         }
    466         Object old = values.remove(name);
    467         if (old instanceof SSLSessionBindingListener) {
    468             SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
    469             listener.valueUnbound(new SSLSessionBindingEvent(this, name));
    470         }
    471     }
    472 
    473     /**
    474      * Returns the name requested by the SNI extension.
    475      */
    476     public String getRequestedServerName() {
    477         return NativeCrypto.get_SSL_SESSION_tlsext_hostname(sslSessionNativePointer);
    478     }
    479 
    480     @Override
    481     protected void finalize() throws Throwable {
    482         try {
    483             // The constructor can throw an exception if this object is constructed from invalid
    484             // saved session data.
    485             if (sslSessionNativePointer != 0) {
    486                 NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    487             }
    488         } finally {
    489             super.finalize();
    490         }
    491     }
    492 }
    493