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