From 45a7ae26f0172c636e42b530561eb11efa0083d5 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 15 May 2023 08:32:34 +0200 Subject: feat(utils): optimize custom Java serialization implementation --- .../core/impl/utils/EaafObjectInputStream.java | 82 ++++++++++++++++++---- .../core/impl/utils/EaafSerializationUtils.java | 60 ++++++++++++++-- 2 files changed, 121 insertions(+), 21 deletions(-) (limited to 'eaaf_core_utils/src/main') diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java index e15c7a37..1924e165 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java @@ -5,35 +5,87 @@ import java.io.InputStream; import java.io.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; -import java.util.List; +import java.util.Set; import javax.annotation.Nonnull; +/** + * Java Object stream implementation with some harding features. + * + * @author tlenz + * + */ public class EaafObjectInputStream extends ObjectInputStream { - private List allowedClassNames; - + private final Set> allowedClassNames; + private final Class firstClassType; + private final Mode modeOfOperation; + private int objectDeep = 0; + /** * Object input-stream with internal class validation. - * - * @param is Inputstream to deserialize. - * @param classNames Whitelisted classnames + * + * @param is Inputstream to deserialize. + * @param initalClassType First class type that was found + * @param classes Whitelisted classnames + * @param mode Operation mode for allowed class checking * @throws IOException In case of an error - */ - public EaafObjectInputStream(@Nonnull InputStream is, @Nonnull List classNames) throws IOException { + */ + public EaafObjectInputStream(@Nonnull InputStream is, @Nonnull Set> classes, + Class initalClassType, Mode mode) + throws IOException { super(is); - this.allowedClassNames = classNames; - + this.allowedClassNames = classes; + this.firstClassType = initalClassType; + this.modeOfOperation = mode; + } - //Only deserialize instances of our expected class + // Only deserialize instances of our expected class @Override protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { - if (!allowedClassNames.contains(desc.getName())) { - throw new InvalidClassException("Unauthorized deserialization attempt: {}",desc.getName()); - + if (Mode.STRICT.equals(modeOfOperation) && !isValidClass(desc.getName())) { + throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName()); + + } else if (Mode.TYPE_SPECIFIC.equals(modeOfOperation)) { + final Class clazz = super.resolveClass(desc); + if (objectDeep == 0 && !firstClassType.isAssignableFrom(clazz)) { + throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName()); + + } else if (objectDeep > 0 + && !(isValidClassType(clazz) || Object.class.getName().equals(desc.getName()))) { + throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName()); + + } else { + objectDeep++; + return clazz; + + } + + } else { + return super.resolveClass(desc); + } - return super.resolveClass(desc); + } + + private boolean isValidClass(String classToDeserialize) { + return allowedClassNames.stream() + .filter(el -> el.getName().equals(classToDeserialize)) + .findFirst() + .isPresent(); + + } + + private boolean isValidClassType(Class clazzToCheck) { + return allowedClassNames.stream() + .filter(el -> el.isAssignableFrom(clazzToCheck)) + .findFirst() + .isPresent(); + + } + + enum Mode { + STRICT, TYPE_SPECIFIC } } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java index e15c6800..efb4c9be 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java @@ -5,10 +5,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.util.List; +import java.util.Set; import org.springframework.lang.Nullable; +import at.gv.egiz.eaaf.core.impl.utils.EaafObjectInputStream.Mode; + public class EaafSerializationUtils { private EaafSerializationUtils() { @@ -42,20 +44,65 @@ public class EaafSerializationUtils { } /** - * Deserialize the byte array into an object. + * Deserialize the byte array into an object with strict allow-list of classes. + * + *

+ * Allow all classes that exact match to elements in allow-list. + *

* - * @param bytes a serialized object - * @param allowedClassName List of classnames that are allowed for deserialization + * @param bytes a serialized object + * @param allowedClassName List of classnames that are explicit allowed for + * deserialization * @return the result of deserializing the bytes */ @Nullable - public static Object deserialize(@Nullable byte[] bytes, List allowedClassName) { + public static Object strictDeserialize(@Nullable byte[] bytes, Set> allowedClassName) { + if (bytes == null) { + return null; + + } + + try (ObjectInputStream ois = new EaafObjectInputStream(new ByteArrayInputStream(bytes), + allowedClassName, null, Mode.STRICT)) { + return ois.readObject(); + + } catch (final IOException ex) { + throw new IllegalArgumentException("Failed to deserialize object", ex); + + } catch (final ClassNotFoundException ex) { + throw new IllegalStateException("Failed to deserialize object type", ex); + + } + } + + /** + * Deserialize the byte array into an object with type-specific allow-list of + * classes. + * + *

+ * Allow all classes that the same or a super-type of elements in + * allow-list.
+ * Hint: Do NOT set {@link Object} as allowed class, because any class is + * an super-type of {@link Object}. This method implementation allows + * {@link Object} as explicit type with strict check-mode. + *

+ * + * @param bytes a serialized object + * @param allowedClassName List of classes that are explicit allowed for + * deserialization + * @param initalClassType First / Initial class type that are required + * @return the result of deserializing the bytes + */ + @Nullable + public static Object typeSpecificDeserialize(@Nullable byte[] bytes, Set> allowedClassName, + Class initalClassType) { if (bytes == null) { return null; } - try (ObjectInputStream ois = new EaafObjectInputStream(new ByteArrayInputStream(bytes), allowedClassName)) { + try (ObjectInputStream ois = new EaafObjectInputStream(new ByteArrayInputStream(bytes), + allowedClassName, initalClassType, Mode.TYPE_SPECIFIC)) { return ois.readObject(); } catch (final IOException ex) { @@ -66,4 +113,5 @@ public class EaafSerializationUtils { } } + } -- cgit v1.2.3