Home | History | Annotate | Download | only in jsse
      1 /*
      2  * Copyright (C) 2009 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.ByteArrayInputStream;
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.DataInputStream;
     22 import java.io.DataOutputStream;
     23 import java.io.IOException;
     24 import java.security.cert.Certificate;
     25 import java.security.cert.CertificateEncodingException;
     26 import java.util.Arrays;
     27 import java.util.Enumeration;
     28 import java.util.Iterator;
     29 import java.util.LinkedHashMap;
     30 import java.util.Map;
     31 import java.util.NoSuchElementException;
     32 import java.util.logging.Level;
     33 import javax.net.ssl.SSLSession;
     34 import javax.net.ssl.SSLSessionContext;
     35 import org.apache.harmony.security.provider.cert.X509CertImpl;
     36 
     37 /**
     38  * Supports SSL session caches.
     39  */
     40 abstract class AbstractSessionContext implements SSLSessionContext {
     41 
     42     volatile int maximumSize;
     43     volatile int timeout;
     44 
     45     final int sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
     46 
     47     /** Identifies OpenSSL sessions. */
     48     static final int OPEN_SSL = 1;
     49 
     50     private final Map<ByteArray, SSLSession> sessions
     51             = new LinkedHashMap<ByteArray, SSLSession>() {
     52         @Override
     53         protected boolean removeEldestEntry(
     54                 Map.Entry<ByteArray, SSLSession> eldest) {
     55             return maximumSize > 0 && size() > maximumSize;
     56         }
     57     };
     58 
     59     /**
     60      * Constructs a new session context.
     61      *
     62      * @param maximumSize of cache
     63      * @param timeout for cache entries
     64      */
     65     AbstractSessionContext(int maximumSize, int timeout) {
     66         this.maximumSize = maximumSize;
     67         this.timeout = timeout;
     68     }
     69 
     70     /**
     71      * Returns the collection of sessions ordered from oldest to newest
     72      */
     73     private Iterator<SSLSession> sessionIterator() {
     74         synchronized (sessions) {
     75             SSLSession[] array = sessions.values().toArray(
     76                     new SSLSession[sessions.size()]);
     77             return Arrays.asList(array).iterator();
     78         }
     79     }
     80 
     81     public final Enumeration getIds() {
     82         final Iterator<SSLSession> i = sessionIterator();
     83         return new Enumeration<byte[]>() {
     84             private SSLSession next;
     85             public boolean hasMoreElements() {
     86                 if (next != null) {
     87                     return true;
     88                 }
     89                 while (i.hasNext()) {
     90                     SSLSession session = i.next();
     91                     if (session.isValid()) {
     92                         next = session;
     93                         return true;
     94                     }
     95                 }
     96                 next = null;
     97                 return false;
     98             }
     99             public byte[] nextElement() {
    100                 if (hasMoreElements()) {
    101                     byte[] id = next.getId();
    102                     next = null;
    103                     return id;
    104                 }
    105                 throw new NoSuchElementException();
    106             }
    107         };
    108     }
    109 
    110     public final int getSessionCacheSize() {
    111         return maximumSize;
    112     }
    113 
    114     public final int getSessionTimeout() {
    115         return timeout;
    116     }
    117 
    118     /**
    119      * Makes sure cache size is < maximumSize.
    120      */
    121     protected void trimToSize() {
    122         synchronized (sessions) {
    123             int size = sessions.size();
    124             if (size > maximumSize) {
    125                 int removals = size - maximumSize;
    126                 Iterator<SSLSession> i = sessions.values().iterator();
    127                 do {
    128                     SSLSession session = i.next();
    129                     i.remove();
    130                     sessionRemoved(session);
    131                 } while (--removals > 0);
    132             }
    133         }
    134     }
    135 
    136     public void setSessionTimeout(int seconds)
    137             throws IllegalArgumentException {
    138         if (seconds < 0) {
    139             throw new IllegalArgumentException("seconds < 0");
    140         }
    141         timeout = seconds;
    142 
    143         synchronized (sessions) {
    144             Iterator<SSLSession> i = sessions.values().iterator();
    145             while (i.hasNext()) {
    146                 SSLSession session = i.next();
    147                 // SSLSession's know their context and consult the
    148                 // timeout as part of their validity condition.
    149                 if (!session.isValid()) {
    150                     i.remove();
    151                     sessionRemoved(session);
    152                 }
    153             }
    154         }
    155     }
    156 
    157     /**
    158      * Called when a session is removed. Used by ClientSessionContext
    159      * to update its host-and-port based cache.
    160      */
    161     abstract protected void sessionRemoved(SSLSession session);
    162 
    163     public final void setSessionCacheSize(int size)
    164             throws IllegalArgumentException {
    165         if (size < 0) {
    166             throw new IllegalArgumentException("size < 0");
    167         }
    168 
    169         int oldMaximum = maximumSize;
    170         maximumSize = size;
    171 
    172         // Trim cache to size if necessary.
    173         if (size < oldMaximum) {
    174             trimToSize();
    175         }
    176     }
    177 
    178     /**
    179      * Converts the given session to bytes.
    180      *
    181      * @return session data as bytes or null if the session can't be converted
    182      */
    183     byte[] toBytes(SSLSession session) {
    184         // TODO: Support SSLSessionImpl, too.
    185         if (!(session instanceof OpenSSLSessionImpl)) {
    186             return null;
    187         }
    188 
    189         OpenSSLSessionImpl sslSession = (OpenSSLSessionImpl) session;
    190         try {
    191             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    192             DataOutputStream daos = new DataOutputStream(baos);
    193 
    194             daos.writeInt(OPEN_SSL); // session type ID
    195 
    196             // Session data.
    197             byte[] data = sslSession.getEncoded();
    198             daos.writeInt(data.length);
    199             daos.write(data);
    200 
    201             // Certificates.
    202             Certificate[] certs = session.getPeerCertificates();
    203             daos.writeInt(certs.length);
    204 
    205             for (Certificate cert : certs) {
    206                 data = cert.getEncoded();
    207                 daos.writeInt(data.length);
    208                 daos.write(data);
    209             }
    210             // TODO: local certificates?
    211 
    212             return baos.toByteArray();
    213         } catch (IOException e) {
    214             log(e);
    215             return null;
    216         } catch (CertificateEncodingException e) {
    217             log(e);
    218             return null;
    219         }
    220     }
    221 
    222     /**
    223      * Creates a session from the given bytes.
    224      *
    225      * @return a session or null if the session can't be converted
    226      */
    227     SSLSession toSession(byte[] data, String host, int port) {
    228         ByteArrayInputStream bais = new ByteArrayInputStream(data);
    229         DataInputStream dais = new DataInputStream(bais);
    230         try {
    231             int type = dais.readInt();
    232             if (type != OPEN_SSL) {
    233                 log(new AssertionError("Unexpected type ID: " + type));
    234                 return null;
    235             }
    236 
    237             int length = dais.readInt();
    238             byte[] sessionData = new byte[length];
    239             dais.readFully(sessionData);
    240 
    241             int count = dais.readInt();
    242             X509CertImpl[] certs = new X509CertImpl[count];
    243             for (int i = 0; i < count; i++) {
    244                 length = dais.readInt();
    245                 byte[] certData = new byte[length];
    246                 dais.readFully(certData);
    247                 certs[i] = new X509CertImpl(certData);
    248             }
    249 
    250             return new OpenSSLSessionImpl(sessionData, host, port, certs, this);
    251         } catch (IOException e) {
    252             log(e);
    253             return null;
    254         }
    255     }
    256 
    257     public SSLSession getSession(byte[] sessionId) {
    258         if (sessionId == null) {
    259             throw new NullPointerException("sessionId == null");
    260         }
    261         ByteArray key = new ByteArray(sessionId);
    262         SSLSession session;
    263         synchronized (sessions) {
    264             session = sessions.get(key);
    265         }
    266         if (session != null && session.isValid()) {
    267             return session;
    268         }
    269         return null;
    270     }
    271 
    272     void putSession(SSLSession session) {
    273         byte[] id = session.getId();
    274         if (id.length == 0) {
    275             return;
    276         }
    277         ByteArray key = new ByteArray(id);
    278         synchronized (sessions) {
    279             sessions.put(key, session);
    280         }
    281     }
    282 
    283     static void log(Throwable t) {
    284         java.util.logging.Logger.global.log(Level.WARNING,
    285                 "Error converting session.", t);
    286     }
    287 
    288     @Override protected void finalize() throws Throwable {
    289         try {
    290             NativeCrypto.SSL_CTX_free(sslCtxNativePointer);
    291         } finally {
    292             super.finalize();
    293         }
    294     }
    295 
    296     /**
    297      * Byte array wrapper. Implements equals() and hashCode().
    298      */
    299     static class ByteArray {
    300 
    301         private final byte[] bytes;
    302 
    303         ByteArray(byte[] bytes) {
    304             this.bytes = bytes;
    305         }
    306 
    307         @Override
    308         public int hashCode() {
    309             return Arrays.hashCode(bytes);
    310         }
    311 
    312         @Override
    313         @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    314         public boolean equals(Object o) {
    315             ByteArray other = (ByteArray) o;
    316             return Arrays.equals(bytes, other.bytes);
    317         }
    318     }
    319 }
    320