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