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