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.AccessControlContext;
     21 import java.security.AccessController;
     22 import java.security.Principal;
     23 import java.security.cert.Certificate;
     24 import java.security.cert.CertificateEncodingException;
     25 import java.security.cert.X509Certificate;
     26 import java.util.Map;
     27 import java.util.Vector;
     28 import java.util.Set;
     29 import javax.net.ssl.SSLPeerUnverifiedException;
     30 import javax.net.ssl.SSLPermission;
     31 import javax.net.ssl.SSLSession;
     32 import javax.net.ssl.SSLSessionBindingEvent;
     33 import javax.net.ssl.SSLSessionBindingListener;
     34 import javax.net.ssl.SSLSessionContext;
     35 import javax.security.cert.CertificateException;
     36 import libcore.base.Objects;
     37 import org.apache.harmony.luni.util.TwoKeyHashMap;
     38 
     39 /**
     40  * Implementation of the class OpenSSLSessionImpl
     41  * based on OpenSSL.
     42  */
     43 public class OpenSSLSessionImpl implements SSLSession {
     44 
     45     private long creationTime = 0;
     46     long lastAccessedTime = 0;
     47     final X509Certificate[] localCertificates;
     48     final X509Certificate[] peerCertificates;
     49 
     50     private boolean isValid = true;
     51     private TwoKeyHashMap values = new TwoKeyHashMap<String, AccessControlContext, Object>();
     52     private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
     53     protected int sslSessionNativePointer;
     54     private String peerHost;
     55     private int peerPort = -1;
     56     private String cipherSuite;
     57     private String protocol;
     58     private String compressionMethod;
     59     private AbstractSessionContext sessionContext;
     60     private byte[] id;
     61 
     62     /**
     63      * Class constructor creates an SSL session context given the appropriate
     64      * SSL parameters.
     65      *
     66      * @param session the Identifier for SSL session
     67      * @param sslParameters the SSL parameters like ciphers' suites etc.
     68      */
     69     protected OpenSSLSessionImpl(int sslSessionNativePointer, X509Certificate[] localCertificates,
     70             X509Certificate[] peerCertificates, String peerHost, int peerPort,
     71             AbstractSessionContext sessionContext) {
     72         this.sslSessionNativePointer = sslSessionNativePointer;
     73         this.localCertificates = localCertificates;
     74         this.peerCertificates = peerCertificates;
     75         this.peerHost = peerHost;
     76         this.peerPort = peerPort;
     77         this.sessionContext = sessionContext;
     78     }
     79 
     80     /**
     81      * Constructs a session from a byte[] containing DER data. This
     82      * allows loading the saved session.
     83      * @throws IOException
     84      */
     85     OpenSSLSessionImpl(byte[] derData,
     86             String peerHost, int peerPort,
     87             X509Certificate[] peerCertificates,
     88             AbstractSessionContext sessionContext)
     89             throws IOException {
     90         this(NativeCrypto.d2i_SSL_SESSION(derData),
     91              null,
     92              peerCertificates,
     93              peerHost,
     94              peerPort,
     95              sessionContext);
     96         // TODO move this check into native code so we can throw an error with more information
     97         if (this.sslSessionNativePointer == 0) {
     98             throw new IOException("Invalid session data");
     99         }
    100     }
    101 
    102     /**
    103      * Gets the identifier of the actual SSL session
    104      * @return array of sessions' identifiers.
    105      */
    106     public byte[] getId() {
    107         if (id == null) {
    108             resetId();
    109         }
    110         return id;
    111     }
    112 
    113     /**
    114      * Reset the id field to the current value found in the native
    115      * SSL_SESSION. It can change during the lifetime of the session
    116      * because while a session is created during initial handshake,
    117      * with handshake_cutthrough, the SSL_do_handshake may return
    118      * before we have read the session ticket from the server side and
    119      * therefore have computed no id based on the SHA of the ticket.
    120      */
    121     void resetId() {
    122         id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
    123     }
    124 
    125     /**
    126      * Get the session object in DER format. This allows saving the session
    127      * data or sharing it with other processes.
    128      */
    129     byte[] getEncoded() {
    130         return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
    131     }
    132 
    133     /**
    134      * Gets the creation time of the SSL session.
    135      * @return the session's creation time in milliseconds since the epoch
    136      */
    137     public long getCreationTime() {
    138         if (creationTime == 0) {
    139             creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
    140         }
    141         return creationTime;
    142     }
    143 
    144     /**
    145      * Returns the last time this concrete SSL session was accessed. Accessing
    146      * here is to mean that a new connection with the same SSL context data was
    147      * established.
    148      *
    149      * @return the session's last access time in milliseconds since the epoch
    150      */
    151     public long getLastAccessedTime() {
    152         return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
    153     }
    154 
    155     /**
    156      * Returns the largest buffer size for the application's data bound to this
    157      * concrete SSL session.
    158      * @return the largest buffer size
    159      */
    160     public int getApplicationBufferSize() {
    161         return SSLRecordProtocol.MAX_DATA_LENGTH;
    162     }
    163 
    164     /**
    165      * Returns the largest SSL/TLS packet size one can expect for this concrete
    166      * SSL session.
    167      * @return the largest packet size
    168      */
    169     public int getPacketBufferSize() {
    170         return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
    171     }
    172 
    173     /**
    174      * Returns the principal (subject) of this concrete SSL session used in the
    175      * handshaking phase of the connection.
    176      * @return a X509 certificate or null if no principal was defined
    177      */
    178     public Principal getLocalPrincipal() {
    179         if (localCertificates != null && localCertificates.length > 0) {
    180             return localCertificates[0].getSubjectX500Principal();
    181         } else {
    182             return null;
    183         }
    184     }
    185 
    186     /**
    187      * Returns the certificate(s) of the principal (subject) of this concrete SSL
    188      * session used in the handshaking phase of the connection. The OpenSSL
    189      * native method supports only RSA certificates.
    190      * @return an array of certificates (the local one first and then eventually
    191      *         that of the certification authority) or null if no certificate
    192      *         were used during the handshaking phase.
    193      */
    194     public Certificate[] getLocalCertificates() {
    195         return localCertificates;
    196     }
    197 
    198     /**
    199      * Returns the certificate(s) of the peer in this SSL session
    200      * used in the handshaking phase of the connection.
    201      * Please notice hat this method is superseded by
    202      * <code>getPeerCertificates()</code>.
    203      * @return an array of X509 certificates (the peer's one first and then
    204      *         eventually that of the certification authority) or null if no
    205      *         certificate were used during the SSL connection.
    206      * @throws <code>SSLPeerUnverifiedCertificateException</code> if either a
    207      *         not X509 certificate was used (i.e. Kerberos certificates) or the
    208      *         peer could not be verified.
    209      */
    210     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
    211             throws SSLPeerUnverifiedException {
    212         checkPeerCertificatesPresent();
    213         javax.security.cert.X509Certificate[] result = peerCertificateChain;
    214         if (result == null) {
    215             // single-check idiom
    216             peerCertificateChain = result = createPeerCertificateChain();
    217         }
    218         return result;
    219     }
    220 
    221     /**
    222      * Provide a value to initialize the volatile peerCertificateChain
    223      * field based on the native SSL_SESSION
    224      */
    225     private javax.security.cert.X509Certificate[] createPeerCertificateChain()
    226             throws SSLPeerUnverifiedException {
    227         try {
    228             javax.security.cert.X509Certificate[] chain
    229                     = new javax.security.cert.X509Certificate[peerCertificates.length];
    230 
    231             for (int i = 0; i < peerCertificates.length; i++) {
    232                 byte[] encoded = peerCertificates[i].getEncoded();
    233                 chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
    234             }
    235             return chain;
    236         } catch (CertificateEncodingException e) {
    237             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    238             exception.initCause(exception);
    239             throw exception;
    240         } catch (CertificateException e) {
    241             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
    242             exception.initCause(exception);
    243             throw exception;
    244         }
    245     }
    246 
    247     /**
    248      * Return the identitity of the peer in this SSL session
    249      * determined via certificate(s).
    250      * @return an array of X509 certificates (the peer's one first and then
    251      *         eventually that of the certification authority) or null if no
    252      *         certificate were used during the SSL connection.
    253      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    254      *         certificate was used (i.e. Kerberos certificates) or the peer
    255      *         could not be verified.
    256      */
    257     public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
    258         checkPeerCertificatesPresent();
    259         return peerCertificates;
    260     }
    261 
    262     /**
    263      * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
    264      */
    265     private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
    266         if (peerCertificates == null || peerCertificates.length == 0) {
    267             throw new SSLPeerUnverifiedException("No peer certificates");
    268         }
    269     }
    270 
    271     /**
    272      * The identity of the principal that was used by the peer during the SSL
    273      * handshake phase is returned by this method.
    274      * @return a X500Principal of the last certificate for X509-based
    275      *         cipher suites.
    276      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    277      *         certificate was used (i.e. Kerberos certificates) or the
    278      *         peer does not exist.
    279      *
    280      */
    281     public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
    282         checkPeerCertificatesPresent();
    283         return peerCertificates[0].getSubjectX500Principal();
    284     }
    285 
    286     /**
    287      * The peer's host name used in this SSL session is returned. It is the host
    288      * name of the client for the server; and that of the server for the client.
    289      * It is not a reliable way to get a fully qualified host name: it is mainly
    290      * used internally to implement links for a temporary cache of SSL sessions.
    291      *
    292      * @return the host name of the peer, or null if no information is
    293      *         available.
    294      *
    295      */
    296     public String getPeerHost() {
    297         return peerHost;
    298     }
    299 
    300     /**
    301      * Returns the peer's port number for the actual SSL session. It is the port
    302      * number of the client for the server; and that of the server for the
    303      * client. It is not a reliable way to get a peer's port number: it is
    304      * mainly used internally to implement links for a temporary cache of SSL
    305      * sessions.
    306      * @return the peer's port number, or -1 if no one is available.
    307      *
    308      */
    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      * @return an identifier for all the cryptographic algorithms used in the
    318      *         actual SSL session.
    319      */
    320     public String getCipherSuite() {
    321         if (cipherSuite == null) {
    322             String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
    323             cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
    324             if (cipherSuite == null) {
    325                 cipherSuite = name;
    326             }
    327         }
    328         return cipherSuite;
    329     }
    330 
    331     /**
    332      * Returns the standard version name of the SSL protocol used in all
    333      * connections pertaining to this SSL session.
    334      *
    335      * @return the standard version name of the SSL protocol used in all
    336      *         connections pertaining to this SSL session.
    337      *
    338      */
    339     public String getProtocol() {
    340         if (protocol == null) {
    341             protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
    342         }
    343         return protocol;
    344     }
    345 
    346     /**
    347      * Returns the compression method name used in all connections
    348      * pertaining to this SSL session.
    349      *
    350      * @return the compresison method used in all connections
    351      *         pertaining to this SSL session.
    352      *
    353      */
    354     public String getCompressionMethod() {
    355         if (compressionMethod == null) {
    356             compressionMethod
    357                     = NativeCrypto.SSL_SESSION_compress_meth(sessionContext.sslCtxNativePointer,
    358                                                              sslSessionNativePointer);
    359         }
    360         return compressionMethod;
    361     }
    362 
    363     /**
    364      * Returns the context to which the actual SSL session is bound. A SSL
    365      * context consists of (1) a possible delegate, (2) a provider and (3) a
    366      * protocol. If the security manager is activated and one tries to access
    367      * the SSL context an exception may be thrown if a
    368      * <code>SSLPermission("getSSLSessionContext")</code>
    369      * permission is not set.
    370      * @return the SSL context used for this session, or null if it is
    371      * unavailable.
    372      */
    373     public SSLSessionContext getSessionContext() {
    374         SecurityManager sm = System.getSecurityManager();
    375         if (sm != null) {
    376             sm.checkPermission(new SSLPermission("getSSLSessionContext"));
    377         }
    378         return sessionContext;
    379     }
    380 
    381     /**
    382      * Returns a boolean flag signaling whether a SSL session is valid
    383      * and available for resuming or joining or not.
    384      *
    385      * @return true if this session may be resumed.
    386      */
    387     public boolean isValid() {
    388         SSLSessionContext context = sessionContext;
    389         if (isValid
    390                 && context != null
    391                 && context.getSessionTimeout() != 0
    392                 && getCreationTime() + (context.getSessionTimeout() * 1000)
    393                     < System.currentTimeMillis()) {
    394             isValid = false;
    395         }
    396         return isValid;
    397     }
    398 
    399     /**
    400      * It invalidates a SSL session forbidding any resumption.
    401      */
    402     public void invalidate() {
    403         isValid = false;
    404         sessionContext = null;
    405     }
    406 
    407     /**
    408      * Returns the object which is bound to the the input parameter name.
    409      * This name is a sort of link to the data of the SSL session's application
    410      * layer, if any exists. The search for this link is monitored, as a matter
    411      * of security, by the full machinery of the <code>AccessController</code>
    412      * class.
    413      *
    414      * @param name the name of the binding to find.
    415      * @return the value bound to that name, or null if the binding does not
    416      *         exist.
    417      * @throws <code>IllegalArgumentException</code> if the argument is null.
    418      */
    419     public Object getValue(String name) {
    420         if (name == null) {
    421             throw new IllegalArgumentException("Parameter is null");
    422         }
    423         return values.get(name, AccessController.getContext());
    424     }
    425 
    426     /**
    427      * Returns an array with the names (sort of links) of all the data
    428      * objects of the application layer bound into the SSL session. The search
    429      * for this link is monitored, as a matter of security, by the full
    430      * machinery of the <code>AccessController</code> class.
    431      *
    432      * @return a non-null (possibly empty) array of names of the data objects
    433      *         bound to this SSL session.
    434      */
    435     public String[] getValueNames() {
    436         Vector<String> v = new Vector<String>();
    437         AccessControlContext current = AccessController.getContext();
    438         Set<Map.Entry<String, Object>> set = values.entrySet();
    439         for (Map.Entry<String, Object> o : set) {
    440             TwoKeyHashMap.Entry<String, AccessControlContext, Object> entry
    441                     = (TwoKeyHashMap.Entry<String, AccessControlContext, Object>) o;
    442             AccessControlContext cont = entry.getKey2();
    443             if (Objects.equal(current, cont)) {
    444                 v.add(entry.getKey1());
    445             }
    446         }
    447         return v.toArray(new String[v.size()]);
    448     }
    449 
    450     /**
    451      * A link (name) with the specified value object of the SSL session's
    452      * application layer data is created or replaced. If the new (or existing)
    453      * value object implements the <code>SSLSessionBindingListener</code>
    454      * interface, that object will be notified in due course. These links-to
    455      * -data bounds are monitored, as a matter of security, by the full
    456      * machinery of the <code>AccessController</code> class.
    457      *
    458      * @param name the name of the link (no null are
    459      *            accepted!)
    460      * @param value data object that shall be bound to
    461      *            name.
    462      * @throws <code>IllegalArgumentException</code> if one or both
    463      *             argument(s) is null.
    464      */
    465     public void putValue(String name, Object value) {
    466         if (name == null || value == null) {
    467             throw new IllegalArgumentException("Parameter is null");
    468         }
    469         Object old = values.put(name, AccessController.getContext(), value);
    470         if (value instanceof SSLSessionBindingListener) {
    471             ((SSLSessionBindingListener) value)
    472                     .valueBound(new SSLSessionBindingEvent(this, name));
    473         }
    474         if (old instanceof SSLSessionBindingListener) {
    475             ((SSLSessionBindingListener) old)
    476                     .valueUnbound(new SSLSessionBindingEvent(this, name));
    477         }
    478     }
    479 
    480     /**
    481      * Removes a link (name) with the specified value object of the SSL
    482      * session's application layer data.
    483      *
    484      * <p>If the value object implements the <code>SSLSessionBindingListener</code>
    485      * interface, the object will receive a <code>valueUnbound</code> notification.
    486      *
    487      * <p>These links-to -data bounds are
    488      * monitored, as a matter of security, by the full machinery of the
    489      * <code>AccessController</code> class.
    490      *
    491      * @param name the name of the link (no null are
    492      *            accepted!)
    493      * @throws <code>IllegalArgumentException</code> if the argument is null.
    494      */
    495     public void removeValue(String name) {
    496         if (name == null) {
    497             throw new IllegalArgumentException("Parameter is null");
    498         }
    499         Object old = values.remove(name, AccessController.getContext());
    500         if (old instanceof SSLSessionBindingListener) {
    501             SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
    502             listener.valueUnbound(new SSLSessionBindingEvent(this, name));
    503         }
    504     }
    505 
    506     @Override protected void finalize() throws Throwable {
    507         try {
    508             NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    509         } finally {
    510             super.finalize();
    511         }
    512     }
    513 }
    514