package io.kaif.token; import static java.util.Arrays.asList; import static java.util.stream.Collectors.*; import static org.junit.Assert.*; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.IntStream; import org.junit.Test; public class SecureTokenCodecTest { private final byte[] macKey = { 12, 23, 31, 41, 5, 16, -7, -8, -9, -10, -11, 112, -11, 14, 15, 16 }; private final byte[] secretKey = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; private void assertFieldsEquals(final List<byte[]> expect, final List<byte[]> actual) { if (actual == null && expect == null) { return; } if (actual == null && expect != null) { fail("expect fields size is " + expect.size() + ", but was null"); } if (actual != null && expect == null) { fail("expect null, but was fields size:" + actual.size()); } if (actual.size() != expect.size()) { fail("expect fields size is " + expect.size() + ", but was " + actual.size()); } for (int i = 0; i < expect.size(); i++) { final byte[] expectByte = expect.get(i); final byte[] actualByte = actual.get(i); assertArrayEquals(expectByte, actualByte); } } @Test public void codec() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final String field1 = UUID.randomUUID().toString(); final String field2 = UUID.randomUUID().toString(); final String field3 = UUID.randomUUID().toString(); Instant expireTime = Instant.now().plus(Duration.ofDays(356 * 20)); final List<byte[]> fields = Arrays.asList(field1.getBytes(), field2.getBytes(), field3.getBytes()); final String token = codec.encode(expireTime.toEpochMilli(), fields); assertTrue(token.length() > 160); assertTrue(codec.validateToken(token, fields)); final List<byte[]> decodedFields = codec.tryDecode(token); assertFieldsEquals(fields, decodedFields); } @Test public void codec_more() throws Exception { final HashSet<String> allTokens = new HashSet<>(); final int testCount = 10000; for (int i = 0; i < testCount; i++) { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final String field1 = UUID.randomUUID().toString(); final String field2 = UUID.randomUUID().toString(); final String field3 = UUID.randomUUID().toString(); Instant expireTime = Instant.now().plus(Duration.ofDays(356 * 20)); final List<byte[]> fields = Arrays.asList(field1.getBytes(), field2.getBytes(), field3.getBytes()); final String token = codec.encode(expireTime.toEpochMilli(), fields); allTokens.add(token); assertTrue(token.length() > 160); assertTrue(codec.validateToken(token, fields)); final List<byte[]> decodedFields = codec.tryDecode(token); assertFieldsEquals(fields, decodedFields); } assertEquals(10000, allTokens.size()); } @Test public void codec_more_fields() throws Exception { final byte[] macKey = { 12, 23, 31, 41, 5, 16, -7, -8, -9, -10, -11 }; final byte[] secretKey = { 16, 1, 2, 3, 4, 5, -6, 7, 8, 9, -10, 11, 12, 13, -14, 15, }; final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final List<byte[]> fields = new ArrayList<>(); for (int i = 0; i < 10; i++) { fields.add(UUID.randomUUID().toString().getBytes()); } Instant expireTime = Instant.now().plus(Duration.ofDays(356 * 20)); final String token = codec.encode(expireTime.toEpochMilli(), fields); assertTrue(codec.validateToken(token, fields)); final List<byte[]> decodedFields = codec.tryDecode(token); assertFieldsEquals(fields, decodedFields); } @Test public void codecString() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final String field1 = UUID.randomUUID().toString(); final String field2 = UUID.randomUUID().toString(); final String field3 = UUID.randomUUID().toString(); Instant expireTime = Instant.now().plus(Duration.ofDays(356 * 20)); final List<byte[]> byteFields = asList(field1.getBytes(), field2.getBytes(), field3.getBytes()); final String token = codec.encodeString(expireTime.toEpochMilli(), field1, field2, field3); assertTrue(token.length() > 160); assertTrue(codec.validateToken(token, byteFields)); final List<String> decodedFields = codec.tryDecodeAsString(token); assertFieldsEquals(byteFields, decodedFields.stream().map(s -> s.getBytes()).collect(toList())); } @Test public void codecString_long_field() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final String field1 = IntStream.range(0, 256).mapToObj(i -> "a").collect(joining()); final String field2 = UUID.randomUUID().toString(); Instant expireTime = Instant.now().plus(Duration.ofDays(356 * 20)); final List<byte[]> byteFields = asList(field1.getBytes(), field2.getBytes()); final String token = codec.encodeString(expireTime.toEpochMilli(), field1, field2); assertTrue(token.length() > 160); assertTrue(codec.validateToken(token, byteFields)); final List<String> decodedFields = codec.tryDecodeAsString(token); assertFieldsEquals(byteFields, decodedFields.stream().map(s -> s.getBytes()).collect(toList())); } @Test public void generateRandomSecretKey() throws Exception { final Set<String> keys = IntStream.range(0, 10000).mapToObj(i -> { return SecureTokenCodec.generateUrlSafeKey(); }).collect(toSet()); assertEquals(10000, keys.size()); assertEquals(10000, keys.stream() .map(SecureTokenCodec::convertUrlSafeKeyToBytes) .filter(bytes -> bytes.length == 16) .count()); } @Test public void tryDecode_expired() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); final String field1 = "123412341234adfa"; final String field2 = "134dfasdfasdfa"; Instant expire = Instant.now().minus(Duration.ofMinutes(1)); final List<byte[]> fields = Arrays.asList(field1.getBytes(), field2.getBytes()); final String token = codec.encode(expire.toEpochMilli(), fields); assertNull(codec.tryDecode(token)); } @Test public void tryDecode_failed() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); assertNull(codec.tryDecode( "qSruSGAIYINH3y461ZjQX3xM08nCPXKp10punG7t15W6ixNivb5aSZUlY5XapiamKY8PsETguAHV4AFoOZx7DSjdEcjqqmrtZqgiXgZgNR3LgujbpBuO1mejxcq3HAS")); } @Test public void tryDecode_failed_invalid_base64() throws Exception { final SecureTokenCodec codec = SecureTokenCodec.create(macKey, secretKey); assertNull(codec.tryDecode( "Ahde3Wy_r4JG0fhsH_NAvZ9StbzAk6nIDUCP_FcTOkEz1QqWVCPStlCpgXEy8NVgzbWOGHzha6enVThKuwlB2dg8SSa1Bme3P6Mh6t7x4tA9SU6sA")); } }