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