From 522173e5c33ea975f6ccab9a1d5aa65b235cbb74 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 13 Jun 2016 12:44:22 +0200 Subject: [PATCH] DATAKV-136 - Choose SecureRandom algorithm based on operating system and availability. We now distinguish between operating systems when choosing a SecureRandom algorithm. Additionally we check the availability of the implementations and choose the first one available. Original pull request: #21. --- .../core/DefaultIdentifierGenerator.java | 67 +++++++++++--- .../DefaultIdentifierGeneratorUnitTests.java | 92 +++++++++++++++++++ 2 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 src/test/java/org/springframework/data/keyvalue/core/DefaultIdentifierGeneratorUnitTests.java diff --git a/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java b/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java index 95dc054..b5b02cb 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,15 @@ package org.springframework.data.keyvalue.core; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.util.TypeInformation; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Default implementation of {@link IdentifierGenerator} to generate identifiers of types {@link UUID}, String, @@ -33,7 +37,7 @@ enum DefaultIdentifierGenerator implements IdentifierGenerator { INSTANCE; - private static final String ALGORITHM = "NativePRNGBlocking"; + private final AtomicReference secureRandom = new AtomicReference(null); /* * (non-Javadoc) @@ -50,22 +54,55 @@ enum DefaultIdentifierGenerator implements IdentifierGenerator { } else if (ClassUtils.isAssignable(String.class, type)) { return (T) UUID.randomUUID().toString(); } else if (ClassUtils.isAssignable(Integer.class, type)) { - - try { - return (T) SecureRandom.getInstance(ALGORITHM); - } catch (NoSuchAlgorithmException e) { - throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); - } - + return (T) Integer.valueOf(getSecureRandom().nextInt()); } else if (ClassUtils.isAssignable(Long.class, type)) { - - try { - return (T) Long.valueOf(SecureRandom.getInstance(ALGORITHM).nextLong()); - } catch (NoSuchAlgorithmException e) { - throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); - } + return (T) Long.valueOf(getSecureRandom().nextLong()); } throw new InvalidDataAccessApiUsageException("Non gereratable id type...."); } + + private SecureRandom getSecureRandom() { + + SecureRandom secureRandom = this.secureRandom.get(); + if (secureRandom != null) { + return secureRandom; + } + + for (String algorithm : OsTools.secureRandomAlgorithmNames()) { + try { + secureRandom = SecureRandom.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + // ignore and try next. + } + } + + if (secureRandom == null) { + throw new InvalidDataAccessApiUsageException( + String.format("Could not create SecureRandom instance for one of the algorithms '%s'.", + StringUtils.collectionToCommaDelimitedString(OsTools.secureRandomAlgorithmNames()))); + } + + this.secureRandom.compareAndSet(null, secureRandom); + + return secureRandom; + } + + /** + * @author Christoph Strobl + * @since 1.1.2 + */ + private static class OsTools { + + private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(); + + private static final List SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS = Arrays.asList("NativePRNGBlocking", + "NativePRNGNonBlocking", "NativePRNG", "SHA1PRNG"); + private static final List SECURE_RANDOM_ALGORITHMS_WINDOWS = Arrays.asList("SHA1PRNG", "Windows-PRNG"); + + static List secureRandomAlgorithmNames() { + return OPERATING_SYSTEM_NAME.indexOf("win") >= 0 ? SECURE_RANDOM_ALGORITHMS_WINDOWS + : SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS; + } + } } diff --git a/src/test/java/org/springframework/data/keyvalue/core/DefaultIdentifierGeneratorUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/DefaultIdentifierGeneratorUnitTests.java new file mode 100644 index 0000000..61b72ce --- /dev/null +++ b/src/test/java/org/springframework/data/keyvalue/core/DefaultIdentifierGeneratorUnitTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 the original author or 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.springframework.data.keyvalue.core; + +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import java.util.Date; +import java.util.UUID; + +import org.junit.Test; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.util.ClassTypeInformation; + +/** + * @author Christoph Strobl + */ +public class DefaultIdentifierGeneratorUnitTests { + + DefaultIdentifierGenerator generator = DefaultIdentifierGenerator.INSTANCE; + + /** + * @DATAKV-136 + */ + @Test(expected = InvalidDataAccessApiUsageException.class) + public void shouldThrowExceptionForUnsupportedType() { + generator.generateIdentifierOfType(ClassTypeInformation.from(Date.class)); + } + + /** + * @DATAKV-136 + */ + @Test + public void shouldGenerateUUIDValueCorrectly() { + + Object value = generator.generateIdentifierOfType(ClassTypeInformation.from(UUID.class)); + + assertThat(value, is(notNullValue())); + assertThat(value, instanceOf(UUID.class)); + } + + /** + * @DATAKV-136 + */ + @Test + public void shouldGenerateStringValueCorrectly() { + + Object value = generator.generateIdentifierOfType(ClassTypeInformation.from(String.class)); + + assertThat(value, is(notNullValue())); + assertThat(value, instanceOf(String.class)); + } + + /** + * @DATAKV-136 + */ + @Test + public void shouldGenerateLongValueCorrectly() { + + Object value = generator.generateIdentifierOfType(ClassTypeInformation.from(Long.class)); + + assertThat(value, is(notNullValue())); + assertThat(value, instanceOf(Long.class)); + } + + /** + * @DATAKV-136 + */ + @Test + public void shouldGenerateIntValueCorrectly() { + + Object value = generator.generateIdentifierOfType(ClassTypeInformation.from(Integer.class)); + + assertThat(value, is(notNullValue())); + assertThat(value, instanceOf(Integer.class)); + } +}