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 }