Home | History | Annotate | Download | only in crypto
      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      */
    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 
    455 final 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