1 /*
   2  * Copyright (c) 1997, 2018, 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.misc.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     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 = so.encryptedContent.clone();
 197         this.sealAlg = so.sealAlg;
 198         this.paramsAlg = so.paramsAlg;
 199         if (so.encodedParams != null) {
 200             this.encodedParams = 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         ObjectInput a = getExtObjectInputStream(c);
 293         try {
 294             Object obj = a.readObject();
 295             return obj;
 296         } finally {
 297             a.close();
 298         }
 299     }
 300 
 301     /**
 302      * Retrieves the original (encapsulated) object.
 303      *
 304      * <p>This method creates a cipher for the algorithm that had been used in
 305      * the sealing operation, using an implementation of that algorithm from
 306      * the given <code>provider</code>.
 307      * The Cipher object is initialized for decryption, using the given
 308      * <code>key</code> and the parameters (if any) that had been used in the
 309      * sealing operation.
 310      *
 311      * <p>The encapsulated object is unsealed and de-serialized, before it is
 312      * returned.
 313      *
 314      * @param key the key used to unseal the object.
 315      * @param provider the name of the provider of the algorithm to unseal
 316      * the object.
 317      *
 318      * @return the original object.
 319      *
 320      * @exception IllegalArgumentException if the given provider is null
 321      * or empty.
 322      * @exception IOException if an error occurs during de-serialiazation.
 323      * @exception ClassNotFoundException if an error occurs during
 324      * de-serialiazation.
 325      * @exception NoSuchAlgorithmException if the algorithm to unseal the
 326      * object is not available.
 327      * @exception NoSuchProviderException if the given provider is not
 328      * configured.
 329      * @exception InvalidKeyException if the given key cannot be used to unseal
 330      * the object (e.g., it has the wrong algorithm).
 331      * @exception NullPointerException if <code>key</code> is null.
 332      */
 333     public final Object getObject(Key key, String provider)
 334         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
 335             NoSuchProviderException, InvalidKeyException
 336     {
 337         if (key == null) {
 338             throw new NullPointerException("key is null");
 339         }
 340         if (provider == null || provider.isEmpty()) {
 341             throw new IllegalArgumentException("missing provider");
 342         }
 343 
 344         try {
 345             return unseal(key, provider);
 346         } catch (IllegalBlockSizeException | BadPaddingException ex) {
 347             throw new InvalidKeyException(ex.getMessage());
 348         }
 349     }
 350 
 351 
 352     private Object unseal(Key key, String provider)
 353         throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
 354             NoSuchProviderException, InvalidKeyException,
 355             IllegalBlockSizeException, BadPaddingException
 356     {
 357         /*
 358          * Create the parameter object.
 359          */
 360         AlgorithmParameters params = null;
 361         if (this.encodedParams != null) {
 362             try {
 363                 if (provider != null)
 364                     params = AlgorithmParameters.getInstance(this.paramsAlg,
 365                                                              provider);
 366                 else
 367                     params = AlgorithmParameters.getInstance(this.paramsAlg);
 368 
 369             } catch (NoSuchProviderException nspe) {
 370                 if (provider == null) {
 371                     throw new NoSuchAlgorithmException(this.paramsAlg
 372                                                        + " not found");
 373                 } else {
 374                     throw new NoSuchProviderException(nspe.getMessage());
 375                 }
 376             }
 377             params.init(this.encodedParams);
 378         }
 379 
 380         /*
 381          * Create and initialize the cipher.
 382          */
 383         Cipher c;
 384         try {
 385             if (provider != null)
 386                 c = Cipher.getInstance(this.sealAlg, provider);
 387             else
 388                 c = Cipher.getInstance(this.sealAlg);
 389         } catch (NoSuchPaddingException nspe) {
 390             throw new NoSuchAlgorithmException("Padding that was used in "
 391                                                + "sealing operation not "
 392                                                + "available");
 393         } catch (NoSuchProviderException nspe) {
 394             if (provider == null) {
 395                 throw new NoSuchAlgorithmException(this.sealAlg+" not found");
 396             } else {
 397                 throw new NoSuchProviderException(nspe.getMessage());
 398             }
 399         }
 400 
 401         try {
 402             if (params != null)
 403                 c.init(Cipher.DECRYPT_MODE, key, params);
 404             else
 405                 c.init(Cipher.DECRYPT_MODE, key);
 406         } catch (InvalidAlgorithmParameterException iape) {
 407             // this should never happen, because we use the exact same
 408             // parameters that were used in the sealing operation
 409             throw new RuntimeException(iape.getMessage());
 410         }
 411 
 412         ObjectInput a = getExtObjectInputStream(c);
 413         try {
 414             Object obj = a.readObject();
 415             return obj;
 416         } finally {
 417             a.close();
 418         }
 419     }
 420 
 421     /**
 422      * Restores the state of the SealedObject from a stream.
 423      * @param s the object input stream.
 424      * @exception NullPointerException if s is null.
 425      */
 426     private void readObject(java.io.ObjectInputStream s)
 427         throws java.io.IOException, ClassNotFoundException
 428     {
 429         s.defaultReadObject();
 430         if (encryptedContent != null)
 431             encryptedContent = encryptedContent.clone();
 432         if (encodedParams != null)
 433             encodedParams = encodedParams.clone();
 434     }
 435 
 436     // This method is also called inside SealedObjectForKeyProtector.java.
 437     private ObjectInputStream getExtObjectInputStream(Cipher c)
 438             throws BadPaddingException, IllegalBlockSizeException, IOException {
 439 
 440         byte[] content = c.doFinal(this.encryptedContent);
 441         ByteArrayInputStream b = new ByteArrayInputStream(content);
 442         return new extObjectInputStream(b);
 443     }
 444 
 445     static {
 446         SharedSecrets.setJavaxCryptoSealedObjectAccess((obj,c) -> obj.getExtObjectInputStream(c));
 447     }
 448 }
 449 
 450 final class extObjectInputStream extends ObjectInputStream {
 451     extObjectInputStream(InputStream in)
 452         throws IOException, StreamCorruptedException {
 453         super(in);
 454     }
 455 
 456     protected Class<?> resolveClass(ObjectStreamClass v)
 457         throws IOException, ClassNotFoundException
 458     {
 459 
 460         try {
 461             /*
 462              * Calling the super.resolveClass() first
 463              * will let us pick up bug fixes in the super
 464              * class (e.g., 4171142).
 465              */
 466             return super.resolveClass(v);
 467         } catch (ClassNotFoundException cnfe) {
 468             /*
 469              * This is a workaround for bug 4224921.
 470              */
 471             ClassLoader loader = Thread.currentThread().getContextClassLoader();
 472             if (loader == null) {
 473                 loader = ClassLoader.getSystemClassLoader();
 474                 if (loader == null) {
 475                     throw new ClassNotFoundException(v.getName());
 476                 }
 477             }
 478 
 479             return Class.forName(v.getName(), false, loader);
 480         }
 481     }
 482 }