This is an automated email from the ASF dual-hosted git repository.

HTHou pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new 52eb36f1e Improve encryption metadata validation (#925)
52eb36f1e is described below

commit 52eb36f1ebafa953c907a1dbf2c4a0ad96306bae
Author: Haonan <[email protected]>
AuthorDate: Fri Aug 28 17:16:00 2026 +0800

    Improve encryption metadata validation (#925)
---
 .../org/apache/tsfile/i18n/messages.properties     |  9 +++
 .../org/apache/tsfile/i18n/messages_zh.properties  |  9 +++
 .../org/apache/tsfile/encrypt/EncryptUtils.java    | 65 ++++++++++++++++----
 .../java/org/apache/tsfile/encrypt/IDecryptor.java |  6 +-
 .../java/org/apache/tsfile/encrypt/IEncryptor.java |  6 +-
 .../tsfile/file/metadata/TsFileMetadata.java       |  3 +
 .../org/apache/tsfile/encrypt/EncryptTest.java     | 71 ++++++++++++++++++++++
 .../tsfile/file/metadata/TsFileMetadataTest.java   | 14 +++++
 8 files changed, 164 insertions(+), 19 deletions(-)

diff --git 
a/java/common/src/main/resources/org/apache/tsfile/i18n/messages.properties 
b/java/common/src/main/resources/org/apache/tsfile/i18n/messages.properties
index ffea7c054..036b8cb69 100644
--- a/java/common/src/main/resources/org/apache/tsfile/i18n/messages.properties
+++ b/java/common/src/main/resources/org/apache/tsfile/i18n/messages.properties
@@ -1069,6 +1069,15 @@ error.external.comparator_chain_empty = ComparatorChains 
must contain at least o
 
 # === encrypt ===
 
+# EncryptUtils — unsupported encryption type
+error.encrypt.type_not_supported = Unsupported encryption type: %1$s
+
+# EncryptUtils.getSecondKeyFromStr — key exceeds the supported length (length 
arg)
+error.encrypt.key_too_long = Encryption key exceeds the maximum length of %1$s 
bytes
+
+# EncryptUtils.getSecondKeyFromStr — null key
+error.encrypt.key_null = Encryption key must not be null
+
 # IEncryptor.getEncryptor — class not found (class name arg)
 error.encrypt.encryptor_class_not_found = Get encryptor class failed, class 
not found: %1$s
 
diff --git 
a/java/common/src/main/resources/org/apache/tsfile/i18n/messages_zh.properties 
b/java/common/src/main/resources/org/apache/tsfile/i18n/messages_zh.properties
index 453977c76..5b2eb479c 100644
--- 
a/java/common/src/main/resources/org/apache/tsfile/i18n/messages_zh.properties
+++ 
b/java/common/src/main/resources/org/apache/tsfile/i18n/messages_zh.properties
@@ -1069,6 +1069,15 @@ error.external.comparator_chain_empty = ComparatorChain 
必须至少包含一个
 
 # === encrypt ===
 
+# EncryptUtils — unsupported encryption type
+error.encrypt.type_not_supported = 不支持的加密类型: %1$s
+
+# EncryptUtils.getSecondKeyFromStr — key exceeds the supported length (length 
arg)
+error.encrypt.key_too_long = 加密密钥长度超过支持的最大值 %1$s 字节
+
+# EncryptUtils.getSecondKeyFromStr — null key
+error.encrypt.key_null = 加密密钥不能为 null
+
 # IEncryptor.getEncryptor — class not found (class name arg)
 error.encrypt.encryptor_class_not_found = 获取 encryptor class 失败,class not 
found: %1$s
 
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/EncryptUtils.java 
b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/EncryptUtils.java
index 42ccec8f7..4018406c5 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/EncryptUtils.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/EncryptUtils.java
@@ -44,6 +44,10 @@ public class EncryptUtils {
 
   private static final String encryptClassPrefix = 
"org.apache.tsfile.encrypt.";
 
+  private static final int MAX_SECOND_KEY_LENGTH = 1024;
+
+  private static final int MAX_SECOND_KEY_STRING_LENGTH = 
MAX_SECOND_KEY_LENGTH * 5;
+
   private static volatile String normalKeyStr;
 
   private static volatile EncryptParameter encryptParam;
@@ -69,16 +73,36 @@ public class EncryptUtils {
   }
 
   public static String getEncryptClass(String encryptType) {
-    String classNameRegex = "^(\\p{Alpha}\\w*)(\\.\\p{Alpha}\\w+)+$";
-    if (IEncrypt.encryptTypeToClassMap.containsKey(encryptType)) {
-      return IEncrypt.encryptTypeToClassMap.get(encryptType);
-    } else if (encryptType.matches(classNameRegex)) {
-      IEncrypt.encryptTypeToClassMap.put(encryptType, encryptType);
-      return encryptType;
-    } else {
-      IEncrypt.encryptTypeToClassMap.put(encryptType, encryptClassPrefix + 
encryptType);
-      return encryptClassPrefix + encryptType;
+    if (encryptType == null || encryptType.isEmpty()) {
+      throw new EncryptException(
+          Messages.format("error.encrypt.type_not_supported", 
String.valueOf(encryptType)));
+    }
+    String mappedClassName = IEncrypt.encryptTypeToClassMap.get(encryptType);
+    if (mappedClassName != null) {
+      return validateEncryptClassName(mappedClassName, encryptType);
+    }
+    String className =
+        encryptType.startsWith(encryptClassPrefix) ? encryptType : 
encryptClassPrefix + encryptType;
+    String previousClassName = 
IEncrypt.encryptTypeToClassMap.putIfAbsent(encryptType, className);
+    return validateEncryptClassName(
+        previousClassName == null ? className : previousClassName, 
encryptType);
+  }
+
+  private static String validateEncryptClassName(String className, String 
encryptType) {
+    if (!className.startsWith(encryptClassPrefix)) {
+      throw new 
EncryptException(Messages.format("error.encrypt.type_not_supported", 
encryptType));
+    }
+    return className;
+  }
+
+  static Class<? extends IEncrypt> loadEncryptClass(String encryptType)
+      throws ClassNotFoundException {
+    Class<?> encryptClass =
+        Class.forName(getEncryptClass(encryptType), false, 
EncryptUtils.class.getClassLoader());
+    if (!IEncrypt.class.isAssignableFrom(encryptClass)) {
+      throw new 
EncryptException(Messages.format("error.encrypt.type_not_supported", 
encryptType));
     }
+    return encryptClass.asSubclass(IEncrypt.class);
   }
 
   public static byte[] getEncryptKeyFromToken(String token, byte[] salt) {
@@ -291,11 +315,11 @@ public class EncryptUtils {
       if (IEncrypt.encryptMap.containsKey(className)) {
         return ((IEncrypt) 
IEncrypt.encryptMap.get(className).newInstance(dataEncryptKey));
       }
-      Class<?> encryptTypeClass = Class.forName(className);
-      java.lang.reflect.Constructor<?> constructor =
+      Class<? extends IEncrypt> encryptTypeClass = 
loadEncryptClass(encryptType);
+      java.lang.reflect.Constructor<? extends IEncrypt> constructor =
           encryptTypeClass.getDeclaredConstructor(byte[].class);
       IEncrypt.encryptMap.put(className, constructor);
-      return ((IEncrypt) constructor.newInstance(dataEncryptKey));
+      return constructor.newInstance(dataEncryptKey);
     } catch (ClassNotFoundException e) {
       throw new EncryptException(
           Messages.format("error.encrypt.encrypt_class_not_found", 
encryptType), e);
@@ -331,11 +355,26 @@ public class EncryptUtils {
   }
 
   public static byte[] getSecondKeyFromStr(String str) {
-    String[] strArray = str.split(",");
+    if (str == null) {
+      throw new EncryptException(Messages.get("error.encrypt.key_null"));
+    }
+    validateSecondKeyStringLength(str.length());
+    String[] strArray = str.split(",", MAX_SECOND_KEY_LENGTH + 1);
+    if (strArray.length > MAX_SECOND_KEY_LENGTH) {
+      throw new EncryptException(
+          Messages.format("error.encrypt.key_too_long", 
MAX_SECOND_KEY_LENGTH));
+    }
     byte[] key = new byte[strArray.length];
     for (int i = 0; i < strArray.length; i++) {
       key[i] = Byte.parseByte(strArray[i]);
     }
     return key;
   }
+
+  public static void validateSecondKeyStringLength(int length) {
+    if (length < 0 || length > MAX_SECOND_KEY_STRING_LENGTH) {
+      throw new EncryptException(
+          Messages.format("error.encrypt.key_too_long", 
MAX_SECOND_KEY_LENGTH));
+    }
+  }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IDecryptor.java 
b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IDecryptor.java
index b906e5b72..de7735dae 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IDecryptor.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IDecryptor.java
@@ -39,11 +39,11 @@ public interface IDecryptor {
       if (IEncrypt.encryptMap.containsKey(className)) {
         return ((IEncrypt) 
IEncrypt.encryptMap.get(className).newInstance(key)).getDecryptor();
       }
-      Class<?> encryptClass = Class.forName(className);
-      java.lang.reflect.Constructor<?> constructor =
+      Class<? extends IEncrypt> encryptClass = 
EncryptUtils.loadEncryptClass(type);
+      java.lang.reflect.Constructor<? extends IEncrypt> constructor =
           encryptClass.getDeclaredConstructor(byte[].class);
       IEncrypt.encryptMap.put(className, constructor);
-      return ((IEncrypt) constructor.newInstance(key)).getDecryptor();
+      return constructor.newInstance(key).getDecryptor();
     } catch (ClassNotFoundException e) {
       throw new EncryptException(
           Messages.format("error.encrypt.decryptor_class_not_found", type), e);
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IEncryptor.java 
b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IEncryptor.java
index 2c0b6cf2e..7d60ecfc4 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IEncryptor.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/encrypt/IEncryptor.java
@@ -39,11 +39,11 @@ public interface IEncryptor {
       if (IEncrypt.encryptMap.containsKey(className)) {
         return ((IEncrypt) 
IEncrypt.encryptMap.get(className).newInstance(key)).getEncryptor();
       }
-      Class<?> encryptClass = Class.forName(className);
-      java.lang.reflect.Constructor<?> constructor =
+      Class<? extends IEncrypt> encryptClass = 
EncryptUtils.loadEncryptClass(type);
+      java.lang.reflect.Constructor<? extends IEncrypt> constructor =
           encryptClass.getDeclaredConstructor(byte[].class);
       IEncrypt.encryptMap.put(className, constructor);
-      return ((IEncrypt) constructor.newInstance(key)).getEncryptor();
+      return constructor.newInstance(key).getEncryptor();
     } catch (ClassNotFoundException e) {
       throw new EncryptException(
           Messages.format("error.encrypt.encryptor_class_not_found", type), e);
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/file/metadata/TsFileMetadata.java 
b/java/tsfile/src/main/java/org/apache/tsfile/file/metadata/TsFileMetadata.java
index 7caae06b2..95759ae20 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/file/metadata/TsFileMetadata.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/file/metadata/TsFileMetadata.java
@@ -122,6 +122,9 @@ public class TsFileMetadata {
       for (int i = 0; i < propertiesSize; i++) {
         String key = ReadWriteIOUtils.readVarIntString(buffer);
         int valueSize = ReadWriteForEncodingUtils.readVarInt(buffer);
+        if ("encryptKey".equals(key) && valueSize >= 0) {
+          EncryptUtils.validateSecondKeyStringLength(valueSize);
+        }
         byte[] value = null;
         if (valueSize >= 0) {
           value = new byte[valueSize];
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/encrypt/EncryptTest.java 
b/java/tsfile/src/test/java/org/apache/tsfile/encrypt/EncryptTest.java
index 51b8f8a07..9d60ff437 100644
--- a/java/tsfile/src/test/java/org/apache/tsfile/encrypt/EncryptTest.java
+++ b/java/tsfile/src/test/java/org/apache/tsfile/encrypt/EncryptTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tsfile.encrypt;
 
+import org.apache.tsfile.exception.encrypt.EncryptException;
 import org.apache.tsfile.file.metadata.enums.EncryptionType;
 
 import org.junit.After;
@@ -26,9 +27,12 @@ import org.junit.Test;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 
 public class EncryptTest {
   private final String inputString = "AES, a fast encryptor/decryptor.";
@@ -74,6 +78,73 @@ public class EncryptTest {
     assertEquals(encryptor2.getEncryptionType(), EncryptionType.UNENCRYPTED);
   }
 
+  @Test
+  public void GetEncryptorDoesNotLoadExternalClassName() {
+    assertThrows(
+        EncryptException.class,
+        () ->
+            IEncryptor.getEncryptor(
+                "java.io.ByteArrayInputStream", 
key.getBytes(StandardCharsets.UTF_8)));
+  }
+
+  @Test
+  public void GetEncryptorUsesRegisteredClassMapping() {
+    String type = "CUSTOM";
+    IEncrypt.encryptTypeToClassMap.put(type, UNENCRYPTED.class.getName());
+    try {
+      IEncryptor encryptor = IEncryptor.getEncryptor(type, 
key.getBytes(StandardCharsets.UTF_8));
+      assertEquals(EncryptionType.UNENCRYPTED, encryptor.getEncryptionType());
+    } finally {
+      IEncrypt.encryptTypeToClassMap.remove(type);
+    }
+  }
+
+  @Test
+  public void GetEncryptorRejectsExternalRegisteredClassMapping() {
+    String type = "CUSTOM_EXTERNAL";
+    IEncrypt.encryptTypeToClassMap.put(type, "java.io.ByteArrayInputStream");
+    try {
+      assertThrows(
+          EncryptException.class,
+          () -> IEncryptor.getEncryptor(type, 
key.getBytes(StandardCharsets.UTF_8)));
+    } finally {
+      IEncrypt.encryptTypeToClassMap.remove(type);
+    }
+  }
+
+  @Test
+  public void GetEncryptorValidatesEncryptionClassBeforeInstantiation() {
+    NonEncryptClass.constructorCalled = false;
+
+    assertThrows(
+        EncryptException.class,
+        () ->
+            IEncryptor.getEncryptor(
+                NonEncryptClass.class.getName(), 
key.getBytes(StandardCharsets.UTF_8)));
+
+    assertFalse(NonEncryptClass.constructorCalled);
+  }
+
+  @Test
+  public void GetSecondKeyFromStrLimitsKeyLength() {
+    String oversizedKey = String.join(",", Collections.nCopies(1025, "0"));
+
+    assertThrows(EncryptException.class, () -> 
EncryptUtils.getSecondKeyFromStr(oversizedKey));
+  }
+
+  @Test
+  public void GetSecondKeyFromStrRejectsNullKey() {
+    assertThrows(EncryptException.class, () -> 
EncryptUtils.getSecondKeyFromStr(null));
+  }
+
+  public static class NonEncryptClass {
+    private static boolean constructorCalled;
+
+    public NonEncryptClass(byte[] key) {
+      constructorCalled = true;
+    }
+  }
+
   @Test
   public void HexStringTransverse() {
     byte[] SboxTable = {
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/file/metadata/TsFileMetadataTest.java
 
b/java/tsfile/src/test/java/org/apache/tsfile/file/metadata/TsFileMetadataTest.java
index 6488acde9..2f4294299 100644
--- 
a/java/tsfile/src/test/java/org/apache/tsfile/file/metadata/TsFileMetadataTest.java
+++ 
b/java/tsfile/src/test/java/org/apache/tsfile/file/metadata/TsFileMetadataTest.java
@@ -21,6 +21,7 @@ package org.apache.tsfile.file.metadata;
 import org.apache.tsfile.common.conf.TSFileConfig;
 import org.apache.tsfile.compatibility.DeserializeConfig;
 import org.apache.tsfile.constant.TestConstant;
+import org.apache.tsfile.exception.encrypt.EncryptException;
 import org.apache.tsfile.file.metadata.utils.TestHelper;
 import org.apache.tsfile.file.metadata.utils.Utils;
 
@@ -65,6 +66,19 @@ public class TsFileMetadataTest {
     Assert.assertTrue(Utils.isFileMetaDataEqual(tsfMetaData, readMetaData));
   }
 
+  @Test
+  public void testRejectOversizedEncryptKeyDuringDeserialization() {
+    TsFileMetadata tsfMetaData = TestHelper.createSimpleFileMetaData();
+    tsfMetaData.addProperty("encryptLevel", 
"1".getBytes(TSFileConfig.STRING_CHARSET));
+    tsfMetaData.addProperty(
+        "encryptType",
+        
"org.apache.tsfile.encrypt.UNENCRYPTED".getBytes(TSFileConfig.STRING_CHARSET));
+    tsfMetaData.addProperty("encryptKey", new byte[5121]);
+    serialized(tsfMetaData);
+
+    Assert.assertThrows(EncryptException.class, this::deSerialized);
+  }
+
   private TsFileMetadata deSerialized() {
     FileInputStream fileInputStream = null;
     TsFileMetadata metaData = null;

Reply via email to