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