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