diff --git a/aaa4j-radius-core/src/main/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodec.java b/aaa4j-radius-core/src/main/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodec.java index 77f772d..e238897 100644 --- a/aaa4j-radius-core/src/main/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodec.java +++ b/aaa4j-radius-core/src/main/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodec.java @@ -82,9 +82,13 @@ public D decode(CodecContext codecContext, AttributeType parentAttributeType, by public byte[] encode(CodecContext codecContext, AttributeType parentAttributeType, Data data) { byte[] password = dataCodec.encode(codecContext, parentAttributeType, data); + if (password.length > 128) { + throw new IllegalArgumentException("Password length must be in range [0, 128]"); + } + MessageDigest md5 = getMd5Instance(); - byte[] paddedPassword = new byte[Math.min(16, password.length + ((16 - password.length) % 16))]; + byte[] paddedPassword = new byte[password.length - ((password.length - 1) % 16) + 15]; System.arraycopy(password, 0, paddedPassword, 0, password.length); byte[] hiddenPassword = new byte[paddedPassword.length]; diff --git a/aaa4j-radius-core/src/test/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodecTest.java b/aaa4j-radius-core/src/test/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodecTest.java new file mode 100644 index 0000000..84e1a14 --- /dev/null +++ b/aaa4j-radius-core/src/test/java/org/aaa4j/radius/core/attribute/UserPasswordDataCodecTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2020 The AAA4J-RADIUS Authors + * + * 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 org.aaa4j.radius.core.attribute; + +import org.aaa4j.radius.core.attribute.attributes.UserPassword; +import org.aaa4j.radius.core.dictionary.dictionaries.StandardDictionary; +import org.aaa4j.radius.core.util.SecureRandomProvider; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.aaa4j.radius.core.Utils.fromHex; +import static org.aaa4j.radius.core.Utils.toHex; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("UserPasswordDataCodec") +class UserPasswordDataCodecTest { + + @Test + @DisplayName("UserPassword data is decoded successfully") + void testDecode() { + UserPasswordDataCodec userPasswordDataCodec = + new UserPasswordDataCodec<>(StringData.Codec.INSTANCE); + + CodecContext codecContext = new CodecContext(new StandardDictionary(), + fromHex("d955e791c15fe6996434be163c8c019d21cd901b867600c2662e8a4628c5bff3"), + fromHex("9fa4ee463dbfd1b0c99a209490c52cb6"), + new SecureRandomProvider()); + + { + byte[] hiddenPassword = fromHex("b04ef795318fb58d6da1ccc94ff1552c"); + + StringData passwordStringData = + userPasswordDataCodec.decode(codecContext, UserPassword.TYPE, hiddenPassword); + + String password = new String(passwordStringData.getValue(), StandardCharsets.UTF_8); + + assertEquals("", password); + } + { + byte[] hiddenPassword = fromHex("d12c9495318fb58d6da1ccc94ff1552c"); + + StringData passwordStringData = + userPasswordDataCodec.decode(codecContext, UserPassword.TYPE, hiddenPassword); + + String password = new String(passwordStringData.getValue(), StandardCharsets.UTF_8); + + assertEquals("abc", password); + } + { + byte[] hiddenPassword = fromHex("d12c94f154e9d2e504cba7a5229f3a5c"); + + StringData passwordStringData = + userPasswordDataCodec.decode(codecContext, UserPassword.TYPE, hiddenPassword); + + String password = new String(passwordStringData.getValue(), StandardCharsets.UTF_8); + + assertEquals("abcdefghijklmnop", password); + } + { + byte[] hiddenPassword = fromHex("d12c94f154e9d2e504cba7a5229f3a5cf3e675578cc3abb486e814c9a97dd5d0" + + "657fd2d428d79fa68b9be651ac0893a440e94a20e238962bc3de4b9184ccbb7c" + + "72dfdb01a95d8c1d473daad70b763494a65dc6c705e276634f8040ae9af5d0b3" + + "a501bb4ce27950968a2ce5926dfd2f1a5c642f59e615b4188389cfe99e518ea9"); + + StringData passwordStringData = + userPasswordDataCodec.decode(codecContext, UserPassword.TYPE, hiddenPassword); + + String password = new String(passwordStringData.getValue(), StandardCharsets.UTF_8); + + assertEquals("abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwx", + password); + } + } + + @Test + @DisplayName("UserPassword is encoded successfully") + void testEncode() { + UserPasswordDataCodec userPasswordDataCodec = + new UserPasswordDataCodec<>(StringData.Codec.INSTANCE); + + CodecContext codecContext = new CodecContext(new StandardDictionary(), + fromHex("d955e791c15fe6996434be163c8c019d21cd901b867600c2662e8a4628c5bff3"), + fromHex("9fa4ee463dbfd1b0c99a209490c52cb6"), + new SecureRandomProvider()); + + { + byte[] password = "".getBytes(StandardCharsets.UTF_8); + + byte[] hiddenPassword = + userPasswordDataCodec.encode(codecContext, UserPassword.TYPE, new StringData(password)); + + assertEquals("b04ef795318fb58d6da1ccc94ff1552c", toHex(hiddenPassword)); + } + { + byte[] password = "abc".getBytes(StandardCharsets.UTF_8); + + byte[] hiddenPassword = + userPasswordDataCodec.encode(codecContext, UserPassword.TYPE, new StringData(password)); + + assertEquals("d12c9495318fb58d6da1ccc94ff1552c", toHex(hiddenPassword)); + } + { + byte[] password = "abcdefghijklmnop".getBytes(StandardCharsets.UTF_8); + + byte[] hiddenPassword = + userPasswordDataCodec.encode(codecContext, UserPassword.TYPE, new StringData(password)); + + assertEquals("d12c94f154e9d2e504cba7a5229f3a5c", toHex(hiddenPassword)); + } + { + byte[] password = ("abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwx").getBytes(StandardCharsets.UTF_8); + + byte[] hiddenPassword = + userPasswordDataCodec.encode(codecContext, UserPassword.TYPE, new StringData(password)); + + assertEquals("d12c94f154e9d2e504cba7a5229f3a5cf3e675578cc3abb486e814c9a97dd5d0" + + "657fd2d428d79fa68b9be651ac0893a440e94a20e238962bc3de4b9184ccbb7c" + + "72dfdb01a95d8c1d473daad70b763494a65dc6c705e276634f8040ae9af5d0b3" + + "a501bb4ce27950968a2ce5926dfd2f1a5c642f59e615b4188389cfe99e518ea9", + toHex(hiddenPassword)); + } + } + + @Test + @DisplayName("Invalid vsa data is decoded into null") + void testEncodeInvalid() { + UserPasswordDataCodec userPasswordDataCodec = + new UserPasswordDataCodec<>(StringData.Codec.INSTANCE); + + CodecContext codecContext = new CodecContext(new StandardDictionary(), + fromHex("d955e791c15fe6996434be163c8c019d21cd901b867600c2662e8a4628c5bff3"), + fromHex("9fa4ee463dbfd1b0c99a209490c52cb6"), + new SecureRandomProvider()); + + { + // Too many bytes (> 128) + assertThrows(IllegalArgumentException.class, () -> { + new EvsData(32473, -1, fromHex("abcd")); + }); + } + } + +} \ No newline at end of file