1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package javax.crypto; 19 20 import java.io.ByteArrayInputStream; 21 import java.io.ByteArrayOutputStream; 22 import java.io.IOException; 23 import java.io.ObjectInputStream; 24 import java.io.ObjectOutputStream; 25 import java.io.Serializable; 26 import java.security.AlgorithmParameters; 27 import java.security.InvalidAlgorithmParameterException; 28 import java.security.InvalidKeyException; 29 import java.security.Key; 30 import java.security.NoSuchAlgorithmException; 31 import java.security.NoSuchProviderException; 32 33 /** 34 * A {@code SealedObject} is a wrapper around a {@code serializable} object 35 * instance and encrypts it using a cryptographic cipher. 36 * 37 * <p>Since a {@code SealedObject} instance is serializable it can 38 * either be stored or transmitted over an insecure channel. 39 * 40 * <p>The wrapped object can later be decrypted (unsealed) using the corresponding 41 * key and then be deserialized to retrieve the original object. The sealed 42 * object itself keeps track of the cipher and corresponding parameters. 43 */ 44 public class SealedObject implements Serializable { 45 46 private static final long serialVersionUID = 4482838265551344752L; 47 48 /** 49 * The cipher's {@link AlgorithmParameters} in encoded format. 50 * Equivalent to {@code cipher.getParameters().getEncoded()}, 51 * or null if the cipher did not use any parameters. 52 */ 53 protected byte[] encodedParams; 54 55 private byte[] encryptedContent; 56 private String sealAlg; 57 private String paramsAlg; 58 59 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 60 // We do unshared reads here to ensure we have our own clones of the byte[]s. 61 encodedParams = (byte[]) s.readUnshared(); 62 encryptedContent = (byte[]) s.readUnshared(); 63 // These are regular shared reads because the algorithms used by a given stream are 64 // almost certain to the be same for each object, and String is immutable anyway, 65 // so there's no security concern about sharing. 66 sealAlg = (String) s.readObject(); 67 paramsAlg = (String) s.readObject(); 68 } 69 70 /** 71 * Creates a new {@code SealedObject} instance wrapping the specified object 72 * and sealing it using the specified cipher. 73 * <p> 74 * The cipher must be fully initialized. 75 * 76 * @param object 77 * the object to seal, can be {@code null}. 78 * @param c 79 * the cipher to encrypt the object. 80 * @throws IOException 81 * if the serialization fails. 82 * @throws IllegalBlockSizeException 83 * if the specified cipher is a block cipher and the length of 84 * the serialized data is not a multiple of the ciphers block 85 * size. 86 * @throws NullPointerException 87 * if the cipher is {@code null}. 88 */ 89 public SealedObject(Serializable object, Cipher c) 90 throws IOException, IllegalBlockSizeException { 91 if (c == null) { 92 throw new NullPointerException("c == null"); 93 } 94 try { 95 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 96 ObjectOutputStream oos = new ObjectOutputStream(bos); 97 oos.writeObject(object); 98 oos.flush(); 99 AlgorithmParameters ap = c.getParameters(); 100 this.encodedParams = (ap == null) ? null : ap.getEncoded(); 101 this.paramsAlg = (ap == null) ? null : ap.getAlgorithm(); 102 this.sealAlg = c.getAlgorithm(); 103 this.encryptedContent = c.doFinal(bos.toByteArray()); 104 } catch (BadPaddingException e) { 105 // should be never thrown because the cipher 106 // should be initialized for encryption 107 throw new IOException(e.toString()); 108 } 109 } 110 111 /** 112 * Creates a new {@code SealedObject} instance by copying the data from 113 * the specified object. 114 * 115 * @param so 116 * the object to copy. 117 */ 118 protected SealedObject(SealedObject so) { 119 if (so == null) { 120 throw new NullPointerException("so == null"); 121 } 122 this.encryptedContent = so.encryptedContent; 123 this.encodedParams = so.encodedParams; 124 this.sealAlg = so.sealAlg; 125 this.paramsAlg = so.paramsAlg; 126 } 127 128 /** 129 * Returns the algorithm this object was sealed with. 130 * 131 * @return the algorithm this object was sealed with. 132 */ 133 public final String getAlgorithm() { 134 return sealAlg; 135 } 136 137 /** 138 * Returns the wrapped object, decrypting it using the specified key. 139 * 140 * @param key 141 * the key to decrypt the data with. 142 * @return the encapsulated object. 143 * @throws IOException 144 * if deserialization fails. 145 * @throws ClassNotFoundException 146 * if deserialization fails. 147 * @throws NoSuchAlgorithmException 148 * if the algorithm to decrypt the data is not available. 149 * @throws InvalidKeyException 150 * if the specified key cannot be used to decrypt the data. 151 */ 152 public final Object getObject(Key key) 153 throws IOException, ClassNotFoundException, 154 NoSuchAlgorithmException, InvalidKeyException { 155 if (key == null) { 156 throw new InvalidKeyException("key == null"); 157 } 158 try { 159 Cipher cipher = Cipher.getInstance(sealAlg); 160 if ((paramsAlg != null) && (paramsAlg.length() != 0)) { 161 AlgorithmParameters params = 162 AlgorithmParameters.getInstance(paramsAlg); 163 params.init(encodedParams); 164 cipher.init(Cipher.DECRYPT_MODE, key, params); 165 } else { 166 cipher.init(Cipher.DECRYPT_MODE, key); 167 } 168 byte[] serialized = cipher.doFinal(encryptedContent); 169 ObjectInputStream ois = 170 new ObjectInputStream( 171 new ByteArrayInputStream(serialized)); 172 return ois.readObject(); 173 } catch (NoSuchPaddingException e) { 174 // should not be thrown because cipher text was made 175 // with existing padding 176 throw new NoSuchAlgorithmException(e.toString()); 177 } catch (InvalidAlgorithmParameterException e) { 178 // should not be thrown because cipher text was made 179 // with correct algorithm parameters 180 throw new NoSuchAlgorithmException(e.toString()); 181 } catch (IllegalBlockSizeException e) { 182 // should not be thrown because the cipher text 183 // was correctly made 184 throw new NoSuchAlgorithmException(e.toString()); 185 } catch (BadPaddingException e) { 186 // should not be thrown because the cipher text 187 // was correctly made 188 throw new NoSuchAlgorithmException(e.toString()); 189 } catch (IllegalStateException e) { 190 // should never be thrown because cipher is initialized 191 throw new NoSuchAlgorithmException(e.toString()); 192 } 193 } 194 195 /** 196 * Returns the wrapped object, decrypting it using the specified 197 * cipher. 198 * 199 * @param c 200 * the cipher to decrypt the data. 201 * @return the encapsulated object. 202 * @throws IOException 203 * if deserialization fails. 204 * @throws ClassNotFoundException 205 * if deserialization fails. 206 * @throws IllegalBlockSizeException 207 * if the specified cipher is a block cipher and the length of 208 * the serialized data is not a multiple of the ciphers block 209 * size. 210 * @throws BadPaddingException 211 * if the padding of the data does not match the padding scheme. 212 */ 213 public final Object getObject(Cipher c) 214 throws IOException, ClassNotFoundException, 215 IllegalBlockSizeException, BadPaddingException { 216 if (c == null) { 217 throw new NullPointerException("c == null"); 218 } 219 byte[] serialized = c.doFinal(encryptedContent); 220 ObjectInputStream ois = 221 new ObjectInputStream( 222 new ByteArrayInputStream(serialized)); 223 return ois.readObject(); 224 } 225 226 /** 227 * Returns the wrapped object, decrypting it using the specified key. The 228 * specified provider is used to retrieve the cipher algorithm. 229 * 230 * @param key 231 * the key to decrypt the data. 232 * @param provider 233 * the name of the provider that provides the cipher algorithm. 234 * @return the encapsulated object. 235 * @throws IOException 236 * if deserialization fails. 237 * @throws ClassNotFoundException 238 * if deserialization fails. 239 * @throws NoSuchAlgorithmException 240 * if the algorithm used to decrypt the data is not available. 241 * @throws NoSuchProviderException 242 * if the specified provider is not available. 243 * @throws InvalidKeyException 244 * if the specified key cannot be used to decrypt the data. 245 */ 246 public final Object getObject(Key key, String provider) 247 throws IOException, ClassNotFoundException, 248 NoSuchAlgorithmException, NoSuchProviderException, 249 InvalidKeyException { 250 if (provider == null || provider.isEmpty()) { 251 throw new IllegalArgumentException("provider name empty or null"); 252 } 253 try { 254 Cipher cipher = Cipher.getInstance(sealAlg, provider); 255 if ((paramsAlg != null) && (paramsAlg.length() != 0)) { 256 AlgorithmParameters params = 257 AlgorithmParameters.getInstance(paramsAlg); 258 params.init(encodedParams); 259 cipher.init(Cipher.DECRYPT_MODE, key, params); 260 } else { 261 cipher.init(Cipher.DECRYPT_MODE, key); 262 } 263 byte[] serialized = cipher.doFinal(encryptedContent); 264 ObjectInputStream ois = 265 new ObjectInputStream( 266 new ByteArrayInputStream(serialized)); 267 return ois.readObject(); 268 } catch (NoSuchPaddingException e) { 269 // should not be thrown because cipher text was made 270 // with existing padding 271 throw new NoSuchAlgorithmException(e.toString()); 272 } catch (InvalidAlgorithmParameterException e) { 273 // should not be thrown because cipher text was made 274 // with correct algorithm parameters 275 throw new NoSuchAlgorithmException(e.toString()); 276 } catch (IllegalBlockSizeException e) { 277 // should not be thrown because the cipher text 278 // was correctly made 279 throw new NoSuchAlgorithmException(e.toString()); 280 } catch (BadPaddingException e) { 281 // should not be thrown because the cipher text 282 // was correctly made 283 throw new NoSuchAlgorithmException(e.toString()); 284 } catch (IllegalStateException e) { 285 // should never be thrown because cipher is initialized 286 throw new NoSuchAlgorithmException(e.toString()); 287 } 288 } 289 } 290