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.Closeable; 23 import java.io.IOException; 24 import java.io.ObjectInputStream; 25 import java.io.ObjectOutputStream; 26 import java.io.Serializable; 27 import java.security.AlgorithmParameters; 28 import java.security.InvalidAlgorithmParameterException; 29 import java.security.InvalidKeyException; 30 import java.security.Key; 31 import java.security.NoSuchAlgorithmException; 32 import java.security.NoSuchProviderException; 33 import libcore.io.IoUtils; 34 35 /** 36 * A {@code SealedObject} is a wrapper around a {@code serializable} object 37 * instance and encrypts it using a cryptographic cipher. 38 * 39 * <p>Since a {@code SealedObject} instance is serializable it can 40 * either be stored or transmitted over an insecure channel. 41 * 42 * <p>The wrapped object can later be decrypted (unsealed) using the corresponding 43 * key and then be deserialized to retrieve the original object. The sealed 44 * object itself keeps track of the cipher and corresponding parameters. 45 */ 46 public class SealedObject implements Serializable { 47 48 private static final long serialVersionUID = 4482838265551344752L; 49 50 /** 51 * The cipher's {@link AlgorithmParameters} in encoded format. 52 * Equivalent to {@code cipher.getParameters().getEncoded()}, 53 * or null if the cipher did not use any parameters. 54 */ 55 protected byte[] encodedParams; 56 57 private byte[] encryptedContent; 58 private String sealAlg; 59 private String paramsAlg; 60 readObject(ObjectInputStream s)61 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 62 // This implementation is based on the latest recommendations for safe deserialization at 63 // the time of writing. See the Serialization spec section A.6. 64 ObjectInputStream.GetField fields = s.readFields(); 65 66 // The mutable byte arrays are cloned and the immutable strings are not. 67 this.encodedParams = getSafeCopy(fields, "encodedParams"); 68 this.encryptedContent = getSafeCopy(fields, "encryptedContent"); 69 this.paramsAlg = (String) fields.get("paramsAlg", null); 70 this.sealAlg = (String) fields.get("sealAlg", null); 71 } 72 getSafeCopy(ObjectInputStream.GetField fields, String fieldName)73 private static byte[] getSafeCopy(ObjectInputStream.GetField fields, String fieldName) 74 throws IOException { 75 byte[] fieldValue = (byte[]) fields.get(fieldName, null); 76 return fieldValue != null ? fieldValue.clone() : null; 77 } 78 79 /** 80 * Creates a new {@code SealedObject} instance wrapping the specified object 81 * and sealing it using the specified cipher. 82 * <p> 83 * The cipher must be fully initialized. 84 * 85 * @param object 86 * the object to seal, can be {@code null}. 87 * @param c 88 * the cipher to encrypt the object. 89 * @throws IOException 90 * if the serialization fails. 91 * @throws IllegalBlockSizeException 92 * if the specified cipher is a block cipher and the length of 93 * the serialized data is not a multiple of the ciphers block 94 * size. 95 * @throws NullPointerException 96 * if the cipher is {@code null}. 97 */ SealedObject(Serializable object, Cipher c)98 public SealedObject(Serializable object, Cipher c) 99 throws IOException, IllegalBlockSizeException { 100 if (c == null) { 101 throw new NullPointerException("c == null"); 102 } 103 ObjectOutputStream oos = null; 104 try { 105 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 106 oos = new ObjectOutputStream(bos); 107 oos.writeObject(object); 108 oos.flush(); 109 AlgorithmParameters ap = c.getParameters(); 110 this.encodedParams = (ap == null) ? null : ap.getEncoded(); 111 this.paramsAlg = (ap == null) ? null : ap.getAlgorithm(); 112 this.sealAlg = c.getAlgorithm(); 113 this.encryptedContent = c.doFinal(bos.toByteArray()); 114 } catch (BadPaddingException e) { 115 // should be never thrown because the cipher 116 // should be initialized for encryption 117 throw new IOException(e.toString()); 118 } finally { 119 IoUtils.closeQuietly(oos); 120 } 121 } 122 123 /** 124 * Creates a new {@code SealedObject} instance by copying the data from 125 * the specified object. 126 * 127 * @param so 128 * the object to copy. 129 */ SealedObject(SealedObject so)130 protected SealedObject(SealedObject so) { 131 if (so == null) { 132 throw new NullPointerException("so == null"); 133 } 134 // For safety: clone the mutable arrays so that each object has its own independent copy of 135 // the data. 136 this.encryptedContent = so.encryptedContent != null ? so.encryptedContent.clone() : null; 137 this.encodedParams = so.encodedParams != null ? so.encodedParams.clone() : null; 138 this.sealAlg = so.sealAlg; 139 this.paramsAlg = so.paramsAlg; 140 } 141 142 /** 143 * Returns the algorithm this object was sealed with. 144 * 145 * @return the algorithm this object was sealed with. 146 */ getAlgorithm()147 public final String getAlgorithm() { 148 return sealAlg; 149 } 150 151 /** 152 * Returns the wrapped object, decrypting it using the specified key. 153 * 154 * @param key 155 * the key to decrypt the data with. 156 * @return the encapsulated object. 157 * @throws IOException 158 * if deserialization fails. 159 * @throws ClassNotFoundException 160 * if deserialization fails. 161 * @throws NoSuchAlgorithmException 162 * if the algorithm to decrypt the data is not available. 163 * @throws InvalidKeyException 164 * if the specified key cannot be used to decrypt the data. 165 */ getObject(Key key)166 public final Object getObject(Key key) 167 throws IOException, ClassNotFoundException, 168 NoSuchAlgorithmException, InvalidKeyException { 169 if (key == null) { 170 throw new InvalidKeyException("key == null"); 171 } 172 try { 173 Cipher cipher = Cipher.getInstance(sealAlg); 174 if ((paramsAlg != null) && (paramsAlg.length() != 0)) { 175 AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg); 176 params.init(encodedParams); 177 cipher.init(Cipher.DECRYPT_MODE, key, params); 178 } else { 179 cipher.init(Cipher.DECRYPT_MODE, key); 180 } 181 byte[] serialized = cipher.doFinal(encryptedContent); 182 return readSerialized(serialized); 183 } catch (NoSuchPaddingException e) { 184 // should not be thrown because cipher text was made 185 // with existing padding 186 throw new NoSuchAlgorithmException(e.toString()); 187 } catch (InvalidAlgorithmParameterException e) { 188 // should not be thrown because cipher text was made 189 // with correct algorithm parameters 190 throw new NoSuchAlgorithmException(e.toString()); 191 } catch (IllegalBlockSizeException e) { 192 // should not be thrown because the cipher text 193 // was correctly made 194 throw new NoSuchAlgorithmException(e.toString()); 195 } catch (BadPaddingException e) { 196 // should not be thrown because the cipher text 197 // was correctly made 198 throw new NoSuchAlgorithmException(e.toString()); 199 } catch (IllegalStateException e) { 200 // should never be thrown because cipher is initialized 201 throw new NoSuchAlgorithmException(e.toString()); 202 } 203 } 204 205 /** 206 * Returns the wrapped object, decrypting it using the specified 207 * cipher. 208 * 209 * @param c 210 * the cipher to decrypt the data. 211 * @return the encapsulated object. 212 * @throws IOException 213 * if deserialization fails. 214 * @throws ClassNotFoundException 215 * if deserialization fails. 216 * @throws IllegalBlockSizeException 217 * if the specified cipher is a block cipher and the length of 218 * the serialized data is not a multiple of the ciphers block 219 * size. 220 * @throws BadPaddingException 221 * if the padding of the data does not match the padding scheme. 222 */ getObject(Cipher c)223 public final Object getObject(Cipher c) 224 throws IOException, ClassNotFoundException, 225 IllegalBlockSizeException, BadPaddingException { 226 if (c == null) { 227 throw new NullPointerException("c == null"); 228 } 229 byte[] serialized = c.doFinal(encryptedContent); 230 return readSerialized(serialized); 231 } 232 233 /** 234 * Returns the wrapped object, decrypting it using the specified key. The 235 * specified provider is used to retrieve the cipher algorithm. 236 * 237 * @param key 238 * the key to decrypt the data. 239 * @param provider 240 * the name of the provider that provides the cipher algorithm. 241 * @return the encapsulated object. 242 * @throws IOException 243 * if deserialization fails. 244 * @throws ClassNotFoundException 245 * if deserialization fails. 246 * @throws NoSuchAlgorithmException 247 * if the algorithm used to decrypt the data is not available. 248 * @throws NoSuchProviderException 249 * if the specified provider is not available. 250 * @throws InvalidKeyException 251 * if the specified key cannot be used to decrypt the data. 252 */ getObject(Key key, String provider)253 public final Object getObject(Key key, String provider) 254 throws IOException, ClassNotFoundException, 255 NoSuchAlgorithmException, NoSuchProviderException, 256 InvalidKeyException { 257 if (provider == null || provider.isEmpty()) { 258 throw new IllegalArgumentException("provider name empty or null"); 259 } 260 try { 261 Cipher cipher = Cipher.getInstance(sealAlg, provider); 262 if ((paramsAlg != null) && (paramsAlg.length() != 0)) { 263 AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg); 264 params.init(encodedParams); 265 cipher.init(Cipher.DECRYPT_MODE, key, params); 266 } else { 267 cipher.init(Cipher.DECRYPT_MODE, key); 268 } 269 byte[] serialized = cipher.doFinal(encryptedContent); 270 return readSerialized(serialized); 271 } catch (NoSuchPaddingException e) { 272 // should not be thrown because cipher text was made 273 // with existing padding 274 throw new NoSuchAlgorithmException(e.toString()); 275 } catch (InvalidAlgorithmParameterException e) { 276 // should not be thrown because cipher text was made 277 // with correct algorithm parameters 278 throw new NoSuchAlgorithmException(e.toString()); 279 } catch (IllegalBlockSizeException e) { 280 // should not be thrown because the cipher text 281 // was correctly made 282 throw new NoSuchAlgorithmException(e.toString()); 283 } catch (BadPaddingException e) { 284 // should not be thrown because the cipher text 285 // was correctly made 286 throw new NoSuchAlgorithmException(e.toString()); 287 } catch (IllegalStateException e) { 288 // should never be thrown because cipher is initialized 289 throw new NoSuchAlgorithmException(e.toString()); 290 } 291 } 292 readSerialized(byte[] serialized)293 private static Object readSerialized(byte[] serialized) 294 throws IOException, ClassNotFoundException { 295 ObjectInputStream ois = null; 296 try { 297 ois = new ObjectInputStream(new ByteArrayInputStream(serialized)); 298 return ois.readObject(); 299 } finally { 300 IoUtils.closeQuietly(ois); 301 } 302 } 303 } 304