2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.common.provider;
19 import java.io.ByteArrayOutputStream;
20 import java.io.DataOutputStream;
21 import java.io.IOException;
22 import java.security.GeneralSecurityException;
23 import java.security.KeyException;
24 import java.security.SecureRandom;
25 import java.util.ArrayList;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.zip.GZIPOutputStream;
32 import javax.crypto.Cipher;
33 import javax.crypto.Mac;
34 import javax.crypto.SecretKey;
35 import javax.crypto.spec.IvParameterSpec;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
40 import edu.internet2.middleware.shibboleth.common.Cache;
41 import edu.internet2.middleware.shibboleth.utils.Base32;
44 * <code>Cache</code> implementation that uses browser cookies to store data. Symmetric and HMAC algorithms are used
45 * to encrypt and verify the data. Due to the size limitations of cookie storage, data may interleaved among multiple
48 * @author Walter Hoehn
50 public class CookieCache extends BaseCache implements Cache {
53 private HttpServletResponse response;
54 private List<Cookie> myCurrentCookies = new ArrayList<Cookie>();
55 private Map<String, CacheEntry> dataCache = new HashMap<String, CacheEntry>();
56 private static final int CHUNK_SIZE = 4 * 1024; // in KB, minimal browser requirement
57 private static final int COOKIE_LIMIT = 20; // minimal browser requirement
58 private static final String NAME_PREFIX = "IDP_CACHE:";
59 protected SecretKey secret;
60 private static SecureRandom random = new SecureRandom();
61 private String cipherAlgorithm = "DESede/CBC/PKCS5Padding";
62 private String macAlgorithm = "HmacSHA1";
63 private String storeType = "JCEKS";
65 CookieCache(String name, HttpServletRequest request, HttpServletResponse response) {
67 super(name, Cache.CacheType.CLIENT_SIDE);
68 this.response = response;
69 Cookie[] requestCookies = request.getCookies();
70 for (int i = 0; i < requestCookies.length; i++) {
71 if (requestCookies[i].getName().startsWith(NAME_PREFIX)) {
72 myCurrentCookies.add(requestCookies[i]);
76 // TODO dechunk, decrypt, and pull in dataCache
79 public boolean contains(String key) {
81 CacheEntry entry = dataCache.get(key);
83 if (entry == null) { return false; }
85 // Clean cache if it is expired
86 if (new Date().after(((CacheEntry) entry).expiration)) {
95 private void deleteFromCache(String key) {
97 dataCache.remove(key);
101 public Object retrieve(String key) {
103 CacheEntry entry = dataCache.get(key);
105 if (entry == null) { return null; }
107 // Clean cache if it is expired
108 if (new Date().after(((CacheEntry) entry).expiration)) {
109 deleteFromCache(key);
117 public void store(String key, String value, long duration) {
119 dataCache.put(key, new CacheEntry(value, duration));
124 * Secures, encodes, and writes out (to cookies) cached data.
126 private void flushCache() {
128 // TODO create String representation of all cache data
129 String stringData = null;
133 // Setup a gzipped data stream
134 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
135 GZIPOutputStream compressedStream = new GZIPOutputStream(byteStream);
136 DataOutputStream dataStream = new DataOutputStream(compressedStream);
138 // Write data and HMAC to stream
139 Mac mac = Mac.getInstance(macAlgorithm);
141 dataStream.write(mac.doFinal(stringData.getBytes()));
142 dataStream.writeUTF(stringData);
146 compressedStream.flush();
147 compressedStream.finish();
150 // Setup encryption lib
151 Cipher cipher = Cipher.getInstance(cipherAlgorithm);
152 byte[] iv = new byte[cipher.getBlockSize()];
153 random.nextBytes(iv);
154 IvParameterSpec ivSpec = new IvParameterSpec(iv);
155 cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
157 // Creat byte array of IV and encrypted cache
158 byte[] encryptedData = cipher.doFinal(byteStream.toByteArray());
159 byte[] cacheBytes = new byte[iv.length + encryptedData.length];
161 System.arraycopy(iv, 0, cacheBytes, 0, iv.length);
162 // Write encrypted cache
163 System.arraycopy(encryptedData, 0, cacheBytes, iv.length, encryptedData.length);
166 String encodedData = Base32.encode(cacheBytes);
169 interleaveInCookies(encodedData);
171 } catch (KeyException e) {
173 } catch (GeneralSecurityException e) {
175 } catch (IOException e) {
181 * Writes encoded data across multiple cookies
183 private void interleaveInCookies(String data) {
185 // Convert the String data to a list of cookies
186 List<Cookie> cookiesToResponse = new ArrayList<Cookie>();
187 StringBuffer bufferredData = new StringBuffer(data);
188 while (bufferredData != null && bufferredData.length() > 0) {
189 Cookie cookie = null;
191 if (bufferredData.length() <= getCookieSpace()) {
192 cookie = new Cookie(name, bufferredData.toString());
193 bufferredData = null;
195 cookie = new Cookie(name, bufferredData.substring(0, getCookieSpace() - 1));
196 bufferredData.delete(0, getCookieSpace() - 1);
198 cookiesToResponse.add(cookie);
201 // We have to null out cookies that are no longer needed
202 for (Cookie previousCookie : myCurrentCookies) {
203 if (!cookiesToResponse.contains(previousCookie)) {
204 cookiesToResponse.add(new Cookie(previousCookie.getName(), null));
208 // Write our cookies to the response object
209 for (Cookie cookie : cookiesToResponse) {
210 response.addCookie(cookie);
213 // Update our cached copy of the cookies
214 myCurrentCookies = cookiesToResponse;
218 * Returns the amount of value space available in cookies we create
220 private int getCookieSpace() {
222 // TODO this needs to be better