/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.security;
import com.liferay.portal.kernel.io.BigEndianCodec;
import com.liferay.portal.kernel.test.ReflectionTestUtil;
import com.liferay.portal.kernel.test.rule.AggregateTestRule;
import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor;
import com.liferay.portal.kernel.test.rule.NewEnv;
import com.liferay.portal.kernel.test.rule.NewEnvTestRule;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
/**
* @author Shuyang Zhou
*/
@NewEnv(type = NewEnv.Type.CLASSLOADER)
public class SecureRandomUtilTest {
@ClassRule
@Rule
public static final AggregateTestRule aggregateTestRule =
new AggregateTestRule(
CodeCoverageAssertor.INSTANCE, NewEnvTestRule.INSTANCE);
@Before
public void setUp() {
System.setProperty(_KEY_BUFFER_SIZE, "2048");
}
@After
public void tearDown() {
System.clearProperty(_KEY_BUFFER_SIZE);
}
@Test
public void testConcurrentReload() throws Exception {
SecureRandom secureRandom = installPredictableRandom();
FutureTask<Long> futureTask = new FutureTask<>(
new Callable<Long>() {
@Override
public Long call() {
return reload();
}
});
Thread reloadThread = new Thread(futureTask);
synchronized (secureRandom) {
reloadThread.start();
while (reloadThread.getState() != Thread.State.BLOCKED);
Assert.assertEquals(getLong(0), reload());
}
reloadThread.join();
Assert.assertEquals((Long)(getLong(0) ^ 1), futureTask.get());
}
@NewEnv(type = NewEnv.Type.NONE)
@Test
public void testConstructor() {
new SecureRandomUtil();
}
@Test
public void testInitialization() {
System.setProperty(_KEY_BUFFER_SIZE, "10");
Assert.assertEquals(
Integer.valueOf(1024),
ReflectionTestUtil.getFieldValue(
SecureRandomUtil.class, "_BUFFER_SIZE"));
byte[] bytes = ReflectionTestUtil.getFieldValue(
SecureRandomUtil.class, "_bytes");
Assert.assertEquals(Arrays.toString(bytes), 1024, bytes.length);
}
@Test
public void testNextBoolean() {
// First load
installPredictableRandom();
for (int i = 0; i < 2048; i++) {
byte b = (byte)i;
if (b < 0) {
Assert.assertFalse(SecureRandomUtil.nextBoolean());
}
else {
Assert.assertTrue(SecureRandomUtil.nextBoolean());
}
}
// Gap number
if ((byte)getLong(7) < 0) {
Assert.assertFalse(SecureRandomUtil.nextBoolean());
}
else {
Assert.assertTrue(SecureRandomUtil.nextBoolean());
}
// Second load
for (int i = 0; i < 2048; i++) {
byte b = (byte)i;
if (b < 0) {
Assert.assertFalse(SecureRandomUtil.nextBoolean());
}
else {
Assert.assertTrue(SecureRandomUtil.nextBoolean());
}
}
// Gap number
if ((byte)(getLong(7) ^ 1) < 0) {
Assert.assertFalse(SecureRandomUtil.nextBoolean());
}
else {
Assert.assertTrue(SecureRandomUtil.nextBoolean());
}
}
@Test
public void testNextByte() {
// First load
installPredictableRandom();
for (int i = 0; i < 2048; i++) {
Assert.assertEquals((byte)i, SecureRandomUtil.nextByte());
}
// Gap number
Assert.assertEquals((byte)getLong(7), SecureRandomUtil.nextByte());
// Second load
for (int i = 0; i < 2048; i++) {
Assert.assertEquals((byte)i, SecureRandomUtil.nextByte());
}
// Gap number
Assert.assertEquals(
(byte)(getLong(7) ^ 1), SecureRandomUtil.nextByte());
}
@Test
public void testNextDouble() {
// First load
installPredictableRandom();
for (int i = 0; i < 256; i++) {
byte b = (byte)(i * 8);
byte[] bytes = new byte[8];
for (int j = 0; j < 8; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getDouble(bytes, 0),
SecureRandomUtil.nextDouble(), 0);
}
// Gap number
Assert.assertEquals(
Double.longBitsToDouble(getLong(7)), SecureRandomUtil.nextDouble(),
0);
// Second load
for (int i = 0; i < 256; i++) {
byte b = (byte)(i * 8);
byte[] bytes = new byte[8];
for (int j = 0; j < 8; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getDouble(bytes, 0),
SecureRandomUtil.nextDouble(), 0);
}
// Gap number
Assert.assertEquals(
Double.longBitsToDouble(getLong(7) ^ 1),
SecureRandomUtil.nextDouble(), 0);
}
@Test
public void testNextFloat() {
// First load
installPredictableRandom();
for (int i = 0; i < 512; i++) {
byte b = (byte)(i * 4);
byte[] bytes = new byte[4];
for (int j = 0; j < 4; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getFloat(bytes, 0), SecureRandomUtil.nextFloat(),
0);
}
// Gap number
Assert.assertEquals(
Float.intBitsToFloat((int)getLong(7)), SecureRandomUtil.nextFloat(),
0);
// Second load
for (int i = 0; i < 512; i++) {
byte b = (byte)(i * 4);
byte[] bytes = new byte[4];
for (int j = 0; j < 4; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getFloat(bytes, 0), SecureRandomUtil.nextFloat(),
0);
}
// Gap number
Assert.assertEquals(
Float.intBitsToFloat((int)getLong(7) ^ 1),
SecureRandomUtil.nextFloat(), 0);
}
@Test
public void testNextInt() {
// First load
installPredictableRandom();
for (int i = 0; i < 512; i++) {
byte b = (byte)(i * 4);
byte[] bytes = new byte[4];
for (int j = 0; j < 4; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getInt(bytes, 0), SecureRandomUtil.nextInt());
}
// Gap number
Assert.assertEquals((int)getLong(7), SecureRandomUtil.nextInt());
// Second load
for (int i = 0; i < 512; i++) {
byte b = (byte)(i * 4);
byte[] bytes = new byte[4];
for (int j = 0; j < 4; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getInt(bytes, 0), SecureRandomUtil.nextInt());
}
// Gap number
Assert.assertEquals((int)(getLong(7) ^ 1L), SecureRandomUtil.nextInt());
}
@Test
public void testNextLong() {
// First load
installPredictableRandom();
for (int i = 0; i < 256; i++) {
byte b = (byte)(i * 8);
byte[] bytes = new byte[8];
for (int j = 0; j < 8; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getLong(bytes, 0), SecureRandomUtil.nextLong());
}
// Gap number
Assert.assertEquals(getLong(7), SecureRandomUtil.nextLong());
// Second load
for (int i = 0; i < 256; i++) {
byte b = (byte)(i * 8);
byte[] bytes = new byte[8];
for (int j = 0; j < 8; j++) {
bytes[j] = (byte)(b + j);
}
Assert.assertEquals(
BigEndianCodec.getLong(bytes, 0), SecureRandomUtil.nextLong());
}
// Gap number
Assert.assertEquals(getLong(7) ^ 1, SecureRandomUtil.nextLong());
}
protected long getLong(int offset) {
byte[] bytes = ReflectionTestUtil.getFieldValue(
SecureRandomUtil.class, "_bytes");
return BigEndianCodec.getLong(bytes, offset);
}
protected SecureRandom installPredictableRandom() {
ReflectionTestUtil.setFieldValue(
SecureRandomUtil.class, "_gapRandom",
new Random() {
@Override
public long nextLong() {
return _counter.getAndIncrement();
}
private static final long serialVersionUID = 1L;
private final AtomicLong _counter = new AtomicLong();
});
SecureRandom predictableRandom = new PredictableRandom();
ReflectionTestUtil.setFieldValue(
SecureRandomUtil.class, "_random", predictableRandom);
byte[] bytes = ReflectionTestUtil.getFieldValue(
SecureRandomUtil.class, "_bytes");
predictableRandom.nextBytes(bytes);
return predictableRandom;
}
protected long reload() {
return ReflectionTestUtil.invoke(
SecureRandomUtil.class, "_reload", new Class<?>[] {int.class}, 0);
}
private static final String _KEY_BUFFER_SIZE =
SecureRandomUtil.class.getName() + ".buffer.size";
private static class PredictableRandom extends SecureRandom {
@Override
public synchronized void nextBytes(byte[] bytes) {
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte)_counter.getAndIncrement();
}
}
@Override
public long nextLong() {
return 0;
}
private static final long serialVersionUID = 1L;
private final AtomicInteger _counter = new AtomicInteger();
}
}