1 /*
2  * Copyright (c) 1997, 2013, 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:
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  * <li>by using one of the
72  * {@link #getObject(java.security.Key) getObject} methods
73  * that take a <code>Key</code> object.
74  *
75  * <p> In this approach, the <code>getObject</code> method creates a cipher
76  * object for the appropriate decryption algorithm and initializes it with the
77  * given decryption key and the algorithm parameters (if any) that were stored
78  * in the sealed object.
79  *
80  * <p> This approach has the advantage that the party who
81  * unseals the object does not need to keep track of the parameters (e.g., an
82  * IV) that were used to seal the object.
83  *
84  * </ul>
85  *
86  * @author Li Gong
87  * @author Jan Luehe
88  * @see Cipher
89  * @since 1.4
90  */
91 
92 public class SealedObject implements Serializable {
93 
94     static final long serialVersionUID = 4482838265551344752L;
95 
96     /**
97      * The serialized object contents in encrypted format.
98      *
99      * @serial
100      */
101     private byte[] encryptedContent = null;
102 
103     /**
104      * The algorithm that was used to seal this object.
105      *
106      * @serial
107      */
108     private String sealAlg = null;
109 
110     /**
111      * The algorithm of the parameters used.
112      *
113      * @serial
114      */
115     private String paramsAlg = null;
116 
117     /**
118      * The cryptographic parameters used by the sealing Cipher,
119      * encoded in the default format.
120      * <p>
121      * That is, <code>cipher.getParameters().getEncoded()</code>.
122      *
123      * @serial
124      */
125     protected byte[] encodedParams = null;
126 
127     /**
128      * Constructs a SealedObject from any Serializable object.
129      *
130      * <p>The given object is serialized, and its serialized contents are
131      * encrypted using the given Cipher, which must be fully initialized.
132      *
133      * <p>Any algorithm parameters that may be used in the encryption
134      * operation are stored inside of the new <code>SealedObject</code>.
135      *
136      * @param object the object to be sealed; can be null.
137      * @param c the cipher used to seal the object.
138      *
139      * @exception NullPointerException if the given cipher is null.
140      * @exception IOException if an error occurs during serialization
141      * @exception IllegalBlockSizeException if the given cipher is a block
142      * cipher, no padding has been requested, and the total input length
143      * (i.e., the length of the serialized object contents) is not a multiple
144      * of the cipher's block size
145      */
SealedObject(Serializable object, Cipher c)146     public SealedObject(Serializable object, Cipher c) throws IOException,
147         IllegalBlockSizeException
148     {
149         /*
150          * Serialize the object
151          */
152 
153         // creating a stream pipe-line, from a to b
154         ByteArrayOutputStream b = new ByteArrayOutputStream();
155         ObjectOutput a = new ObjectOutputStream(b);
156         byte[] content;
157         try {
158             // write and flush the object content to byte array
159             a.writeObject(object);
160             a.flush();
161             content = b.toByteArray();
162         } finally {
163             a.close();
164         }
165 
166         /*
167          * Seal the object
168          */
169         try {
170             this.encryptedContent = c.doFinal(content);
171         }
172         catch (BadPaddingException ex) {
173             // if sealing is encryption only
174             // Should never happen??
175         }
176 
177         // Save the parameters
178         if (c.getParameters() != null) {
179             this.encodedParams = c.getParameters().getEncoded();
180             this.paramsAlg = c.getParameters().getAlgorithm();
181         }
182 
183         // Save the encryption algorithm
184         this.sealAlg = c.getAlgorithm();
185     }
186 
187     /**
188      * Constructs a SealedObject object from the passed-in SealedObject.
189      *
190      * @param so a SealedObject object
191      * @exception NullPointerException if the given sealed object is null.
192      */
SealedObject(SealedObject so)193     protected SealedObject(SealedObject so) {
194         this.encryptedContent = so.encryptedContent.clone();
195         this.sealAlg = so.sealAlg;
196         this.paramsAlg = so.paramsAlg;
197         if (so.encodedParams != null) {
198             this.encodedParams = so.encodedParams.clone();
199         } else {
200             this.encodedParams = null;
201         }
202     }
203 
204     /**
205      * Returns the algorithm that was used to seal this object.
206      *
207      * @return the algorithm that was used to seal this object.
208      */
getAlgorithm()209     public final String getAlgorithm() {
210         return this.sealAlg;
211     }
212 
213     /**
214      * Retrieves the original (encapsulated) object.
215      *
216      * <p>This method creates a cipher for the algorithm that had been used in
217      * the sealing operation.
218      * If the default provider package provides an implementation of that
219      * algorithm, an instance of Cipher containing that implementation is used.
220      * If the algorithm is not available in the default package, other
221      * packages are searched.
222      * The Cipher object is initialized for decryption, using the given
223      * <code>key</code> and the parameters (if any) that had been used in the
224      * sealing operation.
225      *
226      * <p>The encapsulated object is unsealed and de-serialized, before it is
227      * returned.
228      *
229      * @param key the key used to unseal the object.
230      *
231      * @return the original object.
232      *
233      * @exception IOException if an error occurs during de-serialiazation.
234      * @exception ClassNotFoundException if an error occurs during
235      * de-serialiazation.
236      * @exception NoSuchAlgorithmException if the algorithm to unseal the
237      * object is not available.
238      * @exception InvalidKeyException if the given key cannot be used to unseal
239      * the object (e.g., it has the wrong algorithm).
240      * @exception NullPointerException if <code>key</code> is null.
241      */
getObject(Key key)242     public final Object getObject(Key key)
243         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
244             InvalidKeyException
245     {
246         if (key == null) {
247             throw new NullPointerException("key is null");
248         }
249 
250         try {
251             return unseal(key, null);
252         } catch (NoSuchProviderException nspe) {
253             // we've already caught NoSuchProviderException's and converted
254             // them into NoSuchAlgorithmException's with details about
255             // the failing algorithm
256             throw new NoSuchAlgorithmException("algorithm not found");
257         } catch (IllegalBlockSizeException ibse) {
258             throw new InvalidKeyException(ibse.getMessage());
259         } catch (BadPaddingException bpe) {
260             throw new InvalidKeyException(bpe.getMessage());
261         }
262     }
263 
264     /**
265      * Retrieves the original (encapsulated) object.
266      *
267      * <p>The encapsulated object is unsealed (using the given Cipher,
268      * assuming that the Cipher is already properly initialized) and
269      * de-serialized, before it is returned.
270      *
271      * @param c the cipher used to unseal the object
272      *
273      * @return the original object.
274      *
275      * @exception NullPointerException if the given cipher is null.
276      * @exception IOException if an error occurs during de-serialiazation
277      * @exception ClassNotFoundException if an error occurs during
278      * de-serialiazation
279      * @exception IllegalBlockSizeException if the given cipher is a block
280      * cipher, no padding has been requested, and the total input length is
281      * not a multiple of the cipher's block size
282      * @exception BadPaddingException if the given cipher has been
283      * initialized for decryption, and padding has been specified, but
284      * the input data does not have proper expected padding bytes
285      */
getObject(Cipher c)286     public final Object getObject(Cipher c)
287         throws IOException, ClassNotFoundException, IllegalBlockSizeException,
288             BadPaddingException
289     {
290         /*
291          * Unseal the object
292          */
293         byte[] content = c.doFinal(this.encryptedContent);
294 
295         /*
296          * De-serialize it
297          */
298         // creating a stream pipe-line, from b to a
299         ByteArrayInputStream b = new ByteArrayInputStream(content);
300         ObjectInput a = new extObjectInputStream(b);
301         try {
302             Object obj = a.readObject();
303             return obj;
304         } finally {
305             a.close();
306         }
307     }
308 
309     /**
310      * Retrieves the original (encapsulated) object.
311      *
312      * <p>This method creates a cipher for the algorithm that had been used in
313      * the sealing operation, using an implementation of that algorithm from
314      * the given <code>provider</code>.
315      * The Cipher object is initialized for decryption, using the given
316      * <code>key</code> and the parameters (if any) that had been used in the
317      * sealing operation.
318      *
319      * <p>The encapsulated object is unsealed and de-serialized, before it is
320      * returned.
321      *
322      * @param key the key used to unseal the object.
323      * @param provider the name of the provider of the algorithm to unseal
324      * the object.
325      *
326      * @return the original object.
327      *
328      * @exception IllegalArgumentException if the given provider is null
329      * or empty.
330      * @exception IOException if an error occurs during de-serialiazation.
331      * @exception ClassNotFoundException if an error occurs during
332      * de-serialiazation.
333      * @exception NoSuchAlgorithmException if the algorithm to unseal the
334      * object is not available.
335      * @exception NoSuchProviderException if the given provider is not
336      * configured.
337      * @exception InvalidKeyException if the given key cannot be used to unseal
338      * the object (e.g., it has the wrong algorithm).
339      * @exception NullPointerException if <code>key</code> is null.
340      */
getObject(Key key, String provider)341     public final Object getObject(Key key, String provider)
342         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
343             NoSuchProviderException, InvalidKeyException
344     {
345         if (key == null) {
346             throw new NullPointerException("key is null");
347         }
348         if (provider == null || provider.length() == 0) {
349             throw new IllegalArgumentException("missing provider");
350         }
351 
352         try {
353             return unseal(key, provider);
354         } catch (IllegalBlockSizeException | BadPaddingException ex) {
355             throw new InvalidKeyException(ex.getMessage());
356         }
357     }
358 
359 
unseal(Key key, String provider)360     private Object unseal(Key key, String provider)
361         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
362             NoSuchProviderException, InvalidKeyException,
363             IllegalBlockSizeException, BadPaddingException
364     {
365         /*
366          * Create the parameter object.
367          */
368         AlgorithmParameters params = null;
369         if (this.encodedParams != null) {
370             try {
371                 if (provider != null)
372                     params = AlgorithmParameters.getInstance(this.paramsAlg,
373                                                              provider);
374                 else
375                     params = AlgorithmParameters.getInstance(this.paramsAlg);
376 
377             } catch (NoSuchProviderException nspe) {
378                 if (provider == null) {
379                     throw new NoSuchAlgorithmException(this.paramsAlg
380                                                        + " not found");
381                 } else {
382                     throw new NoSuchProviderException(nspe.getMessage());
383                 }
384             }
385             params.init(this.encodedParams);
386         }
387 
388         /*
389          * Create and initialize the cipher.
390          */
391         Cipher c;
392         try {
393             if (provider != null)
394                 c = Cipher.getInstance(this.sealAlg, provider);
395             else
396                 c = Cipher.getInstance(this.sealAlg);
397         } catch (NoSuchPaddingException nspe) {
398             throw new NoSuchAlgorithmException("Padding that was used in "
399                                                + "sealing operation not "
400                                                + "available");
401         } catch (NoSuchProviderException nspe) {
402             if (provider == null) {
403                 throw new NoSuchAlgorithmException(this.sealAlg+" not found");
404             } else {
405                 throw new NoSuchProviderException(nspe.getMessage());
406             }
407         }
408 
409         try {
410             if (params != null)
411                 c.init(Cipher.DECRYPT_MODE, key, params);
412             else
413                 c.init(Cipher.DECRYPT_MODE, key);
414         } catch (InvalidAlgorithmParameterException iape) {
415             // this should never happen, because we use the exact same
416             // parameters that were used in the sealing operation
417             throw new RuntimeException(iape.getMessage());
418         }
419 
420         /*
421          * Unseal the object
422          */
423         byte[] content = c.doFinal(this.encryptedContent);
424 
425         /*
426          * De-serialize it
427          */
428         // creating a stream pipe-line, from b to a
429         ByteArrayInputStream b = new ByteArrayInputStream(content);
430         ObjectInput a = new extObjectInputStream(b);
431         try {
432             Object obj = a.readObject();
433             return obj;
434         } finally {
435             a.close();
436         }
437     }
438 
439     /**
440      * Restores the state of the SealedObject from a stream.
441      * @param s the object input stream.
442      * @exception NullPointerException if s is null.
443      */
readObject(java.io.ObjectInputStream s)444     private void readObject(java.io.ObjectInputStream s)
445         throws java.io.IOException, ClassNotFoundException
446     {
447         s.defaultReadObject();
448         if (encryptedContent != null)
449             encryptedContent = encryptedContent.clone();
450         if (encodedParams != null)
451             encodedParams = encodedParams.clone();
452     }
453 }
454 
455 final class extObjectInputStream extends ObjectInputStream {
456 
457     private static ClassLoader systemClassLoader = null;
458 
extObjectInputStream(InputStream in)459     extObjectInputStream(InputStream in)
460         throws IOException, StreamCorruptedException {
461         super(in);
462     }
463 
resolveClass(ObjectStreamClass v)464     protected Class<?> resolveClass(ObjectStreamClass v)
465         throws IOException, ClassNotFoundException
466     {
467 
468         try {
469             /*
470              * Calling the super.resolveClass() first
471              * will let us pick up bug fixes in the super
472              * class (e.g., 4171142).
473              */
474             return super.resolveClass(v);
475         } catch (ClassNotFoundException cnfe) {
476             /*
477              * This is a workaround for bug 4224921.
478              */
479             ClassLoader loader = Thread.currentThread().getContextClassLoader();
480             if (loader == null) {
481                 if (systemClassLoader == null) {
482                     systemClassLoader = ClassLoader.getSystemClassLoader();
483                 }
484                 loader = systemClassLoader;
485                 if (loader == null) {
486                     throw new ClassNotFoundException(v.getName());
487                 }
488             }
489 
490             return Class.forName(v.getName(), false, loader);
491         }
492     }
493 }
494