SLING-10499 Provide a salt provider using a secure random number generator
diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java
new file mode 100644
index 0000000..e2695fe
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.crypto.internal;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Objects;
+
+import org.apache.sling.commons.crypto.SaltProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+ property = {
+ Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons Crypto – SecureRandom Salt Provider",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ }
+)
+@Designate(
+ ocd = SecureRandomSaltProviderConfiguration.class,
+ factory = true
+)
+public class SecureRandomSaltProvider implements SaltProvider {
+
+ private SecureRandom secureRandom;
+
+ private SecureRandomSaltProviderConfiguration configuration;
+
+ private final Logger logger = LoggerFactory.getLogger(SecureRandomSaltProvider.class);
+
+ public SecureRandomSaltProvider() { //
+ }
+
+ @Activate
+ protected void activate(final SecureRandomSaltProviderConfiguration configuration) throws IOException, NoSuchAlgorithmException {
+ logger.debug("activating");
+ this.configuration = configuration;
+ secureRandom = SecureRandom.getInstance(configuration.algorithm());
+
+ }
+
+ @Modified
+ protected void modified(final SecureRandomSaltProviderConfiguration configuration) throws IOException, NoSuchAlgorithmException {
+ logger.debug("modifying");
+ this.configuration = configuration;
+ secureRandom = SecureRandom.getInstance(configuration.algorithm());
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ logger.debug("deactivating");
+ }
+
+ @Override
+ public byte @NotNull [] getSalt() {
+ Objects.requireNonNull(configuration, "Configuration must not be null");
+ final byte[] bytes = new byte[configuration.keyLength()];
+ secureRandom.nextBytes(bytes);
+ return bytes;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java
new file mode 100644
index 0000000..71306e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.crypto.internal;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(
+ name = "Apache Sling Commons Crypto “SecureRandom Salt Provider”",
+ description = "Provides salts created from random bytes"
+)
+@interface SecureRandomSaltProviderConfiguration {
+
+ @AttributeDefinition(
+ name = "Names",
+ description = "names of this service",
+ required = false
+ )
+ String[] names() default {};
+
+ @AttributeDefinition(
+ name = "Algorithm",
+ description = "secure random number generation algorithm"
+ )
+ String algorithm() default "SHA1PRNG";
+
+ @AttributeDefinition(
+ name = "Key Length",
+ description = "length of the key"
+ )
+ int keyLength() default 8;
+
+}
diff --git a/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java b/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java
new file mode 100644
index 0000000..99972c3
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/crypto/internal/SecureRandomSaltProviderTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.crypto.internal;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SecureRandomSaltProviderTest {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void testMissingConfiguration() throws IOException, NoSuchAlgorithmException {
+ final SecureRandomSaltProvider provider = new SecureRandomSaltProvider();
+ exception.expect(NullPointerException.class);
+ exception.expectMessage("Configuration must not be null");
+ provider.getSalt();
+ }
+
+ @Test
+ public void testComponentLifecycle() throws IOException, NoSuchAlgorithmException {
+ final SecureRandomSaltProvider provider = new SecureRandomSaltProvider();
+ { // activate
+ final SecureRandomSaltProviderConfiguration configuration = mock(SecureRandomSaltProviderConfiguration.class);
+ when(configuration.algorithm()).thenReturn("SHA1PRNG");
+ when(configuration.keyLength()).thenReturn(8);
+ provider.activate(configuration);
+ assertThat(provider.getSalt()).hasLength(8);
+ }
+ { // modified
+ final SecureRandomSaltProviderConfiguration configuration = mock(SecureRandomSaltProviderConfiguration.class);
+ when(configuration.algorithm()).thenReturn("SHA1PRNG");
+ when(configuration.keyLength()).thenReturn(16);
+ provider.modified(configuration);
+ assertThat(provider.getSalt()).hasLength(16);
+ }
+ { // deactivate
+ provider.deactivate();
+ assertThat(provider.getSalt()).hasLength(16);
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java b/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java
new file mode 100644
index 0000000..69f41e9
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/crypto/it/tests/SecureRandomSaltProviderIT.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.crypto.it.tests;
+
+import javax.inject.Inject;
+
+import org.apache.sling.commons.crypto.SaltProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.exam.util.Filter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SecureRandomSaltProviderIT extends CryptoTestSupport {
+
+ @Inject
+ @Filter(value = "(names=secure random)")
+ private SaltProvider saltProvider;
+
+ @Configuration
+ public Option[] configuration() {
+ return options(
+ baseConfiguration(),
+ factoryConfiguration("org.apache.sling.commons.crypto.internal.SecureRandomSaltProvider")
+ .put("names", "secure random")
+ .put("keyLength", 32)
+ .asOption()
+ );
+ }
+
+ @Test
+ public void testSaltProvider() {
+ assertThat(saltProvider).isNotNull();
+ }
+
+ @Test
+ public void testSalt() {
+ final byte[] salt = saltProvider.getSalt();
+ assertThat(salt).isNotNull();
+ assertThat(salt).hasLength(32);
+ }
+
+}