Home | History | Annotate | Download | only in crypto
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    463     extObjectInputStream(InputStream in)
    464         throws IOException, StreamCorruptedException {
    465         super(in);
    466     }
    467 
    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