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
26package javax.crypto;
27
28import java.io.*;
29import java.security.AlgorithmParameters;
30import java.security.Key;
31import java.security.InvalidKeyException;
32import java.security.InvalidAlgorithmParameterException;
33import java.security.NoSuchAlgorithmException;
34import 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
92public 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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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
455final class extObjectInputStream extends ObjectInputStream {
456
457    private static ClassLoader systemClassLoader = null;
458
459    extObjectInputStream(InputStream in)
460        throws IOException, StreamCorruptedException {
461        super(in);
462    }
463
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