Home | History | Annotate | Download | only in crypto
      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 
     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 
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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