Path: blob/master/src/java.base/share/classes/javax/crypto/SealedObject.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.crypto;2627import jdk.internal.access.SharedSecrets;2829import java.io.*;30import java.security.AlgorithmParameters;31import java.security.Key;32import java.security.InvalidKeyException;33import java.security.InvalidAlgorithmParameterException;34import java.security.NoSuchAlgorithmException;35import java.security.NoSuchProviderException;36import java.util.Arrays;3738/**39* This class enables a programmer to create an object and protect its40* confidentiality with a cryptographic algorithm.41*42* <p> Given any Serializable object, one can create a SealedObject43* that encapsulates the original object, in serialized44* format (i.e., a "deep copy"), and seals (encrypts) its serialized contents,45* using a cryptographic algorithm such as AES, to protect its46* confidentiality. The encrypted content can later be decrypted (with47* the corresponding algorithm using the correct decryption key) and48* de-serialized, yielding the original object.49*50* <p> Note that the Cipher object must be fully initialized with the51* correct algorithm, key, padding scheme, etc., before being applied52* to a SealedObject.53*54* <p> The original object that was sealed can be recovered in two different55* ways:56*57* <ul>58*59* <li>by using the {@link #getObject(javax.crypto.Cipher) getObject}60* method that takes a <code>Cipher</code> object.61*62* <p> This method requires a fully initialized <code>Cipher</code> object,63* initialized with the64* exact same algorithm, key, padding scheme, etc., that were used to seal the65* object.66*67* <p> This approach has the advantage that the party who unseals the68* sealed object does not require knowledge of the decryption key. For example,69* after one party has initialized the cipher object with the required70* decryption key, it could hand over the cipher object to71* another party who then unseals the sealed object.72*73* <li>by using one of the74* {@link #getObject(java.security.Key) getObject} methods75* that take a <code>Key</code> object.76*77* <p> In this approach, the <code>getObject</code> method creates a cipher78* object for the appropriate decryption algorithm and initializes it with the79* given decryption key and the algorithm parameters (if any) that were stored80* in the sealed object.81*82* <p> This approach has the advantage that the party who83* unseals the object does not need to keep track of the parameters (e.g., an84* IV) that were used to seal the object.85*86* </ul>87*88* @author Li Gong89* @author Jan Luehe90* @see Cipher91* @since 1.492*/9394public class SealedObject implements Serializable {9596@java.io.Serial97static final long serialVersionUID = 4482838265551344752L;9899/**100* The serialized object contents in encrypted format.101*102* @serial103*/104private byte[] encryptedContent = null;105106/**107* The algorithm that was used to seal this object.108*109* @serial110*/111private String sealAlg = null;112113/**114* The algorithm of the parameters used.115*116* @serial117*/118private String paramsAlg = null;119120/**121* The cryptographic parameters used by the sealing Cipher,122* encoded in the default format.123* <p>124* That is, <code>cipher.getParameters().getEncoded()</code>.125*126* @serial127*/128protected byte[] encodedParams = null;129130/**131* Constructs a SealedObject from any Serializable object.132*133* <p>The given object is serialized, and its serialized contents are134* encrypted using the given Cipher, which must be fully initialized.135*136* <p>Any algorithm parameters that may be used in the encryption137* operation are stored inside of the new <code>SealedObject</code>.138*139* @param object the object to be sealed; can be null.140* @param c the cipher used to seal the object.141*142* @exception NullPointerException if the given cipher is null.143* @exception IOException if an error occurs during serialization144* @exception IllegalBlockSizeException if the given cipher is a block145* cipher, no padding has been requested, and the total input length146* (i.e., the length of the serialized object contents) is not a multiple147* of the cipher's block size148*/149public SealedObject(Serializable object, Cipher c) throws IOException,150IllegalBlockSizeException151{152/*153* Serialize the object154*/155156// creating a stream pipe-line, from a to b157ByteArrayOutputStream b = new ByteArrayOutputStream();158ObjectOutput a = new ObjectOutputStream(b);159byte[] content;160try {161// write and flush the object content to byte array162a.writeObject(object);163a.flush();164content = b.toByteArray();165} finally {166a.close();167}168169/*170* Seal the object171*/172try {173this.encryptedContent = c.doFinal(content);174} catch (BadPaddingException ex) {175// if sealing is encryption only176// Should never happen??177} finally {178Arrays.fill(content, (byte)0);179}180181// Save the parameters182if (c.getParameters() != null) {183this.encodedParams = c.getParameters().getEncoded();184this.paramsAlg = c.getParameters().getAlgorithm();185}186187// Save the encryption algorithm188this.sealAlg = c.getAlgorithm();189}190191/**192* Constructs a SealedObject object from the passed-in SealedObject.193*194* @param so a SealedObject object195* @exception NullPointerException if the given sealed object is null.196*/197protected SealedObject(SealedObject so) {198this.encryptedContent = so.encryptedContent.clone();199this.sealAlg = so.sealAlg;200this.paramsAlg = so.paramsAlg;201if (so.encodedParams != null) {202this.encodedParams = so.encodedParams.clone();203} else {204this.encodedParams = null;205}206}207208/**209* Returns the algorithm that was used to seal this object.210*211* @return the algorithm that was used to seal this object.212*/213public final String getAlgorithm() {214return this.sealAlg;215}216217/**218* Retrieves the original (encapsulated) object.219*220* <p>This method creates a cipher for the algorithm that had been used in221* the sealing operation.222* If the default provider package provides an implementation of that223* algorithm, an instance of Cipher containing that implementation is used.224* If the algorithm is not available in the default package, other225* packages are searched.226* The Cipher object is initialized for decryption, using the given227* <code>key</code> and the parameters (if any) that had been used in the228* sealing operation.229*230* <p>The encapsulated object is unsealed and de-serialized, before it is231* returned.232*233* @param key the key used to unseal the object.234*235* @return the original object.236*237* @exception IOException if an error occurs during de-serialiazation.238* @exception ClassNotFoundException if an error occurs during239* de-serialiazation.240* @exception NoSuchAlgorithmException if the algorithm to unseal the241* object is not available.242* @exception InvalidKeyException if the given key cannot be used to unseal243* the object (e.g., it has the wrong algorithm).244* @exception NullPointerException if <code>key</code> is null.245*/246public final Object getObject(Key key)247throws IOException, ClassNotFoundException, NoSuchAlgorithmException,248InvalidKeyException249{250if (key == null) {251throw new NullPointerException("key is null");252}253254try {255return unseal(key, null);256} catch (NoSuchProviderException nspe) {257// we've already caught NoSuchProviderException's and converted258// them into NoSuchAlgorithmException's with details about259// the failing algorithm260throw new NoSuchAlgorithmException("algorithm not found");261} catch (IllegalBlockSizeException ibse) {262throw new InvalidKeyException(ibse.getMessage());263} catch (BadPaddingException bpe) {264throw new InvalidKeyException(bpe.getMessage());265}266}267268/**269* Retrieves the original (encapsulated) object.270*271* <p>The encapsulated object is unsealed (using the given Cipher,272* assuming that the Cipher is already properly initialized) and273* de-serialized, before it is returned.274*275* @param c the cipher used to unseal the object276*277* @return the original object.278*279* @exception NullPointerException if the given cipher is null.280* @exception IOException if an error occurs during de-serialiazation281* @exception ClassNotFoundException if an error occurs during282* de-serialiazation283* @exception IllegalBlockSizeException if the given cipher is a block284* cipher, no padding has been requested, and the total input length is285* not a multiple of the cipher's block size286* @exception BadPaddingException if the given cipher has been287* initialized for decryption, and padding has been specified, but288* the input data does not have proper expected padding bytes289*/290public final Object getObject(Cipher c)291throws IOException, ClassNotFoundException, IllegalBlockSizeException,292BadPaddingException293{294ObjectInput a = getExtObjectInputStream(c);295try {296Object obj = a.readObject();297return obj;298} finally {299a.close();300}301}302303/**304* Retrieves the original (encapsulated) object.305*306* <p>This method creates a cipher for the algorithm that had been used in307* the sealing operation, using an implementation of that algorithm from308* the given <code>provider</code>.309* The Cipher object is initialized for decryption, using the given310* <code>key</code> and the parameters (if any) that had been used in the311* sealing operation.312*313* <p>The encapsulated object is unsealed and de-serialized, before it is314* returned.315*316* @param key the key used to unseal the object.317* @param provider the name of the provider of the algorithm to unseal318* the object.319*320* @return the original object.321*322* @exception IllegalArgumentException if the given provider is null323* or empty.324* @exception IOException if an error occurs during de-serialiazation.325* @exception ClassNotFoundException if an error occurs during326* de-serialiazation.327* @exception NoSuchAlgorithmException if the algorithm to unseal the328* object is not available.329* @exception NoSuchProviderException if the given provider is not330* configured.331* @exception InvalidKeyException if the given key cannot be used to unseal332* the object (e.g., it has the wrong algorithm).333* @exception NullPointerException if <code>key</code> is null.334*/335public final Object getObject(Key key, String provider)336throws IOException, ClassNotFoundException, NoSuchAlgorithmException,337NoSuchProviderException, InvalidKeyException338{339if (key == null) {340throw new NullPointerException("key is null");341}342if (provider == null || provider.isEmpty()) {343throw new IllegalArgumentException("missing provider");344}345346try {347return unseal(key, provider);348} catch (IllegalBlockSizeException | BadPaddingException ex) {349throw new InvalidKeyException(ex.getMessage());350}351}352353354private Object unseal(Key key, String provider)355throws IOException, ClassNotFoundException, NoSuchAlgorithmException,356NoSuchProviderException, InvalidKeyException,357IllegalBlockSizeException, BadPaddingException358{359/*360* Create the parameter object.361*/362AlgorithmParameters params = null;363if (this.encodedParams != null) {364try {365if (provider != null)366params = AlgorithmParameters.getInstance(this.paramsAlg,367provider);368else369params = AlgorithmParameters.getInstance(this.paramsAlg);370371} catch (NoSuchProviderException nspe) {372if (provider == null) {373throw new NoSuchAlgorithmException(this.paramsAlg374+ " not found");375} else {376throw new NoSuchProviderException(nspe.getMessage());377}378}379params.init(this.encodedParams);380}381382/*383* Create and initialize the cipher.384*/385Cipher c;386try {387if (provider != null)388c = Cipher.getInstance(this.sealAlg, provider);389else390c = Cipher.getInstance(this.sealAlg);391} catch (NoSuchPaddingException nspe) {392throw new NoSuchAlgorithmException("Padding that was used in "393+ "sealing operation not "394+ "available");395} catch (NoSuchProviderException nspe) {396if (provider == null) {397throw new NoSuchAlgorithmException(this.sealAlg+" not found");398} else {399throw new NoSuchProviderException(nspe.getMessage());400}401}402403try {404if (params != null)405c.init(Cipher.DECRYPT_MODE, key, params);406else407c.init(Cipher.DECRYPT_MODE, key);408} catch (InvalidAlgorithmParameterException iape) {409// this should never happen, because we use the exact same410// parameters that were used in the sealing operation411throw new RuntimeException(iape.getMessage());412}413414ObjectInput a = getExtObjectInputStream(c);415try {416Object obj = a.readObject();417return obj;418} finally {419a.close();420}421}422423/**424* Restores the state of the SealedObject from a stream.425*426* @param s the object input stream.427* @throws IOException if an I/O error occurs428* @throws ClassNotFoundException if a serialized class cannot be loaded429* @throws NullPointerException if s is null430*/431@java.io.Serial432private void readObject(java.io.ObjectInputStream s)433throws java.io.IOException, ClassNotFoundException434{435s.defaultReadObject();436if (encryptedContent != null)437encryptedContent = encryptedContent.clone();438if (encodedParams != null)439encodedParams = encodedParams.clone();440}441442// This method is also called inside SealedObjectForKeyProtector.java.443private ObjectInputStream getExtObjectInputStream(Cipher c)444throws BadPaddingException, IllegalBlockSizeException, IOException {445446byte[] content = c.doFinal(this.encryptedContent);447ByteArrayInputStream b = new ByteArrayInputStream(content);448return new extObjectInputStream(b);449}450451static {452SharedSecrets.setJavaxCryptoSealedObjectAccess((obj,c) -> obj.getExtObjectInputStream(c));453}454}455456final class extObjectInputStream extends ObjectInputStream {457extObjectInputStream(InputStream in)458throws IOException, StreamCorruptedException {459super(in);460}461462protected Class<?> resolveClass(ObjectStreamClass v)463throws IOException, ClassNotFoundException464{465466try {467/*468* Calling the super.resolveClass() first469* will let us pick up bug fixes in the super470* class (e.g., 4171142).471*/472return super.resolveClass(v);473} catch (ClassNotFoundException cnfe) {474/*475* This is a workaround for bug 4224921.476*/477ClassLoader loader = Thread.currentThread().getContextClassLoader();478if (loader == null) {479loader = ClassLoader.getSystemClassLoader();480if (loader == null) {481throw new ClassNotFoundException(v.getName());482}483}484485return Class.forName(v.getName(), false, loader);486}487}488}489490491