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
18package javax.crypto;
19
20import java.io.ByteArrayInputStream;
21import java.io.ByteArrayOutputStream;
22import java.io.IOException;
23import java.io.ObjectInputStream;
24import java.io.ObjectOutputStream;
25import java.io.Serializable;
26import java.security.AlgorithmParameters;
27import java.security.InvalidAlgorithmParameterException;
28import java.security.InvalidKeyException;
29import java.security.Key;
30import java.security.NoSuchAlgorithmException;
31import 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 */
44public 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