This is an automated email from the ASF dual-hosted git repository. olli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-crypto.git
The following commit(s) were added to refs/heads/master by this push: new e0f0842 SLING-10500 Provide a secret key provider for password-based encryption e0f0842 is described below commit e0f084215ca832674f5acb1399f86552eefb3cc4 Author: Oliver Lietz <o...@apache.org> AuthorDate: Wed Jun 16 23:26:40 2021 +0200 SLING-10500 Provide a secret key provider for password-based encryption --- .../crypto/internal/PBESecretKeyProvider.java | 101 +++++++++++++++++++ .../PBESecretKeyProviderConfiguration.java | 55 +++++++++++ .../crypto/internal/PBESecretKeyProviderTest.java | 107 +++++++++++++++++++++ .../crypto/it/tests/PBESecretKeyProviderIT.java | 77 +++++++++++++++ 4 files changed, 340 insertions(+) diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProvider.java b/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProvider.java new file mode 100644 index 0000000..e96800d --- /dev/null +++ b/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProvider.java @@ -0,0 +1,101 @@ +/* + * 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.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Objects; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +import org.apache.sling.commons.crypto.PasswordProvider; +import org.apache.sling.commons.crypto.SaltProvider; +import org.apache.sling.commons.crypto.SecretKeyProvider; +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.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + property = { + Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons Crypto – PBE SecretKey Provider", + Constants.SERVICE_VENDOR + "=The Apache Software Foundation" + } +) +@Designate( + ocd = PBESecretKeyProviderConfiguration.class, + factory = true +) +public class PBESecretKeyProvider implements SecretKeyProvider { + + @Reference + protected volatile PasswordProvider passwordProvider; + + @Reference + protected volatile SaltProvider saltProvider; + + private PBESecretKeyProviderConfiguration configuration; + + private SecretKeyFactory factory; + + private final Logger logger = LoggerFactory.getLogger(PBESecretKeyProvider.class); + + public PBESecretKeyProvider() { // + } + + @Activate + protected void activate(final PBESecretKeyProviderConfiguration configuration) throws NoSuchAlgorithmException { + logger.debug("activating"); + this.configuration = configuration; + factory = SecretKeyFactory.getInstance(configuration.algorithm()); + } + + @Modified + protected void modified(final PBESecretKeyProviderConfiguration configuration) throws NoSuchAlgorithmException { + logger.debug("modifying"); + this.configuration = configuration; + factory = SecretKeyFactory.getInstance(configuration.algorithm()); + } + + @Deactivate + protected void deactivate() { + logger.debug("deactivating"); + } + + @Override + public @NotNull SecretKey getSecretKey() { + Objects.requireNonNull(configuration, "Configuration must not be null"); + try { + final KeySpec keySpec = new PBEKeySpec(passwordProvider.getPassword(), saltProvider.getSalt(), configuration.iterationCount(), configuration.keyLength()); + return factory.generateSecret(keySpec); + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderConfiguration.java b/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderConfiguration.java new file mode 100644 index 0000000..dae176a --- /dev/null +++ b/src/main/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderConfiguration.java @@ -0,0 +1,55 @@ +/* + * 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 “PBE SecretKey Provider”", + description = "Provides secret keys for password-based encryption (PBE)" +) +@interface PBESecretKeyProviderConfiguration { + + @AttributeDefinition( + name = "Names", + description = "names of this service", + required = false + ) + String[] names() default {}; + + @AttributeDefinition( + name = "Algorithm", + description = "standard name of the requested secret-key algorithm" + ) + String algorithm() default "PBKDF2WithHmacSHA1"; + + @AttributeDefinition( + name = "Iteration Count", + description = "iteration count" + ) + int iterationCount() default 1024; + + @AttributeDefinition( + name = "Key Length", + description = "to-be-derived key length" + ) + int keyLength() default 256; + +} diff --git a/src/test/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderTest.java b/src/test/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderTest.java new file mode 100644 index 0000000..bcc2a5f --- /dev/null +++ b/src/test/java/org/apache/sling/commons/crypto/internal/PBESecretKeyProviderTest.java @@ -0,0 +1,107 @@ +/* + * 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.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; + +import org.apache.sling.commons.crypto.PasswordProvider; +import org.apache.sling.commons.crypto.SaltProvider; +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 PBESecretKeyProviderTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void testMissingConfiguration() { + final PBESecretKeyProvider provider = new PBESecretKeyProvider(); + exception.expect(NullPointerException.class); + exception.expectMessage("Configuration must not be null"); + provider.getSecretKey(); + } + + @Test + public void testInvalidAlgorithm() throws NoSuchAlgorithmException { + final PBESecretKeyProvider provider = new PBESecretKeyProvider(); + final PBESecretKeyProviderConfiguration configuration = mock(PBESecretKeyProviderConfiguration.class); + when(configuration.algorithm()).thenReturn("Invalid"); + exception.expect(NoSuchAlgorithmException.class); + provider.activate(configuration); + } + + @Test + public void testInvalidKeySpec() throws NoSuchAlgorithmException { + final PasswordProvider passwordProvider = mock(PasswordProvider.class); + when(passwordProvider.getPassword()).thenReturn("+AQ?aDes!'DBMkrCi:FE6q\\sOn=Pbmn=PK8n=PK?".toCharArray()); + final SaltProvider saltProvider = mock(SaltProvider.class); + when(saltProvider.getSalt()).thenReturn("CAFEBABECAFEDEAD".getBytes(StandardCharsets.UTF_8)); + final PBESecretKeyProvider provider = new PBESecretKeyProvider(); + provider.passwordProvider = passwordProvider; + provider.saltProvider = saltProvider; + + final PBESecretKeyProviderConfiguration configuration = mock(PBESecretKeyProviderConfiguration.class); + when(configuration.algorithm()).thenReturn("PBKDF2WithHmacSHA1"); + when(configuration.iterationCount()).thenReturn(-1); + when(configuration.keyLength()).thenReturn(-1); + provider.activate(configuration); + + exception.expect(IllegalArgumentException.class); + provider.getSecretKey(); + } + + @Test + public void testComponentLifecycle() throws NoSuchAlgorithmException { + final PasswordProvider passwordProvider = mock(PasswordProvider.class); + when(passwordProvider.getPassword()).thenReturn("+AQ?aDes!'DBMkrCi:FE6q\\sOn=Pbmn=PK8n=PK?".toCharArray()); + final SaltProvider saltProvider = mock(SaltProvider.class); + when(saltProvider.getSalt()).thenReturn("CAFEBABECAFEDEAD".getBytes(StandardCharsets.UTF_8)); + final PBESecretKeyProvider provider = new PBESecretKeyProvider(); + provider.passwordProvider = passwordProvider; + provider.saltProvider = saltProvider; + { // activate + final PBESecretKeyProviderConfiguration configuration = mock(PBESecretKeyProviderConfiguration.class); + when(configuration.algorithm()).thenReturn("PBKDF2WithHmacSHA1"); + when(configuration.iterationCount()).thenReturn(1024); + when(configuration.keyLength()).thenReturn(128); + provider.activate(configuration); + assertThat(provider.getSecretKey().getAlgorithm()).isEqualTo("PBKDF2WithHmacSHA1"); + } + { // modified + final PBESecretKeyProviderConfiguration configuration = mock(PBESecretKeyProviderConfiguration.class); + when(configuration.algorithm()).thenReturn("PBKDF2WithHmacSHA256"); + when(configuration.iterationCount()).thenReturn(2048); + when(configuration.keyLength()).thenReturn(256); + provider.modified(configuration); + assertThat(provider.getSecretKey().getAlgorithm()).isEqualTo("PBKDF2WithHmacSHA256"); + } + { // deactivate + provider.deactivate(); + assertThat(provider.getSecretKey().getAlgorithm()).isEqualTo("PBKDF2WithHmacSHA256"); + } + } + +} diff --git a/src/test/java/org/apache/sling/commons/crypto/it/tests/PBESecretKeyProviderIT.java b/src/test/java/org/apache/sling/commons/crypto/it/tests/PBESecretKeyProviderIT.java new file mode 100644 index 0000000..66d3583 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/crypto/it/tests/PBESecretKeyProviderIT.java @@ -0,0 +1,77 @@ +/* + * 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.crypto.SecretKey; +import javax.inject.Inject; + +import org.apache.sling.commons.crypto.SecretKeyProvider; +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 org.ops4j.pax.exam.util.PathUtils; + +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 PBESecretKeyProviderIT extends CryptoTestSupport { + + @Inject + @Filter(value = "(names=messaging)") + private SecretKeyProvider secretKeyProvider; + + @Configuration + public Option[] configuration() { + final String path = String.format("%s/src/test/resources/password.utf8", PathUtils.getBaseDir()); + return options( + baseConfiguration(), + factoryConfiguration("org.apache.sling.commons.crypto.internal.FilePasswordProvider") + .put("path", path) + .asOption(), + factoryConfiguration("org.apache.sling.commons.crypto.internal.SecureRandomSaltProvider") + .put("keyLength", 16) + .asOption(), + factoryConfiguration("org.apache.sling.commons.crypto.internal.PBESecretKeyProvider") + .put("names", "messaging") + .put("algorithm", "PBKDF2WithHmacSHA256") + .asOption() + ); + } + + @Test + public void testSecretKeyProvider() { + assertThat(secretKeyProvider).isNotNull(); + } + + @Test + public void testSecretKey() { + final SecretKey secretKey = secretKeyProvider.getSecretKey(); + assertThat(secretKey).isNotNull(); + assertThat(secretKey.getAlgorithm()).isEqualTo("PBKDF2WithHmacSHA256"); + } + +}