jdk/src/share/classes/javax/crypto/SealedObject.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/crypto/SealedObject.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,497 @@
+/*
+ * Copyright 1997-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.crypto;
+
+import java.io.*;
+import java.security.AlgorithmParameters;
+import java.security.Key;
+import java.security.InvalidKeyException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+/**
+ * This class enables a programmer to create an object and protect its
+ * confidentiality with a cryptographic algorithm.
+ *
+ * <p> Given any Serializable object, one can create a SealedObject
+ * that encapsulates the original object, in serialized
+ * format (i.e., a "deep copy"), and seals (encrypts) its serialized contents,
+ * using a cryptographic algorithm such as DES, to protect its
+ * confidentiality.  The encrypted content can later be decrypted (with
+ * the corresponding algorithm using the correct decryption key) and
+ * de-serialized, yielding the original object.
+ *
+ * <p> Note that the Cipher object must be fully initialized with the
+ * correct algorithm, key, padding scheme, etc., before being applied
+ * to a SealedObject.
+ *
+ * <p> The original object that was sealed can be recovered in two different
+ * ways: <p>
+ *
+ * <ul>
+ *
+ * <li>by using the {@link #getObject(javax.crypto.Cipher) getObject}
+ * method that takes a <code>Cipher</code> object.
+ *
+ * <p> This method requires a fully initialized <code>Cipher</code> object,
+ * initialized with the
+ * exact same algorithm, key, padding scheme, etc., that were used to seal the
+ * object.
+ *
+ * <p> This approach has the advantage that the party who unseals the
+ * sealed object does not require knowledge of the decryption key. For example,
+ * after one party has initialized the cipher object with the required
+ * decryption key, it could hand over the cipher object to
+ * another party who then unseals the sealed object.
+ *
+ * <p>
+ *
+ * <li>by using one of the
+ * {@link #getObject(java.security.Key) getObject} methods
+ * that take a <code>Key</code> object.
+ *
+ * <p> In this approach, the <code>getObject</code> method creates a cipher
+ * object for the appropriate decryption algorithm and initializes it with the
+ * given decryption key and the algorithm parameters (if any) that were stored
+ * in the sealed object.
+ *
+ * <p> This approach has the advantage that the party who
+ * unseals the object does not need to keep track of the parameters (e.g., an
+ * IV) that were used to seal the object.
+ *
+ * </ul>
+ *
+ * @author Li Gong
+ * @author Jan Luehe
+ * @see Cipher
+ * @since 1.4
+ */
+
+public class SealedObject implements Serializable {
+
+    static final long serialVersionUID = 4482838265551344752L;
+
+    /**
+     * The serialized object contents in encrypted format.
+     *
+     * @serial
+     */
+    private byte[] encryptedContent = null;
+
+    /**
+     * The algorithm that was used to seal this object.
+     *
+     * @serial
+     */
+    private String sealAlg = null;
+
+    /**
+     * The algorithm of the parameters used.
+     *
+     * @serial
+     */
+    private String paramsAlg = null;
+
+    /**
+     * The cryptographic parameters used by the sealing Cipher,
+     * encoded in the default format.
+     * <p>
+     * That is, <code>cipher.getParameters().getEncoded()</code>.
+     *
+     * @serial
+     */
+    protected byte[] encodedParams = null;
+
+    /**
+     * Constructs a SealedObject from any Serializable object.
+     *
+     * <p>The given object is serialized, and its serialized contents are
+     * encrypted using the given Cipher, which must be fully initialized.
+     *
+     * <p>Any algorithm parameters that may be used in the encryption
+     * operation are stored inside of the new <code>SealedObject</code>.
+     *
+     * @param object the object to be sealed; can be null.
+     * @param c the cipher used to seal the object.
+     *
+     * @exception NullPointerException if the given cipher is null.
+     * @exception IOException if an error occurs during serialization
+     * @exception IllegalBlockSizeException if the given cipher is a block
+     * cipher, no padding has been requested, and the total input length
+     * (i.e., the length of the serialized object contents) is not a multiple
+     * of the cipher's block size
+     */
+    public SealedObject(Serializable object, Cipher c) throws IOException,
+        IllegalBlockSizeException
+    {
+        /*
+         * Serialize the object
+         */
+
+        // creating a stream pipe-line, from a to b
+        ByteArrayOutputStream b = new ByteArrayOutputStream();
+        ObjectOutput a = new ObjectOutputStream(b);
+        byte[] content;
+        try {
+            // write and flush the object content to byte array
+            a.writeObject(object);
+            a.flush();
+            content = b.toByteArray();
+        } finally {
+            a.close();
+        }
+
+        /*
+         * Seal the object
+         */
+        try {
+            this.encryptedContent = c.doFinal(content);
+        }
+        catch (BadPaddingException ex) {
+            // if sealing is encryption only
+            // Should never happen??
+        }
+
+        // Save the parameters
+        if (c.getParameters() != null) {
+            this.encodedParams = c.getParameters().getEncoded();
+            this.paramsAlg = c.getParameters().getAlgorithm();
+        }
+
+        // Save the encryption algorithm
+        this.sealAlg = c.getAlgorithm();
+    }
+
+    /**
+     * Constructs a SealedObject object from the passed-in SealedObject.
+     *
+     * @param so a SealedObject object
+     * @exception NullPointerException if the given sealed object is null.
+     */
+    protected SealedObject(SealedObject so) {
+        this.encryptedContent = (byte[]) so.encryptedContent.clone();
+        this.sealAlg = so.sealAlg;
+        this.paramsAlg = so.paramsAlg;
+        if (so.encodedParams != null) {
+            this.encodedParams = (byte[]) so.encodedParams.clone();
+        } else {
+            this.encodedParams = null;
+        }
+    }
+
+    /**
+     * Returns the algorithm that was used to seal this object.
+     *
+     * @return the algorithm that was used to seal this object.
+     */
+    public final String getAlgorithm() {
+        return this.sealAlg;
+    }
+
+    /**
+     * Retrieves the original (encapsulated) object.
+     *
+     * <p>This method creates a cipher for the algorithm that had been used in
+     * the sealing operation.
+     * If the default provider package provides an implementation of that
+     * algorithm, an instance of Cipher containing that implementation is used.
+     * If the algorithm is not available in the default package, other
+     * packages are searched.
+     * The Cipher object is initialized for decryption, using the given
+     * <code>key</code> and the parameters (if any) that had been used in the
+     * sealing operation.
+     *
+     * <p>The encapsulated object is unsealed and de-serialized, before it is
+     * returned.
+     *
+     * @param key the key used to unseal the object.
+     *
+     * @return the original object.
+     *
+     * @exception IOException if an error occurs during de-serialiazation.
+     * @exception ClassNotFoundException if an error occurs during
+     * de-serialiazation.
+     * @exception NoSuchAlgorithmException if the algorithm to unseal the
+     * object is not available.
+     * @exception InvalidKeyException if the given key cannot be used to unseal
+     * the object (e.g., it has the wrong algorithm).
+     * @exception NullPointerException if <code>key</code> is null.
+     */
+    public final Object getObject(Key key)
+        throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
+            InvalidKeyException
+    {
+        if (key == null) {
+            throw new NullPointerException("key is null");
+        }
+
+        try {
+            return unseal(key, null);
+        } catch (NoSuchProviderException nspe) {
+            // we've already caught NoSuchProviderException's and converted
+            // them into NoSuchAlgorithmException's with details about
+            // the failing algorithm
+            throw new NoSuchAlgorithmException("algorithm not found");
+        } catch (IllegalBlockSizeException ibse) {
+            throw new InvalidKeyException(ibse.getMessage());
+        } catch (BadPaddingException bpe) {
+            throw new InvalidKeyException(bpe.getMessage());
+        }
+    }
+
+    /**
+     * Retrieves the original (encapsulated) object.
+     *
+     * <p>The encapsulated object is unsealed (using the given Cipher,
+     * assuming that the Cipher is already properly initialized) and
+     * de-serialized, before it is returned.
+     *
+     * @param c the cipher used to unseal the object
+     *
+     * @return the original object.
+     *
+     * @exception NullPointerException if the given cipher is null.
+     * @exception IOException if an error occurs during de-serialiazation
+     * @exception ClassNotFoundException if an error occurs during
+     * de-serialiazation
+     * @exception IllegalBlockSizeException if the given cipher is a block
+     * cipher, no padding has been requested, and the total input length is
+     * not a multiple of the cipher's block size
+     * @exception BadPaddingException if the given cipher has been
+     * initialized for decryption, and padding has been specified, but
+     * the input data does not have proper expected padding bytes
+     */
+    public final Object getObject(Cipher c)
+        throws IOException, ClassNotFoundException, IllegalBlockSizeException,
+            BadPaddingException
+    {
+        /*
+         * Unseal the object
+         */
+        byte[] content = c.doFinal(this.encryptedContent);
+
+        /*
+         * De-serialize it
+         */
+        // creating a stream pipe-line, from b to a
+        ByteArrayInputStream b = new ByteArrayInputStream(content);
+        ObjectInput a = new extObjectInputStream(b);
+        try {
+            Object obj = a.readObject();
+            return obj;
+        } finally {
+            a.close();
+        }
+    }
+
+    /**
+     * Retrieves the original (encapsulated) object.
+     *
+     * <p>This method creates a cipher for the algorithm that had been used in
+     * the sealing operation, using an implementation of that algorithm from
+     * the given <code>provider</code>.
+     * The Cipher object is initialized for decryption, using the given
+     * <code>key</code> and the parameters (if any) that had been used in the
+     * sealing operation.
+     *
+     * <p>The encapsulated object is unsealed and de-serialized, before it is
+     * returned.
+     *
+     * @param key the key used to unseal the object.
+     * @param provider the name of the provider of the algorithm to unseal
+     * the object.
+     *
+     * @return the original object.
+     *
+     * @exception IllegalArgumentException if the given provider is null
+     * or empty.
+     * @exception IOException if an error occurs during de-serialiazation.
+     * @exception ClassNotFoundException if an error occurs during
+     * de-serialiazation.
+     * @exception NoSuchAlgorithmException if the algorithm to unseal the
+     * object is not available.
+     * @exception NoSuchProviderException if the given provider is not
+     * configured.
+     * @exception InvalidKeyException if the given key cannot be used to unseal
+     * the object (e.g., it has the wrong algorithm).
+     * @exception NullPointerException if <code>key</code> is null.
+     */
+    public final Object getObject(Key key, String provider)
+        throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
+            NoSuchProviderException, InvalidKeyException
+    {
+        if (key == null) {
+            throw new NullPointerException("key is null");
+        }
+        if (provider == null || provider.length() == 0) {
+            throw new IllegalArgumentException("missing provider");
+        }
+
+        try {
+            return unseal(key, provider);
+        } catch (IllegalBlockSizeException ibse) {
+            throw new InvalidKeyException(ibse.getMessage());
+        } catch (BadPaddingException bpe) {
+            throw new InvalidKeyException(bpe.getMessage());
+        }
+    }
+
+
+    private Object unseal(Key key, String provider)
+        throws IOException, ClassNotFoundException, NoSuchAlgorithmException,
+            NoSuchProviderException, InvalidKeyException,
+            IllegalBlockSizeException, BadPaddingException
+    {
+        /*
+         * Create the parameter object.
+         */
+        AlgorithmParameters params = null;
+        if (this.encodedParams != null) {
+            try {
+                if (provider != null)
+                    params = AlgorithmParameters.getInstance(this.paramsAlg,
+                                                             provider);
+                else
+                    params = AlgorithmParameters.getInstance(this.paramsAlg);
+
+            } catch (NoSuchProviderException nspe) {
+                if (provider == null) {
+                    throw new NoSuchAlgorithmException(this.paramsAlg
+                                                       + " not found");
+                } else {
+                    throw new NoSuchProviderException(nspe.getMessage());
+                }
+            }
+            params.init(this.encodedParams);
+        }
+
+        /*
+         * Create and initialize the cipher.
+         */
+        Cipher c;
+        try {
+            if (provider != null)
+                c = Cipher.getInstance(this.sealAlg, provider);
+            else
+                c = Cipher.getInstance(this.sealAlg);
+        } catch (NoSuchPaddingException nspe) {
+            throw new NoSuchAlgorithmException("Padding that was used in "
+                                               + "sealing operation not "
+                                               + "available");
+        } catch (NoSuchProviderException nspe) {
+            if (provider == null) {
+                throw new NoSuchAlgorithmException(this.sealAlg+" not found");
+            } else {
+                throw new NoSuchProviderException(nspe.getMessage());
+            }
+        }
+
+        try {
+            if (params != null)
+                c.init(Cipher.DECRYPT_MODE, key, params);
+            else
+                c.init(Cipher.DECRYPT_MODE, key);
+        } catch (InvalidAlgorithmParameterException iape) {
+            // this should never happen, because we use the exact same
+            // parameters that were used in the sealing operation
+            throw new RuntimeException(iape.getMessage());
+        }
+
+        /*
+         * Unseal the object
+         */
+        byte[] content = c.doFinal(this.encryptedContent);
+
+        /*
+         * De-serialize it
+         */
+        // creating a stream pipe-line, from b to a
+        ByteArrayInputStream b = new ByteArrayInputStream(content);
+        ObjectInput a = new extObjectInputStream(b);
+        try {
+            Object obj = a.readObject();
+            return obj;
+        } finally {
+            a.close();
+        }
+    }
+
+    /**
+     * Restores the state of the SealedObject from a stream.
+     * @param s the object input stream.
+     * @exception NullPointerException if s is null.
+     */
+    private void readObject(java.io.ObjectInputStream s)
+        throws java.io.IOException, ClassNotFoundException
+    {
+        s.defaultReadObject();
+        if (encryptedContent != null)
+            encryptedContent = (byte[])encryptedContent.clone();
+        if (encodedParams != null)
+            encodedParams = (byte[])encodedParams.clone();
+    }
+}
+
+final class extObjectInputStream extends ObjectInputStream {
+
+    private static ClassLoader systemClassLoader = null;
+
+    extObjectInputStream(InputStream in)
+        throws IOException, StreamCorruptedException {
+        super(in);
+    }
+
+    protected Class resolveClass(ObjectStreamClass v)
+        throws IOException, ClassNotFoundException
+    {
+
+        try {
+            /*
+             * Calling the super.resolveClass() first
+             * will let us pick up bug fixes in the super
+             * class (e.g., 4171142).
+             */
+            return super.resolveClass(v);
+        } catch (ClassNotFoundException cnfe) {
+            /*
+             * This is a workaround for bug 4224921.
+             */
+            ClassLoader loader = Thread.currentThread().getContextClassLoader();
+            if (loader == null) {
+                if (systemClassLoader == null) {
+                    systemClassLoader = ClassLoader.getSystemClassLoader();
+                }
+                loader = systemClassLoader;
+                if (loader == null) {
+                    throw new ClassNotFoundException(v.getName());
+                }
+            }
+
+            return Class.forName(v.getName(), false, loader);
+        }
+    }
+}