001 package com.croftsoft.core.util.cache.secure; 002 003 import java.io.*; 004 import java.security.*; 005 import java.util.*; 006 007 import com.croftsoft.core.util.SoftHashMap; 008 import com.croftsoft.core.util.cache.Cache; 009 import com.croftsoft.core.util.cache.ContentAccessor; 010 import com.croftsoft.core.util.cache.WeakCache; 011 import com.croftsoft.core.util.id.Id; 012 013 /********************************************************************* 014 * Cache implementation that securely stores and retrieves content 015 * using digests. 016 * 017 * <P> 018 * 019 * Applications where a SecureCache may be useful include when 020 * 021 * <UL> 022 * 023 * <LI> it is desired to be able to retrieve content from the cache 024 * even though originally copied and stored from an unknown and 025 * potentially untrusted source, so long as the content is 026 * bit-for-bit identical to what is currently required; 027 * 028 * <P> 029 * 030 * <LI> multiple entities want to share the same cache without the need 031 * to worry that the activities of another, potentially untrusted, 032 * entity might modify the stored content to be something other 033 * than what is expected; 034 * 035 * <P> 036 * 037 * <LI> the cache is stored on an insecure medium which may 038 * be accessed directly and corrupted by another entity; and 039 * 040 * <P> 041 * 042 * <LI> it is desired to detect changes to content stored elsewhere 043 * by comparing the digests and then, if necessary, updating the 044 * copy in the cache to match. 045 * 046 * </UL> 047 * 048 * <P> 049 * 050 * The security is based upon the use of cryptographically secure 051 * content digests which have the property that it is computationally 052 * infeasible to find two non-identical instances of content that 053 * generate digests with the same value. 054 * 055 * <P> 056 * 057 * The SecureCache wraps around a delegate Cache, which itself may 058 * or may not be secure. Multiple digest algorithms may be 059 * simultaneously used by "chaining" SecureCache objects. 060 * 061 * <P> 062 * 063 * Note further that an insecure implementation of the Cache interface 064 * which validates content using the more traditional mechanisms, e.g., 065 * comparing values of content length, freshness date, version 066 * number, semi-unique identifier, etc., may be secured by using a 067 * SecureCache as a delegate. For example, an insecure Cache that 068 * validates content using URL or path/filename, content length, 069 * and/or last modified date may map an Id object storing those values 070 * to a SecureId object to be passed to a delegate SecureCache. 071 * 072 * <P> 073 * 074 * The Serializable interface is implemented to support persistence. 075 * To use this feature, the delegateCache and delegateIdMap must also 076 * be Serializable. 077 * 078 * <B> 079 * Reference 080 * </B> 081 * 082 * <P> 083 & 084 * Sun Microsystems, "Java Cryptography Architecture API Specification 085 * & Reference", 086 * <A HREF="http://www.javasoft.com/products/jdk/1.2/docs/guide/security/CryptoSpec.html#MessageDigest"> 087 * "Message Digest"</A>, 1998-10-30. 088 * 089 * <P> 090 * 091 * @see 092 * java.security.MessageDigest 093 * @see 094 * SecureId 095 * @see 096 * com.orbs.open.a.mpl.util.id.Id 097 * @see 098 * com.orbs.open.a.mpl.util.cache.Cache 099 * @see 100 * com.orbs.open.a.mpl.util.cache.ContentAccessor 101 * @see 102 * com.orbs.open.a.mpl.util.cache.WeakCache 103 * @see 104 * com.orbs.open.a.mpl.util.SoftHashMap 105 * 106 * @version 107 * 1999-04-20 108 * @author 109 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 110 *********************************************************************/ 111 112 public final class SecureCache implements Cache, Serializable 113 ////////////////////////////////////////////////////////////////////// 114 ////////////////////////////////////////////////////////////////////// 115 { 116 117 private final Cache delegateCache; 118 private final Map delegateIdMap; 119 private final String algorithm; 120 private final boolean doVerification; 121 122 ////////////////////////////////////////////////////////////////////// 123 // Constructor method 124 ////////////////////////////////////////////////////////////////////// 125 126 /********************************************************************* 127 * 128 * Creates a new SecureCache that stores content to a delegate Cache. 129 * 130 * @param delegateCache 131 * 132 * Where the content is actually relayed to and from. This may be 133 * an insecure Cache or another chained SecureCache. 134 * 135 * @param delegateIdMap 136 * 137 * A Map of delegateCache Id objects keyed by SecureId objects 138 * generated by this SecureCache. 139 * 140 * @param algorithm 141 * 142 * The secure message digest algorithm to use. 143 * 144 * @param doVerification 145 * 146 * If true, the content digest will be recalculated upon retrieval 147 * to confirm that it has not been altered while in the 148 * delegateCache. This is especially recommended if the 149 * delegateCache is stored on an insecure medium such as a disk 150 * drive. 151 * 152 * <P> 153 * 154 * If false, it is up to the caller to verify that the digest of the 155 * returned content is the same. If the delegateCache medium is 156 * reasonably secure, such as a shared memory area with 157 * exclusive access only through this SecureCache, the caller may 158 * be reasonably secure without needing to take the extra 159 * verification step. 160 * 161 * <P> 162 * 163 * Note that the doVerification parameter only applies to retrieval; 164 * the digest is always calculated from the content during storage. 165 * 166 *********************************************************************/ 167 public SecureCache ( 168 Cache delegateCache, 169 Map delegateIdMap, 170 String algorithm, 171 boolean doVerification ) throws NoSuchAlgorithmException 172 ////////////////////////////////////////////////////////////////////// 173 { 174 this.delegateCache = delegateCache; 175 this.delegateIdMap = delegateIdMap; 176 this.algorithm = algorithm; 177 this.doVerification = doVerification; 178 179 // Test for NoSuchAlgorithmException 180 MessageDigest.getInstance ( algorithm ); 181 } 182 183 /********************************************************************* 184 * 185 * This example zero argument constructor caches the content in memory, 186 * dumps the content when memory runs low, identifies the content 187 * using the NIST Secure Hash Algorithm (SHA), and verifies the content 188 * digest upon retrieval. 189 * 190 * <PRE> 191 * 192 * this ( new WeakCache ( ), new SoftHashMap ( ), "SHA", true ); 193 * 194 * </PRE> 195 * 196 *********************************************************************/ 197 public SecureCache ( ) throws NoSuchAlgorithmException 198 ////////////////////////////////////////////////////////////////////// 199 { 200 this ( new WeakCache ( ), new SoftHashMap ( ), "SHA", true ); 201 } 202 203 ////////////////////////////////////////////////////////////////////// 204 ////////////////////////////////////////////////////////////////////// 205 206 /********************************************************************* 207 * 208 * "Validates" the content by 209 * 210 * <OL> 211 * 212 * <LI> confirming that identical content already exists in the cache; 213 * or, if otherwise necessary, 214 * 215 * <LI> storing a new copy of the content in the cache. 216 * 217 * </OL> 218 * 219 * @param secureId 220 * 221 * The content identifier passed to isAvailable() to determine if 222 * the content is already valid. The parameter may be any SecureId 223 * with potentially matching algorithm and digest values. 224 * 225 * @param contentAccessor 226 * 227 * An object capable of making content accessible via an InputStream. 228 * For example, a ContentAccessor might retrieve content from a 229 * website via a URL, a database or file storage, a remote object 230 * such as another cache, or even dynamically generate the content 231 * upon demand. As yet another possibility, a ContentAccessor object 232 * may potentially attempt to access the content from several 233 * different sources sequentially until it is successful. 234 * 235 * @return 236 * 237 * Returns a SecureId object for the validated content which may be 238 * used later for retrieval. 239 * 240 * <P> 241 * 242 * If valid content was already available in the cache, the returned 243 * SecureId object will be the secureId parameter. 244 * 245 * <P> 246 * 247 * If valid content was not already available and the content could 248 * not be accessed and stored via the contentAccessor, the returned 249 * value will be null. 250 * 251 * <P> 252 * 253 * If valid content was not already available and the content could 254 * be accessed and stored via the contentAccessor, the returned 255 * value will be a new SecureId object with a digest that may or may 256 * not match that of the secureId object parameter, depending on 257 * the actual content available via the contentAccessor. 258 * 259 *********************************************************************/ 260 public Id validate ( Id secureId, ContentAccessor contentAccessor ) 261 throws IOException 262 ////////////////////////////////////////////////////////////////////// 263 { 264 if ( isAvailable ( secureId ) ) return secureId; 265 266 InputStream inputStream = contentAccessor.getInputStream ( ); 267 268 if ( inputStream == null ) return null; 269 270 return store ( inputStream ); 271 } 272 273 /********************************************************************* 274 * 275 * Stores the content to the delegateCache and returns a SecureId 276 * object which may be used to retrieve it. 277 * 278 * @param inputStream 279 * 280 * Any finite ordered sequence of bits. The inputStream will be 281 * read until completion by the delegateCache before return. 282 * 283 * @return 284 * 285 * Returns a SecureId object with a digest calculated from the 286 * content inputStream. 287 * 288 *********************************************************************/ 289 public Id store ( InputStream inputStream ) throws IOException 290 ////////////////////////////////////////////////////////////////////// 291 { 292 if ( inputStream == null ) 293 { 294 throw new IllegalArgumentException ( "null inputStream" ); 295 } 296 297 MessageDigest messageDigest = null; 298 299 try 300 { 301 messageDigest = MessageDigest.getInstance ( algorithm ); 302 } 303 catch ( NoSuchAlgorithmException ex ) 304 { 305 return null; 306 } 307 308 DigestInputStream digestInputStream 309 = new DigestInputStream ( inputStream, messageDigest ); 310 311 Id insecureId = delegateCache.store ( digestInputStream ); 312 313 SecureId secureId 314 = new SecureId ( algorithm, messageDigest.digest ( ) ); 315 316 delegateIdMap.put ( secureId, insecureId ); 317 318 return secureId; 319 } 320 321 /********************************************************************* 322 * 323 * Retrieves content with a matching content digest from the 324 * delegateCache. 325 * 326 * <P> 327 * 328 * If the SecureCache instance variable doVerification is set to true, 329 * the digest will be recalculated via a SecureInputStream while the 330 * content is being read from the delegateCache. When the last byte is 331 * read, the newly calculated digest will be compared to that of the 332 * SecureId parameter object. If the digests differ, an IOException 333 * will be thrown to prevent use of the corrupted content. 334 * 335 * @param secureId 336 * 337 * Any SecureId object with a potentially matching content digest. 338 * 339 * @return 340 * 341 * Returns null if the content was not or is no longer available. 342 * 343 *********************************************************************/ 344 public InputStream retrieve ( Id secureId ) throws IOException 345 ////////////////////////////////////////////////////////////////////// 346 { 347 if ( secureId == null ) 348 { 349 throw new IllegalArgumentException ( "null secureId" ); 350 } 351 352 Id insecureId = ( Id ) delegateIdMap.get ( secureId ); 353 354 if ( insecureId == null ) return null; 355 356 InputStream inputStream = delegateCache.retrieve ( insecureId ); 357 358 if ( inputStream == null ) return null; 359 360 if ( doVerification ) 361 { 362 try 363 { 364 return new SecureInputStream ( inputStream, algorithm, 365 ( ( SecureId ) secureId ).getDigest ( ) ); 366 } 367 catch ( NoSuchAlgorithmException ex ) 368 { 369 return null; 370 } 371 } 372 else 373 { 374 return inputStream; 375 } 376 } 377 378 /********************************************************************* 379 * 380 * Determines if the content with a matching digest is available in 381 * the delegateCache. 382 * 383 * @return 384 * 385 * Returns false if the content was not or is no longer available. 386 * 387 *********************************************************************/ 388 public boolean isAvailable ( Id secureId ) 389 ////////////////////////////////////////////////////////////////////// 390 { 391 if ( secureId == null ) return false; 392 393 Id insecureId = ( Id ) delegateIdMap.get ( secureId ); 394 395 if ( insecureId == null ) return false; 396 397 return delegateCache.isAvailable ( insecureId ); 398 } 399 400 ////////////////////////////////////////////////////////////////////// 401 ////////////////////////////////////////////////////////////////////// 402 }