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 */ SealedObject(Serializable object, Cipher c)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 */ SealedObject(SealedObject so)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 */ getAlgorithm()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 */ getObject(Key key)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 */ getObject(Cipher c)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 */ getObject(Key key, String provider)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 unseal(Key key, String provider)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 */ readObject(java.io.ObjectInputStream s)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 extObjectInputStream(InputStream in)459 extObjectInputStream(InputStream in) 460 throws IOException, StreamCorruptedException { 461 super(in); 462 } 463 resolveClass(ObjectStreamClass v)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