blob: a35fbac551794c286315ad5163ccfa1e2d5f793f [file] [log] [blame]
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001/*
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
17package org.apache.harmony.xnet.provider.jsse;
18
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070019import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.DataInputStream;
22import java.io.DataOutputStream;
23import java.io.IOException;
24import java.util.Arrays;
25import java.util.Enumeration;
26import java.util.Iterator;
Brian Carlstrom9acacc32010-05-14 11:14:18 -070027import java.util.LinkedHashMap;
28import java.util.Map;
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070029import java.util.NoSuchElementException;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080030import java.util.logging.Level;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080031import javax.net.ssl.SSLSession;
32import javax.net.ssl.SSLSessionContext;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080033import javax.security.cert.CertificateEncodingException;
34import javax.security.cert.CertificateException;
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070035import javax.security.cert.X509Certificate;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080036
37/**
38 * Supports SSL session caches.
39 */
40abstract class AbstractSessionContext implements SSLSessionContext {
41
42 volatile int maximumSize;
43 volatile int timeout;
44
Brian Carlstromf365a1c2010-05-10 13:23:58 -070045 final int sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080046
47 /** Identifies OpenSSL sessions. */
48 static final int OPEN_SSL = 1;
49
Brian Carlstrom9acacc32010-05-14 11:14:18 -070050 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
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080059 /**
60 * Constructs a new session context.
61 *
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080062 * @param maximumSize of cache
63 * @param timeout for cache entries
64 */
Brian Carlstromf365a1c2010-05-10 13:23:58 -070065 AbstractSessionContext(int maximumSize, int timeout) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080066 this.maximumSize = maximumSize;
67 this.timeout = timeout;
68 }
69
70 /**
Brian Carlstrom9acacc32010-05-14 11:14:18 -070071 * Returns the collection of sessions ordered from oldest to newest
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080072 */
Brian Carlstrom9acacc32010-05-14 11:14:18 -070073 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 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080080
81 public final Enumeration getIds() {
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070082 final Iterator<SSLSession> i = sessionIterator();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080083 return new Enumeration<byte[]>() {
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070084 private SSLSession next;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080085 public boolean hasMoreElements() {
Brian Carlstroma7ae90d2010-05-11 11:22:00 -070086 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;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080098 }
99 public byte[] nextElement() {
Brian Carlstroma7ae90d2010-05-11 11:22:00 -0700100 if (hasMoreElements()) {
101 byte[] id = next.getId();
102 next = null;
103 return id;
104 }
105 throw new NoSuchElementException();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800106 }
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 */
Brian Carlstrom9acacc32010-05-14 11:14:18 -0700121 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
Brian Carlstrom12cd1f02010-06-22 23:43:20 -0700157 /**
Brian Carlstrom9acacc32010-05-14 11:14:18 -0700158 * 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);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800162
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 X509Certificate[] certs = session.getPeerCertificateChain();
203 daos.writeInt(certs.length);
204
205 // TODO: Call nativegetpeercertificates()
206 for (X509Certificate cert : certs) {
207 data = cert.getEncoded();
208 daos.writeInt(data.length);
209 daos.write(data);
210 }
Brian Carlstrombcfb3252010-05-02 11:27:52 -0700211 // TODO: local certificates?
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800212
213 return baos.toByteArray();
214 } catch (IOException e) {
215 log(e);
216 return null;
217 } catch (CertificateEncodingException e) {
218 log(e);
219 return null;
220 }
221 }
222
223 /**
224 * Creates a session from the given bytes.
225 *
226 * @return a session or null if the session can't be converted
227 */
228 SSLSession toSession(byte[] data, String host, int port) {
229 ByteArrayInputStream bais = new ByteArrayInputStream(data);
230 DataInputStream dais = new DataInputStream(bais);
231 try {
232 int type = dais.readInt();
233 if (type != OPEN_SSL) {
234 log(new AssertionError("Unexpected type ID: " + type));
235 return null;
236 }
237
238 int length = dais.readInt();
239 byte[] sessionData = new byte[length];
240 dais.readFully(sessionData);
241
242 int count = dais.readInt();
243 X509Certificate[] certs = new X509Certificate[count];
244 for (int i = 0; i < count; i++) {
245 length = dais.readInt();
246 byte[] certData = new byte[length];
247 dais.readFully(certData);
248 certs[i] = X509Certificate.getInstance(certData);
249 }
250
Brian Carlstrombcfb3252010-05-02 11:27:52 -0700251 return new OpenSSLSessionImpl(sessionData, host, port, certs, this);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800252 } catch (IOException e) {
253 log(e);
254 return null;
255 } catch (CertificateException e) {
256 log(e);
257 return null;
258 }
259 }
260
Brian Carlstrom9acacc32010-05-14 11:14:18 -0700261 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 }
Brian Carlstromecaf7592010-03-02 16:55:35 -0800286
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800287 static void log(Throwable t) {
288 java.util.logging.Logger.global.log(Level.WARNING,
289 "Error converting session.", t);
290 }
291
Brian Carlstromecaf7592010-03-02 16:55:35 -0800292 protected void finalize() throws IOException {
293 NativeCrypto.SSL_CTX_free(sslCtxNativePointer);
294 }
295
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800296 /**
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 }
Brian Carlstromecaf7592010-03-02 16:55:35 -0800319}