1 /*
2  * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.crypto;
27 
28 import java.io.*;
29 import java.security.AlgorithmParameters;
30 import java.security.Key;
31 import java.security.InvalidKeyException;
32 import java.security.InvalidAlgorithmParameterException;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.NoSuchProviderException;
35 
36 /**
37  * This class enables a programmer to create an object and protect its
38  * confidentiality with a cryptographic algorithm.
39  *
40  * <p> Given any Serializable object, one can create a SealedObject
41  * that encapsulates the original object, in serialized
42  * format (i.e., a "deep copy"), and seals (encrypts) its serialized contents,
43  * using a cryptographic algorithm such as DES, to protect its
44  * confidentiality.  The encrypted content can later be decrypted (with
45  * the corresponding algorithm using the correct decryption key) and
46  * de-serialized, yielding the original object.
47  *
48  * <p> Note that the Cipher object must be fully initialized with the
49  * correct algorithm, key, padding scheme, etc., before being applied
50  * to a SealedObject.
51  *
52  * <p> The original object that was sealed can be recovered in two different
53  * ways: <p>
54  *
55  * <ul>
56  *
57  * <li>by using the {@link #getObject(javax.crypto.Cipher) getObject}
58  * method that takes a <code>Cipher</code> object.
59  *
60  * <p> This method requires a fully initialized <code>Cipher</code> object,
61  * initialized with the
62  * exact same algorithm, key, padding scheme, etc., that were used to seal the
63  * object.
64  *
65  * <p> This approach has the advantage that the party who unseals the
66  * sealed object does not require knowledge of the decryption key. For example,
67  * after one party has initialized the cipher object with the required
68  * decryption key, it could hand over the cipher object to
69  * another party who then unseals the sealed object.
70  *
71  * <p>
72  *
73  * <li>by using one of the
74  * {@link #getObject(java.security.Key) getObject} methods
75  * that take a <code>Key</code> object.
76  *
77  * <p> In this approach, the <code>getObject</code> method creates a cipher
78  * object for the appropriate decryption algorithm and initializes it with the
79  * given decryption key and the algorithm parameters (if any) that were stored
80  * in the sealed object.
81  *
82  * <p> This approach has the advantage that the party who
83  * unseals the object does not need to keep track of the parameters (e.g., an
84  * IV) that were used to seal the object.
85  *
86  * </ul>
87  *
88  * @author Li Gong
89  * @author Jan Luehe
90  * @see Cipher
91  * @since 1.4
92  */
93 
94 public class SealedObject implements Serializable {
95 
96     static final long serialVersionUID = 4482838265551344752L;
97 
98     /**
99      * The serialized object contents in encrypted format.
100      *
101      * @serial
102      */
103     private byte[] encryptedContent = null;
104 
105     /**
106      * The algorithm that was used to seal this object.
107      *
108      * @serial
109      */
110     private String sealAlg = null;
111 
112     /**
113      * The algorithm of the parameters used.
114      *
115      * @serial
116      */
117     private String paramsAlg = null;
118 
119     /**
120      * The cryptographic parameters used by the sealing Cipher,
121      * encoded in the default format.
122      * <p>
123      * That is, <code>cipher.getParameters().getEncoded()</code>.
124      *
125      * @serial
126      */
127     protected byte[] encodedParams = null;
128 
129     /**
130      * Constructs a SealedObject from any Serializable object.
131      *
132      * <p>The given object is serialized, and its serialized contents are
133      * encrypted using the given Cipher, which must be fully initialized.
134      *
135      * <p>Any algorithm parameters that may be used in the encryption
136      * operation are stored inside of the new <code>SealedObject</code>.
137      *
138      * @param object the object to be sealed; can be null.
139      * @param c the cipher used to seal the object.
140      *
141      * @exception NullPointerException if the given cipher is null.
142      * @exception IOException if an error occurs during serialization
143      * @exception IllegalBlockSizeException if the given cipher is a block
144      * cipher, no padding has been requested, and the total input length
145      * (i.e., the length of the serialized object contents) is not a multiple
146      * of the cipher's block size
147      */
SealedObject(Serializable object, Cipher c)148     public SealedObject(Serializable object, Cipher c) throws IOException,
149         IllegalBlockSizeException
150     {
151         /*
152          * Serialize the object
153          */
154 
155         // creating a stream pipe-line, from a to b
156         ByteArrayOutputStream b = new ByteArrayOutputStream();
157         ObjectOutput a = new ObjectOutputStream(b);
158         byte[] content;
159         try {
160             // write and flush the object content to byte array
161             a.writeObject(object);
162             a.flush();
163             content = b.toByteArray();
164         } finally {
165             a.close();
166         }
167 
168         /*
169          * Seal the object
170          */
171         try {
172             this.encryptedContent = c.doFinal(content);
173         }
174         catch (BadPaddingException ex) {
175             // if sealing is encryption only
176             // Should never happen??
177         }
178 
179         // Save the parameters
180         if (c.getParameters() != null) {
181             this.encodedParams = c.getParameters().getEncoded();
182             this.paramsAlg = c.getParameters().getAlgorithm();
183         }
184 
185         // Save the encryption algorithm
186         this.sealAlg = c.getAlgorithm();
187     }
188 
189     /**
190      * Constructs a SealedObject object from the passed-in SealedObject.
191      *
192      * @param so a SealedObject object
193      * @exception NullPointerException if the given sealed object is null.
194      */
SealedObject(SealedObject so)195     protected SealedObject(SealedObject so) {
196         this.encryptedContent = (byte[]) so.encryptedContent.clone();
197         this.sealAlg = so.sealAlg;
198         this.paramsAlg = so.paramsAlg;
199         if (so.encodedParams != null) {
200             this.encodedParams = (byte[]) so.encodedParams.clone();
201         } else {
202             this.encodedParams = null;
203         }
204     }
205 
206     /**
207      * Returns the algorithm that was used to seal this object.
208      *
209      * @return the algorithm that was used to seal this object.
210      */
getAlgorithm()211     public final String getAlgorithm() {
212         return this.sealAlg;
213     }
214 
215     /**
216      * Retrieves the original (encapsulated) object.
217      *
218      * <p>This method creates a cipher for the algorithm that had been used in
219      * the sealing operation.
220      * If the default provider package provides an implementation of that
221      * algorithm, an instance of Cipher containing that implementation is used.
222      * If the algorithm is not available in the default package, other
223      * packages are searched.
224      * The Cipher object is initialized for decryption, using the given
225      * <code>key</code> and the parameters (if any) that had been used in the
226      * sealing operation.
227      *
228      * <p>The encapsulated object is unsealed and de-serialized, before it is
229      * returned.
230      *
231      * @param key the key used to unseal the object.
232      *
233      * @return the original object.
234      *
235      * @exception IOException if an error occurs during de-serialiazation.
236      * @exception ClassNotFoundException if an error occurs during
237      * de-serialiazation.
238      * @exception NoSuchAlgorithmException if the algorithm to unseal the
239      * object is not available.
240      * @exception InvalidKeyException if the given key cannot be used to unseal
241      * the object (e.g., it has the wrong algorithm).
242      * @exception NullPointerException if <code>key</code> is null.
243      */
getObject(Key key)244     public final Object getObject(Key key)
245         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
246             InvalidKeyException
247     {
248         if (key == null) {
249             throw new NullPointerException("key is null");
250         }
251 
252         try {
253             return unseal(key, null);
254         } catch (NoSuchProviderException nspe) {
255             // we've already caught NoSuchProviderException's and converted
256             // them into NoSuchAlgorithmException's with details about
257             // the failing algorithm
258             throw new NoSuchAlgorithmException("algorithm not found");
259         } catch (IllegalBlockSizeException ibse) {
260             throw new InvalidKeyException(ibse.getMessage());
261         } catch (BadPaddingException bpe) {
262             throw new InvalidKeyException(bpe.getMessage());
263         }
264     }
265 
266     /**
267      * Retrieves the original (encapsulated) object.
268      *
269      * <p>The encapsulated object is unsealed (using the given Cipher,
270      * assuming that the Cipher is already properly initialized) and
271      * de-serialized, before it is returned.
272      *
273      * @param c the cipher used to unseal the object
274      *
275      * @return the original object.
276      *
277      * @exception NullPointerException if the given cipher is null.
278      * @exception IOException if an error occurs during de-serialiazation
279      * @exception ClassNotFoundException if an error occurs during
280      * de-serialiazation
281      * @exception IllegalBlockSizeException if the given cipher is a block
282      * cipher, no padding has been requested, and the total input length is
283      * not a multiple of the cipher's block size
284      * @exception BadPaddingException if the given cipher has been
285      * initialized for decryption, and padding has been specified, but
286      * the input data does not have proper expected padding bytes
287      */
getObject(Cipher c)288     public final Object getObject(Cipher c)
289         throws IOException, ClassNotFoundException, IllegalBlockSizeException,
290             BadPaddingException
291     {
292         /*
293          * Unseal the object
294          */
295         byte[] content = c.doFinal(this.encryptedContent);
296 
297         /*
298          * De-serialize it
299          */
300         // creating a stream pipe-line, from b to a
301         ByteArrayInputStream b = new ByteArrayInputStream(content);
302         ObjectInput a = new extObjectInputStream(b);
303         try {
304             Object obj = a.readObject();
305             return obj;
306         } finally {
307             a.close();
308         }
309     }
310 
311     /**
312      * Retrieves the original (encapsulated) object.
313      *
314      * <p>This method creates a cipher for the algorithm that had been used in
315      * the sealing operation, using an implementation of that algorithm from
316      * the given <code>provider</code>.
317      * The Cipher object is initialized for decryption, using the given
318      * <code>key</code> and the parameters (if any) that had been used in the
319      * sealing operation.
320      *
321      * <p>The encapsulated object is unsealed and de-serialized, before it is
322      * returned.
323      *
324      * @param key the key used to unseal the object.
325      * @param provider the name of the provider of the algorithm to unseal
326      * the object.
327      *
328      * @return the original object.
329      *
330      * @exception IllegalArgumentException if the given provider is null
331      * or empty.
332      * @exception IOException if an error occurs during de-serialiazation.
333      * @exception ClassNotFoundException if an error occurs during
334      * de-serialiazation.
335      * @exception NoSuchAlgorithmException if the algorithm to unseal the
336      * object is not available.
337      * @exception NoSuchProviderException if the given provider is not
338      * configured.
339      * @exception InvalidKeyException if the given key cannot be used to unseal
340      * the object (e.g., it has the wrong algorithm).
341      * @exception NullPointerException if <code>key</code> is null.
342      */
getObject(Key key, String provider)343     public final Object getObject(Key key, String provider)
344         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
345             NoSuchProviderException, InvalidKeyException
346     {
347         if (key == null) {
348             throw new NullPointerException("key is null");
349         }
350         if (provider == null || provider.length() == 0) {
351             throw new IllegalArgumentException("missing provider");
352         }
353 
354         try {
355             return unseal(key, provider);
356         } catch (IllegalBlockSizeException ibse) {
357             throw new InvalidKeyException(ibse.getMessage());
358         } catch (BadPaddingException bpe) {
359             throw new InvalidKeyException(bpe.getMessage());
360         }
361     }
362 
363 
unseal(Key key, String provider)364     private Object unseal(Key key, String provider)
365         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
366             NoSuchProviderException, InvalidKeyException,
367             IllegalBlockSizeException, BadPaddingException
368     {
369         /*
370          * Create the parameter object.
371          */
372         AlgorithmParameters params = null;
373         if (this.encodedParams != null) {
374             try {
375                 if (provider != null)
376                     params = AlgorithmParameters.getInstance(this.paramsAlg,
377                                                              provider);
378                 else
379                     params = AlgorithmParameters.getInstance(this.paramsAlg);
380 
381             } catch (NoSuchProviderException nspe) {
382                 if (provider == null) {
383                     throw new NoSuchAlgorithmException(this.paramsAlg
384                                                        + " not found");
385                 } else {
386                     throw new NoSuchProviderException(nspe.getMessage());
387                 }
388             }
389             params.init(this.encodedParams);
390         }
391 
392         /*
393          * Create and initialize the cipher.
394          */
395         Cipher c;
396         try {
397             if (provider != null)
398                 c = Cipher.getInstance(this.sealAlg, provider);
399             else
400                 c = Cipher.getInstance(this.sealAlg);
401         } catch (NoSuchPaddingException nspe) {
402             throw new NoSuchAlgorithmException("Padding that was used in "
403                                                + "sealing operation not "
404                                                + "available");
405         } catch (NoSuchProviderException nspe) {
406             if (provider == null) {
407                 throw new NoSuchAlgorithmException(this.sealAlg+" not found");
408             } else {
409                 throw new NoSuchProviderException(nspe.getMessage());
410             }
411         }
412 
413         try {
414             if (params != null)
415                 c.init(Cipher.DECRYPT_MODE, key, params);
416             else
417                 c.init(Cipher.DECRYPT_MODE, key);
418         } catch (InvalidAlgorithmParameterException iape) {
419             // this should never happen, because we use the exact same
420             // parameters that were used in the sealing operation
421             throw new RuntimeException(iape.getMessage());
422         }
423 
424         /*
425          * Unseal the object
426          */
427         byte[] content = c.doFinal(this.encryptedContent);
428 
429         /*
430          * De-serialize it
431          */
432         // creating a stream pipe-line, from b to a
433         ByteArrayInputStream b = new ByteArrayInputStream(content);
434         ObjectInput a = new extObjectInputStream(b);
435         try {
436             Object obj = a.readObject();
437             return obj;
438         } finally {
439             a.close();
440         }
441     }
442 
443     /**
444      * Restores the state of the SealedObject from a stream.
445      * @param s the object input stream.
446      * @exception NullPointerException if s is null.
447      */
readObject(java.io.ObjectInputStream s)448     private void readObject(java.io.ObjectInputStream s)
449         throws java.io.IOException, ClassNotFoundException
450     {
451         s.defaultReadObject();
452         if (encryptedContent != null)
453             encryptedContent = (byte[])encryptedContent.clone();
454         if (encodedParams != null)
455             encodedParams = (byte[])encodedParams.clone();
456     }
457 }
458 
459 final class extObjectInputStream extends ObjectInputStream {
460 
461     private static ClassLoader systemClassLoader = null;
462 
extObjectInputStream(InputStream in)463     extObjectInputStream(InputStream in)
464         throws IOException, StreamCorruptedException {
465         super(in);
466     }
467 
resolveClass(ObjectStreamClass v)468     protected Class resolveClass(ObjectStreamClass v)
469         throws IOException, ClassNotFoundException
470     {
471 
472         try {
473             /*
474              * Calling the super.resolveClass() first
475              * will let us pick up bug fixes in the super
476              * class (e.g., 4171142).
477              */
478             return super.resolveClass(v);
479         } catch (ClassNotFoundException cnfe) {
480             /*
481              * This is a workaround for bug 4224921.
482              */
483             ClassLoader loader = Thread.currentThread().getContextClassLoader();
484             if (loader == null) {
485                 if (systemClassLoader == null) {
486                     systemClassLoader = ClassLoader.getSystemClassLoader();
487                 }
488                 loader = systemClassLoader;
489                 if (loader == null) {
490                     throw new ClassNotFoundException(v.getName());
491                 }
492             }
493 
494             return Class.forName(v.getName(), false, loader);
495         }
496     }
497 }
498