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