--- /dev/null
+module aaa-password-service-config {
+ yang-version 1;
+ namespace "urn:opendaylight:aaa:password:service:config";
+ prefix "aaa-password-service-config";
+ organization "OpenDaylight";
+
+ contact "ryandgoulding@gmail.com";
+
+ revision "2017-06-19" {
+ description "Initial password service configuration.";
+ }
+
+ container password-service-config {
+
+ leaf algorithm {
+ type string;
+ default "SHA-512";
+ description "The algorithm utilized for aaa-password-service hashing.";
+ }
+
+ leaf iterations {
+ type int32;
+ default 20000;
+ description "The number of times to hash.";
+ }
+
+ leaf private-salt {
+ type string;
+ description "The private salt for password hashing.";
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2017 Brocade Communications Systems and others. All rights reserved.
+
+This program and the accompanying materials are made available under the
+terms of the Eclipse Public License v1.0 which accompanies this distribution,
+and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-password-service-impl</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>ODL :: aaa :: ${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-password-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <!-- attach aaa-app-config.xml as an artifact -->
+ <artifact>
+ <file>${project.build.directory}/classes/initial/aaa-password-service-config.xml</file>
+ <type>xml</type>
+ <classifier>aaa-password-service-config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright © 2018 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.impl.password.service;
+
+import java.util.Optional;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.DefaultHashService;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.HashRequest;
+import org.apache.shiro.crypto.hash.SimpleHashRequest;
+import org.apache.shiro.util.ByteSource;
+import org.opendaylight.aaa.api.password.service.PasswordHash;
+import org.opendaylight.aaa.api.password.service.PasswordHashService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.password.service.config.rev170619.PasswordServiceConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultPasswordHashService implements PasswordHashService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultPasswordHashService.class);
+
+ public static final String DEFAULT_HASH_ALGORITHM = "SHA-512";
+ public static final int DEFAULT_NUM_ITERATIONS = 10000;
+
+ private final DefaultHashService hashService = new DefaultHashService();
+
+ public DefaultPasswordHashService(final PasswordServiceConfig passwordServiceConfig) {
+ final Optional<Integer> numIterationsOptional = Optional.ofNullable(passwordServiceConfig.getIterations());
+ setNumIterations(numIterationsOptional);
+
+ final Optional<String> hashAlgorithmOptional = Optional.ofNullable(passwordServiceConfig.getAlgorithm());
+ setHashAlgorithm(hashAlgorithmOptional);
+
+ final Optional<String> privateSaltOptional = Optional.ofNullable(passwordServiceConfig.getPrivateSalt());
+ setPrivateSalt(privateSaltOptional);
+
+ hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());
+ hashService.setGeneratePublicSalt(true);
+ }
+
+ private void setNumIterations(final Optional<Integer> numIterationsOptional) {
+ if (numIterationsOptional.isPresent()) {
+ final Integer numIterations = numIterationsOptional.get();
+ hashService.setHashIterations(numIterations);
+ LOG.info("DefaultPasswordHashService will utilize configured iteration count={}", numIterations);
+ } else {
+ hashService.setHashIterations(DEFAULT_NUM_ITERATIONS);
+ LOG.info("DefaultPasswordHashService will utilize default iteration count={}", DEFAULT_NUM_ITERATIONS);
+ }
+ }
+
+ private void setHashAlgorithm(final Optional<String> hashAlgorithmOptional) {
+ if (hashAlgorithmOptional.isPresent()) {
+ final String hashAlgorithm = hashAlgorithmOptional.get();
+ hashService.setHashAlgorithmName(hashAlgorithm);
+ LOG.info("DefaultPasswordHashService will utilize configured algorithm={}", hashAlgorithm);
+ } else {
+ hashService.setHashAlgorithmName(DEFAULT_HASH_ALGORITHM);
+ LOG.info("DefaultPasswordHashService will utilize default algorithm={}", DEFAULT_HASH_ALGORITHM);
+ }
+ }
+
+ private void setPrivateSalt(final Optional<String> privateSaltOptional) {
+ if (privateSaltOptional.isPresent()) {
+ hashService.setPrivateSalt(ByteSource.Util.bytes(privateSaltOptional.get()));
+ LOG.info("DefaultPasswordHashService will utilize a configured private salt");
+ } else {
+ LOG.info("DefaultPasswordHashService will not utilize a private salt, since none was configured");
+ }
+ }
+
+ @Override
+ public PasswordHash getPasswordHash(final String password) {
+ final HashRequest hashRequest = new HashRequest.Builder()
+ .setAlgorithmName(hashService.getHashAlgorithmName())
+ .setIterations(hashService.getHashIterations())
+ .setSource(ByteSource.Util.bytes(password)).build();
+
+ final Hash hash = hashService.computeHash(hashRequest);
+ return PasswordHashImpl.create(
+ hash.getAlgorithmName(),
+ hash.getSalt().toBase64(),
+ hash.getIterations(),
+ hash.toBase64());
+ }
+
+ @Override
+ public PasswordHash getPasswordHash(final String password, final String salt) {
+ final HashRequest hashRequest = new SimpleHashRequest(
+ hashService.getHashAlgorithmName(),
+ ByteSource.Util.bytes(password),
+ ByteSource.Util.bytes(Base64.decode(salt)),
+ hashService.getHashIterations());
+
+ final Hash hash = hashService.computeHash(hashRequest);
+ return PasswordHashImpl.create(
+ hash.getAlgorithmName(),
+ hash.getSalt().toBase64(),
+ hash.getIterations(),
+ hash.toBase64());
+ }
+
+ @Override
+ public boolean passwordsMatch(final String plaintext, final String stored, final String salt) {
+ final String hash = getPasswordHash(plaintext, salt).getHashedPassword();
+ return hash.equals(stored);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2018 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.impl.password.service;
+
+import org.opendaylight.aaa.api.password.service.PasswordHash;
+
+public final class PasswordHashImpl implements PasswordHash {
+
+ private final String algorithmName;
+ private final String salt;
+ private final int iterations;
+ private final String hashedPassword;
+
+ private PasswordHashImpl(final String algorithmName, final String salt, final int iterations,
+ final String hashedPassword) {
+
+ this.algorithmName = algorithmName;
+ this.salt = salt;
+ this.iterations = iterations;
+ this.hashedPassword = hashedPassword;
+ }
+
+ public static PasswordHash create(final String algorithmName, final String salt, final int iterations,
+ final String hashedPassword) {
+
+ return new PasswordHashImpl(algorithmName, salt, iterations, hashedPassword);
+ }
+
+ public String getAlgorithmName() {
+ return this.algorithmName;
+ }
+
+ public String getSalt() {
+ return this.salt;
+ }
+
+ public int getIterations() {
+ return this.iterations;
+ }
+
+ public String getHashedPassword() {
+ return this.hashedPassword;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<password-service-config xmlns="urn:opendaylight:aaa:password:service:config">
+ <algorithm>SHA-512</algorithm>
+ <iterations>20000</iterations>
+</password-service-config>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2018 Inocybe Technologies and others. All rights reserved.
+
+This program and the accompanying materials are made available under the
+terms of the Eclipse Public License v1.0 which accompanies this distribution,
+and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+ odl:use-default-for-reference-types="true">
+
+ <odl:clustered-app-config
+ binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.password.service.config.rev170619.PasswordServiceConfig"
+ id="passwordServiceConfig" default-config-file-name="aaa-password-service-config.xml" />
+
+ <bean id="passwordService" class="org.opendaylight.aaa.impl.password.service.DefaultPasswordHashService">
+ <argument ref="passwordServiceConfig" />
+ </bean>
+ <service ref="passwordService" interface="org.opendaylight.aaa.api.password.service.PasswordService"
+ odl:type="default"/>
+
+</blueprint>
--- /dev/null
+/*
+ * Copyright © 2018 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.impl.password.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.shiro.codec.Base64;
+import org.junit.Test;
+import org.opendaylight.aaa.api.password.service.PasswordHash;
+import org.opendaylight.aaa.api.password.service.PasswordHashService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.password.service.config.rev170619.PasswordServiceConfigBuilder;
+
+public class DefaultPasswordHashServiceTest {
+
+ @Test
+ public void testDefault() {
+ PasswordHashService hashService = new DefaultPasswordHashService(new PasswordServiceConfigBuilder().build());
+ PasswordHash hash = hashService.getPasswordHash("password");
+ assertEquals(DefaultPasswordHashService.DEFAULT_HASH_ALGORITHM, hash.getAlgorithmName());
+ assertEquals(DefaultPasswordHashService.DEFAULT_NUM_ITERATIONS, hash.getIterations());
+ assertNotNull(hash.getSalt());
+ assertNotNull(hash.getHashedPassword());
+
+
+ assertEquals(hash.getSalt(),
+ hashService.getPasswordHash("password", hash.getSalt()).getSalt());
+ // make sure that when utilizing the returned salt, the answer is the same
+ assertEquals(hash.getHashedPassword(),
+ hashService.getPasswordHash("password", hash.getSalt()).getHashedPassword());
+ }
+
+ @Test
+ public void testWithSpecialConfiguration() {
+ String privateSalt = Base64.encodeToString("somePrivateSalt".getBytes());
+ String publicSalt = Base64.encodeToString("somePublicSalt".getBytes());
+ DefaultPasswordHashService hashService = new DefaultPasswordHashService(new PasswordServiceConfigBuilder()
+ .setAlgorithm("MD5")
+ .setIterations(24)
+ .setPrivateSalt(privateSalt)
+ .build());
+ PasswordHash hash = hashService.getPasswordHash("password", publicSalt);
+ assertEquals("MD5", hash.getAlgorithmName());
+ assertEquals(24, hash.getIterations());
+ assertEquals(publicSalt, hash.getSalt());
+ assertNotNull(hash.getHashedPassword());
+ assertEquals(hash.getHashedPassword(),
+ hashService.getPasswordHash("password", hash.getSalt()).getHashedPassword());
+
+ hashService = new DefaultPasswordHashService(new PasswordServiceConfigBuilder()
+ .setAlgorithm("MD5")
+ .setIterations(20)
+ .build());
+ hash = hashService.getPasswordHash("password");
+ assertEquals(hash.getSalt(),
+ hashService.getPasswordHash("password", hash.getSalt()).getSalt());
+ // make sure that when utilizing the returned salt, the answer is the same
+ assertEquals(hash.getHashedPassword(),
+ hashService.getPasswordHash("password", hash.getSalt()).getHashedPassword());
+
+ assertEquals(hashService.getPasswordHash("password", privateSalt).getHashedPassword(),
+ hashService.getPasswordHash("password", privateSalt).getHashedPassword());
+ }
+
+ @Test
+ public void testPasswordsMatch() {
+ PasswordHashService hashService = new DefaultPasswordHashService(new PasswordServiceConfigBuilder().build());
+ PasswordHash passwordHash = hashService.getPasswordHash("password");
+ assertTrue(hashService.passwordsMatch("password",
+ passwordHash.getHashedPassword(), passwordHash.getSalt()));
+ }
+}
\ No newline at end of file
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-password-service-api</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
<parent>
<groupId>org.opendaylight.odlparent</groupId>
<modules>
<module>api</module>
- <!--<module>impl</module>-->
+ <module>impl</module>
</modules>
</project>
<artifactId>aaa-password-service-api</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-password-service-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-password-service-impl</artifactId>
+ <version>${project.version}</version>
+ <classifier>aaa-password-service-config</classifier>
+ <type>xml</type>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>features-aaa</artifactId>