From 6587029851771a52004ad3e91d2ebc068ffb78d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=96=BB=E4=B8=96=E6=96=87?= <61678924+YuShiwen@users.noreply.github.com> Date: Wed, 27 Dec 2023 11:08:23 +0800 Subject: [PATCH] [ISSUE #11544] Supplementary single test, when the prefix of dataId is cipher. (#11546) * [ISSUE #11544] Supplementary single test, when the prefix of dataId is cipher. * [ISSUE #11544] Supplementary single test, when the prefix of dataId is cipher. Adjusting code format. * [ISSUE #11544] Supplementary single test, when the prefix of dataId is cipher. Change static code block to @Before annotation. * [ISSUE #11544] Supplementary single test, when the prefix of dataId is cipher. run ci. --- .../impl/ConfigEncryptionFilterTest1.java | 201 +++++++++++++++ .../handler/EncryptionAesHandlerTest.java | 235 ++++++++++++++++++ .../config/AbstractConfigAPI_CITCase.java | 126 ++++++++++ 3 files changed, 562 insertions(+) create mode 100644 client/src/test/java/com/alibaba/nacos/client/config/filter/impl/ConfigEncryptionFilterTest1.java create mode 100644 plugin/encryption/src/test/java/com/alibaba/nacos/plugin/encryption/handler/EncryptionAesHandlerTest.java diff --git a/client/src/test/java/com/alibaba/nacos/client/config/filter/impl/ConfigEncryptionFilterTest1.java b/client/src/test/java/com/alibaba/nacos/client/config/filter/impl/ConfigEncryptionFilterTest1.java new file mode 100644 index 000000000..34566ebf6 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/config/filter/impl/ConfigEncryptionFilterTest1.java @@ -0,0 +1,201 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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 com.alibaba.nacos.client.config.filter.impl; + +import com.alibaba.nacos.api.config.filter.IConfigFilterChain; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager; +import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * AES encryption algorithm testing dataId with prefix cipher. + * + * @Author shiwenyu + * @Date 2023/12/23 9:45 PM + * @Version 1.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ConfigEncryptionFilterTest1 { + + private ConfigEncryptionFilter configEncryptionFilter; + + private EncryptionPluginService mockEncryptionPluginService; + + @Mock + private IConfigFilterChain iConfigFilterChain; + + @Before + public void setUp() throws Exception { + mockEncryptionPluginService = new EncryptionPluginService() { + + private static final String ALGORITHM = "AES"; + + private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding"; + + // 随机生成密钥-用来加密数据内容 + private final String contentKey = generateKey(); + + // 随机生成密钥-用来加密密钥 + private final String theKeyOfContentKey = generateKey(); + + private String generateKey() { + SecureRandom secureRandom = new SecureRandom(); + KeyGenerator keyGenerator; + try { + keyGenerator = KeyGenerator.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + keyGenerator.init(128, secureRandom); + SecretKey secretKey = keyGenerator.generateKey(); + byte[] keyBytes = secretKey.getEncoded(); + return Base64.encodeBase64String(keyBytes); + } + + @Override + public String encrypt(String secretKey, String content) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, content, secretKey)); + } + + @Override + public String decrypt(String secretKey, String content) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(content, secretKey); + } + + @Override + public String generateSecretKey() { + return contentKey; + } + + @Override + public String algorithmName() { + return ALGORITHM.toLowerCase(); + } + + @Override + public String encryptSecretKey(String secretKey) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, generateSecretKey(), theKeyOfContentKey)); + } + + @Override + public String decryptSecretKey(String secretKey) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(secretKey, theKeyOfContentKey); + } + + private byte[] aes(int mode, String content, String key) { + try { + return aesBytes(mode, content.getBytes(StandardCharsets.UTF_8), key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] aesBytes(int mode, byte[] content, String key) { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); + Cipher cipher = null; + try { + cipher = Cipher.getInstance(AES_PKCS5P); + cipher.init(mode, keySpec); + return cipher.doFinal(content); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String aesDecrypt(String content, String key) { + byte[] bytes = aesBytes(Cipher.DECRYPT_MODE, Base64.decodeBase64(content), key); + return new String(bytes, StandardCharsets.UTF_8); + } + }; + EncryptionPluginManager.join(mockEncryptionPluginService); + + configEncryptionFilter = new ConfigEncryptionFilter(); + } + + @Test + public void testDoFilterEncryptedData() throws NacosException { + String dataId = "cipher-aes-test"; + String content = "nacos"; + final String encryptionContent = mockEncryptionPluginService.encrypt(mockEncryptionPluginService.generateSecretKey(), content); + final String theKeyOfContentKey = mockEncryptionPluginService.encryptSecretKey(mockEncryptionPluginService.generateSecretKey()); + + ConfigRequest configRequest = new ConfigRequest(); + configRequest.setDataId(dataId); + configRequest.setContent(content); + configEncryptionFilter.doFilter(configRequest, null, iConfigFilterChain); + Assert.assertEquals(configRequest.getContent(), encryptionContent); + Assert.assertEquals(configRequest.getEncryptedDataKey(), theKeyOfContentKey); + + ConfigResponse configResponse = new ConfigResponse(); + configResponse.setDataId(dataId); + configResponse.setContent(encryptionContent); + configResponse.setEncryptedDataKey(theKeyOfContentKey); + configEncryptionFilter.doFilter(null, configResponse, iConfigFilterChain); + Assert.assertEquals(configResponse.getContent(), content); + Assert.assertEquals(configResponse.getEncryptedDataKey(), mockEncryptionPluginService.generateSecretKey()); + } + + @Test + public void testDoFilter() throws NacosException { + String dataId = "test"; + String content = "nacos"; + + ConfigRequest configRequest = new ConfigRequest(); + configRequest.setDataId(dataId); + configRequest.setContent(content); + configEncryptionFilter.doFilter(configRequest, null, iConfigFilterChain); + Assert.assertEquals(configRequest.getContent(), content); + Assert.assertEquals(configRequest.getEncryptedDataKey(), ""); + + ConfigResponse configResponse = new ConfigResponse(); + configResponse.setDataId(dataId); + configResponse.setContent(content); + configResponse.setEncryptedDataKey(""); + configEncryptionFilter.doFilter(null, configResponse, iConfigFilterChain); + Assert.assertEquals(configResponse.getContent(), content); + Assert.assertEquals(configResponse.getEncryptedDataKey(), ""); + } + + @Test + public void testGetOrder() { + int order = configEncryptionFilter.getOrder(); + Assert.assertEquals(order, 0); + } +} diff --git a/plugin/encryption/src/test/java/com/alibaba/nacos/plugin/encryption/handler/EncryptionAesHandlerTest.java b/plugin/encryption/src/test/java/com/alibaba/nacos/plugin/encryption/handler/EncryptionAesHandlerTest.java new file mode 100644 index 000000000..4690a3f70 --- /dev/null +++ b/plugin/encryption/src/test/java/com/alibaba/nacos/plugin/encryption/handler/EncryptionAesHandlerTest.java @@ -0,0 +1,235 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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 com.alibaba.nacos.plugin.encryption.handler; + +import com.alibaba.nacos.common.utils.Pair; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager; +import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * AES encryption algorithm testing dataId with prefix cipher. + * + * @Author shiwenyu + * @Date 2023/12/22 6:07 PM + * @Version 1.0 + */ +public class EncryptionAesHandlerTest { + + private EncryptionPluginService mockEncryptionPluginService; + + @Before + public void setUp() { + mockEncryptionPluginService = new EncryptionPluginService() { + + private static final String ALGORITHM = "AES"; + + private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding"; + + // 随机生成密钥-用来加密数据内容 + private final String contentKey = generateKey(); + + // 随机生成密钥-用来加密密钥 + private final String theKeyOfContentKey = generateKey(); + + private String generateKey() { + SecureRandom secureRandom = new SecureRandom(); + KeyGenerator keyGenerator; + try { + keyGenerator = KeyGenerator.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + keyGenerator.init(128, secureRandom); + SecretKey secretKey = keyGenerator.generateKey(); + byte[] keyBytes = secretKey.getEncoded(); + return Base64.encodeBase64String(keyBytes); + } + + @Override + public String encrypt(String secretKey, String content) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, content, secretKey)); + } + + @Override + public String decrypt(String secretKey, String content) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(content, secretKey); + } + + @Override + public String generateSecretKey() { + return contentKey; + } + + @Override + public String algorithmName() { + return ALGORITHM.toLowerCase(); + } + + @Override + public String encryptSecretKey(String secretKey) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, generateSecretKey(), theKeyOfContentKey)); + } + + @Override + public String decryptSecretKey(String secretKey) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(secretKey, theKeyOfContentKey); + } + + private byte[] aes(int mode, String content, String key) { + try { + return aesBytes(mode, content.getBytes(StandardCharsets.UTF_8), key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] aesBytes(int mode, byte[] content, String key) { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); + Cipher cipher = null; + try { + cipher = Cipher.getInstance(AES_PKCS5P); + cipher.init(mode, keySpec); + return cipher.doFinal(content); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String aesDecrypt(String content, String key) { + byte[] bytes = aesBytes(Cipher.DECRYPT_MODE, Base64.decodeBase64(content), key); + return new String(bytes, StandardCharsets.UTF_8); + } + }; + EncryptionPluginManager.join(mockEncryptionPluginService); + } + + @Test + public void testEncrypt() { + String content = "content"; + String contentKey = mockEncryptionPluginService.generateSecretKey(); + Pair pair = EncryptionHandler.encryptHandler("cipher-aes-dataId", content); + Assert.assertEquals("should return the encryption secret key if algorithm defined.", mockEncryptionPluginService.encryptSecretKey(contentKey), + pair.getFirst()); + Assert.assertEquals("should return the encryption content if algorithm defined.", mockEncryptionPluginService.encrypt(contentKey, content), + pair.getSecond()); + } + + @Test + public void testDecrypt() { + String content = "content"; + String contentKey = mockEncryptionPluginService.generateSecretKey(); + String encryptionSecretKey = mockEncryptionPluginService.encryptSecretKey(contentKey); + String encryptionContent = mockEncryptionPluginService.encrypt(contentKey, content); + + Pair pair = EncryptionHandler.decryptHandler("cipher-aes-dataId", encryptionSecretKey, encryptionContent); + + Assert.assertEquals("should return the original secret key if algorithm defined.", mockEncryptionPluginService.generateSecretKey(), + pair.getFirst()); + Assert.assertEquals("should return the original content if algorithm defined.", content, pair.getSecond()); + + } + + @Test + public void testEncryptAndDecrypt() { + String dataId = "cipher-aes-dataId"; + String content = "content"; + String contentKey = mockEncryptionPluginService.generateSecretKey(); + + Pair encryptPair = EncryptionHandler.encryptHandler(dataId, content); + String encryptionSecretKey = encryptPair.getFirst(); + String encryptionContent = encryptPair.getSecond(); + Assert.assertNotNull(encryptPair); + Assert.assertEquals("should return the encryption secret key if algorithm defined.", mockEncryptionPluginService.encryptSecretKey(contentKey), + encryptionSecretKey); + Assert.assertEquals("should return the encryption content if algorithm defined.", mockEncryptionPluginService.encrypt(contentKey, content), + encryptionContent); + + Pair decryptPair = EncryptionHandler.decryptHandler(dataId, encryptionSecretKey, encryptionContent); + Assert.assertNotNull(decryptPair); + Assert.assertEquals("should return the original secret key if algorithm defined.", mockEncryptionPluginService.generateSecretKey(), + decryptPair.getFirst()); + Assert.assertEquals("should return the original content if algorithm defined.", content, decryptPair.getSecond()); + } + + @Test + public void testPrefixNotCipherEncrypt() { + String content = "content"; + Pair pair = EncryptionHandler.encryptHandler("test-dataId", content); + Assert.assertNotNull(pair); + Assert.assertEquals(pair.getFirst(), ""); + Assert.assertEquals(pair.getSecond(), content); + } + + @Test + public void testPrefixNotCipherDecrypt() { + String content = "content"; + Pair pair = EncryptionHandler.decryptHandler("test-dataId", "", content); + Assert.assertNotNull(pair); + Assert.assertEquals(pair.getFirst(), ""); + Assert.assertEquals(pair.getSecond(), content); + } + + @Test + public void testAlgorithmEmpty() { + String dataId = "cipher-"; + String content = "content"; + Pair pair = EncryptionHandler.encryptHandler(dataId, content); + Assert.assertNotNull("should not throw exception when parsing enc algo for dataId '" + dataId + "'", pair); + Assert.assertEquals(pair.getFirst(), ""); + Assert.assertEquals(pair.getSecond(), content); + } + + @Test + public void testUnknownAlgorithmNameEncrypt() { + String dataId = "cipher-mySM4-application"; + String content = "content"; + Pair pair = EncryptionHandler.encryptHandler(dataId, content); + Assert.assertNotNull(pair); + Assert.assertEquals(pair.getFirst(), ""); + Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond()); + } + + @Test + public void testUnknownAlgorithmNameDecrypt() { + String dataId = "cipher-mySM4-application"; + String content = "content"; + Pair pair = EncryptionHandler.decryptHandler(dataId, "", content); + Assert.assertNotNull(pair); + Assert.assertEquals(pair.getFirst(), ""); + Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond()); + } +} diff --git a/test/config-test/src/test/java/com/alibaba/nacos/test/config/AbstractConfigAPI_CITCase.java b/test/config-test/src/test/java/com/alibaba/nacos/test/config/AbstractConfigAPI_CITCase.java index aafe8a8de..f95114ea2 100644 --- a/test/config-test/src/test/java/com/alibaba/nacos/test/config/AbstractConfigAPI_CITCase.java +++ b/test/config-test/src/test/java/com/alibaba/nacos/test/config/AbstractConfigAPI_CITCase.java @@ -28,7 +28,18 @@ import com.alibaba.nacos.client.config.http.MetricsHttpAgent; import com.alibaba.nacos.client.config.http.ServerHttpAgent; import com.alibaba.nacos.common.http.HttpRestResult; import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager; +import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Base64; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -65,6 +76,100 @@ public abstract class AbstractConfigAPI_CITCase { @LocalServerPort private int port; + private EncryptionPluginService mockEncryptionPluginService; + + @Before + public void initEncryptionPluginService() { + mockEncryptionPluginService = new EncryptionPluginService() { + + private static final String ALGORITHM = "AES"; + + private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding"; + + // 随机生成密钥-用来加密数据内容 + private final String contentKey = generateKey(); + + // 随机生成密钥-用来加密密钥 + private final String theKeyOfContentKey = generateKey(); + + private String generateKey() { + SecureRandom secureRandom = new SecureRandom(); + KeyGenerator keyGenerator; + try { + keyGenerator = KeyGenerator.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + keyGenerator.init(128, secureRandom); + SecretKey secretKey = keyGenerator.generateKey(); + byte[] keyBytes = secretKey.getEncoded(); + return Base64.encodeBase64String(keyBytes); + } + + @Override + public String encrypt(String secretKey, String content) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, content, secretKey)); + } + + @Override + public String decrypt(String secretKey, String content) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(content, secretKey); + } + + @Override + public String generateSecretKey() { + return contentKey; + } + + @Override + public String algorithmName() { + return ALGORITHM.toLowerCase(); + } + + @Override + public String encryptSecretKey(String secretKey) { + return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, generateSecretKey(), theKeyOfContentKey)); + } + + @Override + public String decryptSecretKey(String secretKey) { + if (StringUtils.isBlank(secretKey)) { + return null; + } + return aesDecrypt(secretKey, theKeyOfContentKey); + } + + private byte[] aes(int mode, String content, String key) { + try { + return aesBytes(mode, content.getBytes(StandardCharsets.UTF_8), key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] aesBytes(int mode, byte[] content, String key) { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); + Cipher cipher = null; + try { + cipher = Cipher.getInstance(AES_PKCS5P); + cipher.init(mode, keySpec); + return cipher.doFinal(content); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String aesDecrypt(String content, String key) { + byte[] bytes = aesBytes(Cipher.DECRYPT_MODE, Base64.decodeBase64(content), key); + return new String(bytes, StandardCharsets.UTF_8); + } + }; + EncryptionPluginManager.join(mockEncryptionPluginService); + } + @Before public void setUp() throws Exception { Properties properties = new Properties(); @@ -116,6 +221,27 @@ public abstract class AbstractConfigAPI_CITCase { Assert.assertNull(value); } + /** + * @TCDescription : nacos_正常推送&获取加密数据 + * @TestStep : + * @ExpectResult : + */ + @Test(timeout = 3 * TIME_OUT) + public void nacosPublishAndGetConfig() throws Exception { + String dataId = "cipher-aes-dataId"; + final String content = "test"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(content, value); + result = iconfig.removeConfig(dataId, group); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertNull(value); + } + /** * @throws Exception * @TCDescription : nacos_服务端无配置时,获取配置