Merge "Fix SealedObject.readObject."
diff --git a/luni/src/main/java/javax/crypto/SealedObject.java b/luni/src/main/java/javax/crypto/SealedObject.java
index c9c1534..cfb970b 100644
--- a/luni/src/main/java/javax/crypto/SealedObject.java
+++ b/luni/src/main/java/javax/crypto/SealedObject.java
@@ -33,12 +33,12 @@
/**
* A {@code SealedObject} is a wrapper around a {@code serializable} object
* instance and encrypts it using a cryptographic cipher.
- * <p>
- * Since a {@code SealedObject} instance is a serializable object itself it can
+ *
+ * <p>Since a {@code SealedObject} instance is serializable it can
* either be stored or transmitted over an insecure channel.
- * <p>
- * The wrapped object can later be decrypted (unsealed) using the corresponding
- * key and then be deserialized to retrieve the original object.The sealed
+ *
+ * <p>The wrapped object can later be decrypted (unsealed) using the corresponding
+ * key and then be deserialized to retrieve the original object. The sealed
* object itself keeps track of the cipher and corresponding parameters.
*/
public class SealedObject implements Serializable {
@@ -46,19 +46,25 @@
private static final long serialVersionUID = 4482838265551344752L;
/**
- * The {@link AlgorithmParameters} in encoded format.
+ * The cipher's {@link AlgorithmParameters} in encoded format.
+ * Equivalent to {@code cipher.getParameters().getEncoded()},
+ * or null if the cipher did not use any parameters.
*/
protected byte[] encodedParams;
+
private byte[] encryptedContent;
private String sealAlg;
private String paramsAlg;
- private void readObject(ObjectInputStream s)
- throws IOException, ClassNotFoundException {
- encodedParams = (byte []) s.readUnshared();
- encryptedContent = (byte []) s.readUnshared();
- sealAlg = (String) s.readUnshared();
- paramsAlg = (String) s.readUnshared();
+ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
+ // We do unshared reads here to ensure we have our own clones of the byte[]s.
+ encodedParams = (byte[]) s.readUnshared();
+ encryptedContent = (byte[]) s.readUnshared();
+ // These are regular shared reads because the algorithms used by a given stream are
+ // almost certain to the be same for each object, and String is immutable anyway,
+ // so there's no security concern about sharing.
+ sealAlg = (String) s.readObject();
+ paramsAlg = (String) s.readObject();
}
/**
diff --git a/luni/src/test/java/libcore/util/SerializationTester.java b/luni/src/test/java/libcore/util/SerializationTester.java
new file mode 100644
index 0000000..59f319c
--- /dev/null
+++ b/luni/src/test/java/libcore/util/SerializationTester.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+import junit.framework.AssertionFailedError;
+
+public class SerializationTester<T> {
+ private final String golden;
+ private final T value;
+
+ public SerializationTester(T value, String golden) {
+ this.golden = golden;
+ this.value = value;
+ }
+
+ /**
+ * Returns true if {@code a} and {@code b} are equal. Override this if
+ * {@link Object#equals} isn't appropriate or sufficient for this tester's
+ * value type.
+ */
+ protected boolean equals(T a, T b) {
+ return a.equals(b);
+ }
+
+ /**
+ * Verifies that {@code deserialized} is valid. Implementations of this
+ * method may mutate {@code deserialized}.
+ */
+ protected void verify(T deserialized) throws Exception {}
+
+ public void test() {
+ try {
+ if (golden == null || golden.length() == 0) {
+ fail("No golden value supplied! Consider using this: "
+ + hexEncode(serialize(value)));
+ }
+
+ @SuppressWarnings("unchecked") // deserialize should return the proper type
+ T deserialized = (T) deserialize(hexDecode(golden));
+ assertTrue("User-constructed value doesn't equal deserialized golden value",
+ equals(value, deserialized));
+
+ @SuppressWarnings("unchecked") // deserialize should return the proper type
+ T reserialized = (T) deserialize(serialize(value));
+ assertTrue("User-constructed value doesn't equal itself, reserialized",
+ equals(value, reserialized));
+
+ // just a sanity check! if this fails, verify() is probably broken
+ verify(value);
+ verify(deserialized);
+ verify(reserialized);
+
+ } catch (Exception e) {
+ Error failure = new AssertionFailedError();
+ failure.initCause(e);
+ throw failure;
+ }
+ }
+
+ private static byte[] serialize(Object object) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ObjectOutputStream(out).writeObject(object);
+ return out.toByteArray();
+ }
+
+ private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
+ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ Object result = in.readObject();
+ assertEquals(-1, in.read());
+ return result;
+ }
+
+ private static String hexEncode(byte[] bytes) {
+ StringBuilder result = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ result.append(String.format("%02x", b));
+ }
+ return result.toString();
+ }
+
+ private static byte[] hexDecode(String s) {
+ byte[] result = new byte[s.length() / 2];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = (byte) Integer.parseInt(s.substring(i*2, i*2 + 2), 16);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a serialized-and-deserialized copy of {@code object}.
+ */
+ public static Object reserialize(Object object) throws IOException, ClassNotFoundException {
+ return deserialize(serialize(object));
+ }
+
+ public static String serializeHex(Object object) throws IOException {
+ return hexEncode(serialize(object));
+ }
+
+ public static Object deserializeHex(String hex) throws IOException, ClassNotFoundException {
+ return deserialize(hexDecode(hex));
+ }
+}
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/SealedObjectTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/SealedObjectTest.java
index b3b2931..3ea57bf 100644
--- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/SealedObjectTest.java
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/SealedObjectTest.java
@@ -33,6 +33,7 @@
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchProviderException;
+import java.util.ArrayList;
import java.util.Arrays;
import javax.crypto.Cipher;
@@ -43,6 +44,8 @@
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import libcore.util.SerializationTester;
+
/**
*/
public class SealedObjectTest extends TestCase {
@@ -291,4 +294,23 @@
}
}
+ // http://code.google.com/p/android/issues/detail?id=4834
+ public void testDeserialization() throws Exception {
+ // (Boilerplate so we can create SealedObject instances.)
+ KeyGenerator kg = KeyGenerator.getInstance("DES");
+ Key key = kg.generateKey();
+ Cipher cipher = Cipher.getInstance("DES");
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+
+ // Incorrect use of readUnshared meant you couldn't have two SealedObjects
+ // with the same algorithm or parameters algorithm...
+ ArrayList<SealedObject> sealedObjects = new ArrayList<SealedObject>();
+ for (int i = 0; i < 10; ++i) {
+ sealedObjects.add(new SealedObject("hello", cipher));
+ }
+ String serializedForm = SerializationTester.serializeHex(sealedObjects);
+
+ // ...so this would throw "java.io.InvalidObjectException: Unshared read of back reference".
+ SerializationTester.deserializeHex(serializedForm);
+ }
}