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             throw new SSLPeerUnverifiedException(e.getMessage());
    238         } catch (CertificateException e) {
    239             throw new SSLPeerUnverifiedException(e.getMessage());
    240         }
    241     }
    242 
    243     /**
    244      * Return the identitity of the peer in this SSL session
    245      * determined via certificate(s).
    246      * @return an array of X509 certificates (the peer's one first and then
    247      *         eventually that of the certification authority) or null if no
    248      *         certificate were used during the SSL connection.
    249      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    250      *         certificate was used (i.e. Kerberos certificates) or the peer
    251      *         could not be verified.
    252      */
    253     public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
    254         checkPeerCertificatesPresent();
    255         return peerCertificates;
    256     }
    257 
    258     /**
    259      * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
    260      */
    261     private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
    262         if (peerCertificates == null || peerCertificates.length == 0) {
    263             throw new SSLPeerUnverifiedException("No peer certificates");
    264         }
    265     }
    266 
    267     /**
    268      * The identity of the principal that was used by the peer during the SSL
    269      * handshake phase is returned by this method.
    270      * @return a X500Principal of the last certificate for X509-based
    271      *         cipher suites.
    272      * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
    273      *         certificate was used (i.e. Kerberos certificates) or the
    274      *         peer does not exist.
    275      *
    276      */
    277     public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
    278         checkPeerCertificatesPresent();
    279         return peerCertificates[0].getSubjectX500Principal();
    280     }
    281 
    282     /**
    283      * The peer's host name used in this SSL session is returned. It is the host
    284      * name of the client for the server; and that of the server for the client.
    285      * It is not a reliable way to get a fully qualified host name: it is mainly
    286      * used internally to implement links for a temporary cache of SSL sessions.
    287      *
    288      * @return the host name of the peer, or null if no information is
    289      *         available.
    290      *
    291      */
    292     public String getPeerHost() {
    293         return peerHost;
    294     }
    295 
    296     /**
    297      * Returns the peer's port number for the actual SSL session. It is the port
    298      * number of the client for the server; and that of the server for the
    299      * client. It is not a reliable way to get a peer's port number: it is
    300      * mainly used internally to implement links for a temporary cache of SSL
    301      * sessions.
    302      * @return the peer's port number, or -1 if no one is available.
    303      *
    304      */
    305     public int getPeerPort() {
    306         return peerPort;
    307     }
    308 
    309     /**
    310      * Returns a string identifier of the crypto tools used in the actual SSL
    311      * session. For example AES_256_WITH_MD5.
    312      *
    313      * @return an identifier for all the cryptographic algorithms used in the
    314      *         actual SSL session.
    315      */
    316     public String getCipherSuite() {
    317         if (cipherSuite == null) {
    318             String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
    319             cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
    320             if (cipherSuite == null) {
    321                 cipherSuite = name;
    322             }
    323         }
    324         return cipherSuite;
    325     }
    326 
    327     /**
    328      * Returns the standard version name of the SSL protocol used in all
    329      * connections pertaining to this SSL session.
    330      *
    331      * @return the standard version name of the SSL protocol used in all
    332      *         connections pertaining to this SSL session.
    333      *
    334      */
    335     public String getProtocol() {
    336         if (protocol == null) {
    337             protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
    338         }
    339         return protocol;
    340     }
    341 
    342     /**
    343      * Returns the compression method name used in all connections
    344      * pertaining to this SSL session.
    345      *
    346      * @return the compresison method used in all connections
    347      *         pertaining to this SSL session.
    348      *
    349      */
    350     public String getCompressionMethod() {
    351         if (compressionMethod == null) {
    352             compressionMethod
    353                     = NativeCrypto.SSL_SESSION_compress_meth(sessionContext.sslCtxNativePointer,
    354                                                              sslSessionNativePointer);
    355         }
    356         return compressionMethod;
    357     }
    358 
    359     /**
    360      * Returns the context to which the actual SSL session is bound. A SSL
    361      * context consists of (1) a possible delegate, (2) a provider and (3) a
    362      * protocol. If the security manager is activated and one tries to access
    363      * the SSL context an exception may be thrown if a
    364      * <code>SSLPermission("getSSLSessionContext")</code>
    365      * permission is not set.
    366      * @return the SSL context used for this session, or null if it is
    367      * unavailable.
    368      */
    369     public SSLSessionContext getSessionContext() {
    370         SecurityManager sm = System.getSecurityManager();
    371         if (sm != null) {
    372             sm.checkPermission(new SSLPermission("getSSLSessionContext"));
    373         }
    374         return sessionContext;
    375     }
    376 
    377     /**
    378      * Returns a boolean flag signaling whether a SSL session is valid
    379      * and available for resuming or joining or not.
    380      *
    381      * @return true if this session may be resumed.
    382      */
    383     public boolean isValid() {
    384         SSLSessionContext context = sessionContext;
    385         if (isValid
    386                 && context != null
    387                 && context.getSessionTimeout() != 0
    388                 && getCreationTime() + (context.getSessionTimeout() * 1000)
    389                     < System.currentTimeMillis()) {
    390             isValid = false;
    391         }
    392         return isValid;
    393     }
    394 
    395     /**
    396      * It invalidates a SSL session forbidding any resumption.
    397      */
    398     public void invalidate() {
    399         isValid = false;
    400         sessionContext = null;
    401     }
    402 
    403     /**
    404      * Returns the object which is bound to the the input parameter name.
    405      * This name is a sort of link to the data of the SSL session's application
    406      * layer, if any exists. The search for this link is monitored, as a matter
    407      * of security, by the full machinery of the <code>AccessController</code>
    408      * class.
    409      *
    410      * @param name the name of the binding to find.
    411      * @return the value bound to that name, or null if the binding does not
    412      *         exist.
    413      * @throws <code>IllegalArgumentException</code> if the argument is null.
    414      */
    415     public Object getValue(String name) {
    416         if (name == null) {
    417             throw new IllegalArgumentException("Parameter is null");
    418         }
    419         return values.get(name, AccessController.getContext());
    420     }
    421 
    422     /**
    423      * Returns an array with the names (sort of links) of all the data
    424      * objects of the application layer bound into the SSL session. The search
    425      * for this link is monitored, as a matter of security, by the full
    426      * machinery of the <code>AccessController</code> class.
    427      *
    428      * @return a non-null (possibly empty) array of names of the data objects
    429      *         bound to this SSL session.
    430      */
    431     public String[] getValueNames() {
    432         Vector<String> v = new Vector<String>();
    433         AccessControlContext current = AccessController.getContext();
    434         Set<Map.Entry<String, Object>> set = values.entrySet();
    435         for (Map.Entry<String, Object> o : set) {
    436             TwoKeyHashMap.Entry<String, AccessControlContext, Object> entry
    437                     = (TwoKeyHashMap.Entry<String, AccessControlContext, Object>) o;
    438             AccessControlContext cont = entry.getKey2();
    439             if (Objects.equal(current, cont)) {
    440                 v.add(entry.getKey1());
    441             }
    442         }
    443         return v.toArray(new String[v.size()]);
    444     }
    445 
    446     /**
    447      * A link (name) with the specified value object of the SSL session's
    448      * application layer data is created or replaced. If the new (or existing)
    449      * value object implements the <code>SSLSessionBindingListener</code>
    450      * interface, that object will be notified in due course. These links-to
    451      * -data bounds are monitored, as a matter of security, by the full
    452      * machinery of the <code>AccessController</code> class.
    453      *
    454      * @param name the name of the link (no null are
    455      *            accepted!)
    456      * @param value data object that shall be bound to
    457      *            name.
    458      * @throws <code>IllegalArgumentException</code> if one or both
    459      *             argument(s) is null.
    460      */
    461     public void putValue(String name, Object value) {
    462         if (name == null || value == null) {
    463             throw new IllegalArgumentException("Parameter is null");
    464         }
    465         Object old = values.put(name, AccessController.getContext(), value);
    466         if (value instanceof SSLSessionBindingListener) {
    467             ((SSLSessionBindingListener) value)
    468                     .valueBound(new SSLSessionBindingEvent(this, name));
    469         }
    470         if (old instanceof SSLSessionBindingListener) {
    471             ((SSLSessionBindingListener) old)
    472                     .valueUnbound(new SSLSessionBindingEvent(this, name));
    473         }
    474     }
    475 
    476     /**
    477      * Removes a link (name) with the specified value object of the SSL
    478      * session's application layer data.
    479      *
    480      * <p>If the value object implements the <code>SSLSessionBindingListener</code>
    481      * interface, the object will receive a <code>valueUnbound</code> notification.
    482      *
    483      * <p>These links-to -data bounds are
    484      * monitored, as a matter of security, by the full machinery of the
    485      * <code>AccessController</code> class.
    486      *
    487      * @param name the name of the link (no null are
    488      *            accepted!)
    489      * @throws <code>IllegalArgumentException</code> if the argument is null.
    490      */
    491     public void removeValue(String name) {
    492         if (name == null) {
    493             throw new IllegalArgumentException("Parameter is null");
    494         }
    495         Object old = values.remove(name, AccessController.getContext());
    496         if (old instanceof SSLSessionBindingListener) {
    497             SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
    498             listener.valueUnbound(new SSLSessionBindingEvent(this, name));
    499         }
    500     }
    501 
    502     @Override protected void finalize() throws Throwable {
    503         try {
    504             NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    505         } finally {
    506             super.finalize();
    507         }
    508     }
    509 }
    510