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         }