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.IOException;
     23 import java.io.ObjectInputStream;
     24 import java.io.ObjectOutputStream;
     25 import java.io.Serializable;
     26 import java.security.AlgorithmParameters;
     27 import java.security.InvalidAlgorithmParameterException;
     28 import java.security.InvalidKeyException;
     29 import java.security.Key;
     30 import java.security.NoSuchAlgorithmException;
     31 import 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  */
     44 public 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