Home | History | Annotate | Download | only in jsse
      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.apache.harmony.xnet.provider.jsse;
     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 int sslSessionNativePointer;
     48     private String peerHost;
     49     private int peerPort = -1;
     50     private String cipherSuite;
     51     private String protocol;
     52     private String compressionMethod;
     53     private AbstractSessionContext sessionContext;
     54     private byte[] id;
     55 
     56     /**
     57      * Class constructor creates an SSL session context given the appropriate
     58      * SSL parameters.
     59      */
     60     protected OpenSSLSessionImpl(int sslSessionNativePointer, X509Certificate[] localCertificates,
     61             X509Certificate[] peerCertificates, String peerHost, int peerPort,
     62             AbstractSessionContext sessionContext) {
     63         this.sslSessionNativePointer = sslSessionNativePointer;
     64         this.localCertificates = localCertificates;
     65         this.peerCertificates = peerCertificates;
     66         this.peerHost = peerHost;
     67         this.peerPort = peerPort;
     68         this.sessionContext = sessionContext;
     69     }
     70 
     71     /**
     72      * Constructs a session from a byte[] containing DER data. This
     73      * allows loading the saved session.
     74      * @throws IOException
     75      */
     76     OpenSSLSessionImpl(byte[] derData,
     77             String peerHost, int peerPort,
     78             X509Certificate[] peerCertificates,
     79             AbstractSessionContext sessionContext)
     80             throws IOException {
     81         this(NativeCrypto.d2i_SSL_SESSION(derData),
     82              null,
     83              peerCertificates,
     84              peerHost,
     85              peerPort,
     86              sessionContext);
     87         // TODO move this check into native code so we can throw an error with more information
     88         if (this.sslSessionNativePointer == 0) {
     89             throw new IOException("Invalid session data");
     90         }
     91     }
     92 
     93     /**
     94      * Gets the identifier of the actual SSL session
     95      * @return array of sessions' identifiers.
     96      */
     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     public long getCreationTime() {
    129         if (creationTime == 0) {
    130             creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
    131         }
    132         return creationTime;
    133     }
    134 
    135     /**
    136      * Returns the last time this concrete SSL session was accessed. Accessing
    137      * here is to mean that a new connection with the same SSL context data was
    138      * established.
    139      *
    140      * @return the session's last access time in milliseconds since the epoch
    141      */
    142     public long getLastAccessedTime() {
    143         return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
    144     }
    145 
    146     /**
    147      * Returns the largest buffer size for the application's data bound to this
    148      * concrete SSL session.
    149      * @return the largest buffer size
    150      */
    151     public int getApplicationBufferSize() {
    152         return SSLRecordProtocol.MAX_DATA_LENGTH;
    153     }
    154 
    155     /**
    156      * Returns the largest SSL/TLS packet size one can expect for this concrete
    157      * SSL session.
    158      * @return the largest packet size
    159      */
    160     public int getPacketBufferSize() {
    161         return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
    162     }
    163 
    164     /**
    165      * Returns the principal (subject) of this concrete SSL session used in the
    166      * handshaking phase of the connection.
    167      * @return a X509 certificate or null if no principal was defined
    168      */
    169     public Principal getLocalPrincipal() {
    170         if (localCertificates != null && localCertificates.length > 0) {
    171             return localCertificates[0].getSubjectX500Principal();
    172         } else {
    173             return null;
    174         }
    175     }
    176 
    177     /**
    178      * Returns the certificate(s) of the principal (subject) of this concrete SSL
    179      * session used in the handshaking phase of the connection. The OpenSSL
    180      * native method supports only RSA certificates.
    181      * @return an array of certificates (the local one first and then eventually
    182      *         that of the certification authority) or null if no certificate
    183      *         were used during the handshaking phase.
    184      */
    185     public Certificate[] getLocalCertificates() {
    186         return localCertificates;
    187     }
    188 
    189     /**
    190      * Returns the certificate(s) of the peer in this SSL session
    191      * used in the handshaking phase of the connection.
    192      * Please notice hat this method is superseded by
    193      * <code>getPeerCertificates()</code>.
    194      * @return an array of X509 certificates (the peer's one first and then
    195      *         eventually that of the certification authority) or null if no
    196      *         certificate were used during the SSL connection.
    197      * @throws <code>SSLPeerUnverifiedCertificateException</code> if either a
    198      *         not X509 certificate was used (i.e. Kerberos certificates) or the
    199      *         peer could not be verified.
    200      */
    201     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
    202             throws SSLPeerUnverifiedException {
    203         checkPeerCertificatesPresent();
    204         javax.security.cert.X509Certificate[] result = peerCertificateChain;
    205         if (result == null) {
    206             // single-check idiom
    207             peerCertificateChain = result = createPeerCertificateChain();
    208         }
    209         return result;
    210     }
    211 
    212     /**
    213      * Provide a value to initialize the volatile peerCertificateChain
    214      * field based on the native SSL_SESSION
    215      */
    216     private javax.security.cert.X509Certificate[] createPeerCertificateChain()
    217             throws SSLPeerUnverifiedException {
    218         try {
    219             javax.security.cert.X509Certificate[] chain
    220                     = new javax.security.cert.X509Certificate[peerCertificates.length];
    221 
    222             for (int i = 0; i < peerCertificates.length; i++) {
    223                 byte[] encoded = peerCertificates[i].getEncoded();
    224                 chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
    225             }
    226             return chain;
    227         } catch (CertificateEncodingException e) {
    228             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    229             exception.initCause(exception);
    230             throw exception;
    231         } catch (CertificateException e) {
    232             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    233             exception.initCause(exception);
    234             throw exception;
    235         }
    236     }
    237 
    238     /**
    239      * Return the identity of the peer in this SSL session
    240      * determined via certificate(s).
    241      * @return an array of X509 certificates (the peer's one first and then
    242      *         eventually that of the certification authority) or null if no
    243      *         certificate were used during the SSL connection.
    244      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    245      *         certificate was used (i.e. Kerberos certificates) or the peer
    246      *         could not be verified.
    247      */
    248     public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
    249         checkPeerCertificatesPresent();
    250         return peerCertificates;
    251     }
    252 
    253     /**
    254      * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
    255      */
    256     private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
    257         if (peerCertificates == null || peerCertificates.length == 0) {
    258             throw new SSLPeerUnverifiedException("No peer certificates");
    259         }
    260     }
    261 
    262     /**
    263      * The identity of the principal that was used by the peer during the SSL
    264      * handshake phase is returned by this method.
    265      * @return a X500Principal of the last certificate for X509-based
    266      *         cipher suites.
    267      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    268      *         certificate was used (i.e. Kerberos certificates) or the
    269      *         peer does not exist.
    270      *
    271      */
    272     public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
    273         checkPeerCertificatesPresent();
    274         return peerCertificates[0].getSubjectX500Principal();
    275     }
    276 
    277     /**
    278      * The peer's host name used in this SSL session is returned. It is the host
    279      * name of the client for the server; and that of the server for the client.
    280      * It is not a reliable way to get a fully qualified host name: it is mainly
    281      * used internally to implement links for a temporary cache of SSL sessions.
    282      *
    283      * @return the host name of the peer, or null if no information is
    284      *         available.
    285      *
    286      */
    287     public String getPeerHost() {
    288         return peerHost;
    289     }
    290 
    291     /**
    292      * Returns the peer's port number for the actual SSL session. It is the port
    293      * number of the client for the server; and that of the server for the
    294      * client. It is not a reliable way to get a peer's port number: it is
    295      * mainly used internally to implement links for a temporary cache of SSL
    296      * sessions.
    297      * @return the peer's port number, or -1 if no one is available.
    298      *
    299      */
    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     public String getCipherSuite() {
    309         if (cipherSuite == null) {
    310             String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
    311             cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
    312             if (cipherSuite == null) {
    313                 cipherSuite = name;
    314             }
    315         }
    316         return cipherSuite;
    317     }
    318 
    319     /**
    320      * Returns the standard version name of the SSL protocol used in all
    321      * connections pertaining to this SSL session.
    322      */
    323     public String getProtocol() {
    324         if (protocol == null) {
    325             protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
    326         }
    327         return protocol;
    328     }
    329 
    330     /**
    331      * Returns the compression method name used in all connections
    332      * pertaining to this SSL session.
    333      */
    334     public String getCompressionMethod() {
    335         if (compressionMethod == null) {
    336             compressionMethod
    337                     = NativeCrypto.SSL_SESSION_compress_meth(sessionContext.sslCtxNativePointer,
    338                                                              sslSessionNativePointer);
    339         }
    340         return compressionMethod;
    341     }
    342 
    343     /**
    344      * Returns the context to which the actual SSL session is bound. A SSL
    345      * context consists of (1) a possible delegate, (2) a provider and (3) a
    346      * protocol.
    347      * @return the SSL context used for this session, or null if it is
    348      * unavailable.
    349      */
    350     public SSLSessionContext getSessionContext() {
    351         return sessionContext;
    352     }
    353 
    354     /**
    355      * Returns a boolean flag signaling whether a SSL session is valid
    356      * and available for resuming or joining or not.
    357      *
    358      * @return true if this session may be resumed.
    359      */
    360     public boolean isValid() {
    361         SSLSessionContext context = sessionContext;
    362         if (isValid
    363                 && context != null
    364                 && context.getSessionTimeout() != 0
    365                 && getCreationTime() + (context.getSessionTimeout() * 1000)
    366                     < System.currentTimeMillis()) {
    367             isValid = false;
    368         }
    369         return isValid;
    370     }
    371 
    372     /**
    373      * It invalidates a SSL session forbidding any resumption.
    374      */
    375     public void invalidate() {
    376         isValid = false;
    377         sessionContext = null;
    378     }
    379 
    380     /**
    381      * Returns the object which is bound to the the input parameter name.
    382      * This name is a sort of link to the data of the SSL session's application
    383      * layer, if any exists. The search for this link is monitored, as a matter
    384      * of security, by the full machinery of the <code>AccessController</code>
    385      * class.
    386      *
    387      * @param name the name of the binding to find.
    388      * @return the value bound to that name, or null if the binding does not
    389      *         exist.
    390      * @throws <code>IllegalArgumentException</code> if the argument is null.
    391      */
    392     public Object getValue(String name) {
    393         if (name == null) {
    394             throw new IllegalArgumentException("name == null");
    395         }
    396         return values.get(name);
    397     }
    398 
    399     /**
    400      * Returns an array with the names (sort of links) of all the data
    401      * objects of the application layer bound into the SSL session. The search
    402      * for this link is monitored, as a matter of security, by the full
    403      * machinery of the <code>AccessController</code> class.
    404      *
    405      * @return a non-null (possibly empty) array of names of the data objects
    406      *         bound to this SSL session.
    407      */
    408     public String[] getValueNames() {
    409         return values.keySet().toArray(new String[values.size()]);
    410     }
    411 
    412     /**
    413      * A link (name) with the specified value object of the SSL session's
    414      * application layer data is created or replaced. If the new (or existing)
    415      * value object implements the <code>SSLSessionBindingListener</code>
    416      * interface, that object will be notified in due course. These links-to
    417      * -data bounds are monitored, as a matter of security, by the full
    418      * machinery of the <code>AccessController</code> class.
    419      *
    420      * @param name the name of the link (no null are
    421      *            accepted!)
    422      * @param value data object that shall be bound to
    423      *            name.
    424      * @throws <code>IllegalArgumentException</code> if one or both
    425      *             argument(s) is null.
    426      */
    427     public void putValue(String name, Object value) {
    428         if (name == null || value == null) {
    429             throw new IllegalArgumentException("name == null || value == null");
    430         }
    431         Object old = values.put(name, value);
    432         if (value instanceof SSLSessionBindingListener) {
    433             ((SSLSessionBindingListener) value)
    434                     .valueBound(new SSLSessionBindingEvent(this, name));
    435         }
    436         if (old instanceof SSLSessionBindingListener) {
    437             ((SSLSessionBindingListener) old)
    438                     .valueUnbound(new SSLSessionBindingEvent(this, name));
    439         }
    440     }
    441 
    442     /**
    443      * Removes a link (name) with the specified value object of the SSL
    444      * session's application layer data.
    445      *
    446      * <p>If the value object implements the <code>SSLSessionBindingListener</code>
    447      * interface, the object will receive a <code>valueUnbound</code> notification.
    448      *
    449      * <p>These links-to -data bounds are
    450      * monitored, as a matter of security, by the full machinery of the
    451      * <code>AccessController</code> class.
    452      *
    453      * @param name the name of the link (no null are
    454      *            accepted!)
    455      * @throws <code>IllegalArgumentException</code> if the argument is null.
    456      */
    457     public void removeValue(String name) {
    458         if (name == null) {
    459             throw new IllegalArgumentException("name == null");
    460         }
    461         Object old = values.remove(name);
    462         if (old instanceof SSLSessionBindingListener) {
    463             SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
    464             listener.valueUnbound(new SSLSessionBindingEvent(this, name));
    465         }
    466     }
    467 
    468     @Override protected void finalize() throws Throwable {
    469         try {
    470             NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    471         } finally {
    472             super.finalize();
    473         }
    474     }
    475 }
    476