You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by wi...@apache.org on 2015/03/04 11:06:05 UTC

[21/50] [abbrv] git commit: updated refs/heads/reporter to 178a938

CLOUDSTACK-5237: Add a default PBKDF2-SHA-256 based authenticator

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
(cherry picked from commit 9533c54db669b22b268fcc21766e21c231e48d84)
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/6f4db0ce
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/6f4db0ce
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/6f4db0ce

Branch: refs/heads/reporter
Commit: 6f4db0ce4b493106eb4ac06737d3f2e9781ed535
Parents: d8e1bf1
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Fri Feb 27 15:45:06 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Fri Feb 27 15:53:58 2015 +0530

----------------------------------------------------------------------
 client/pom.xml                                  |   5 +
 .../core/spring-core-registry-core-context.xml  |   4 +-
 plugins/pom.xml                                 |   1 +
 plugins/user-authenticators/pbkdf2/pom.xml      |  29 ++++
 .../cloudstack/pbkdf2/module.properties         |  18 +++
 .../cloudstack/pbkdf2/spring-pbkdf2-context.xml |  32 +++++
 .../server/auth/PBKDF2UserAuthenticator.java    | 143 +++++++++++++++++++
 .../server/auth/PBKD2UserAuthenticatorTest.java |  75 ++++++++++
 8 files changed, 305 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/client/pom.xml
----------------------------------------------------------------------
diff --git a/client/pom.xml b/client/pom.xml
index 590a9ad..9453159 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -77,6 +77,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-plugin-user-authenticator-pbkdf2</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-plugin-user-authenticator-plaintext</artifactId>
       <version>${project.version}</version>
     </dependency>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
----------------------------------------------------------------------
diff --git a/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
index 939cffe..d967540 100644
--- a/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
+++ b/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
@@ -33,7 +33,7 @@
         class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
         <property name="orderConfigKey" value="user.authenticators.order" />
         <property name="excludeKey" value="user.authenticators.exclude" />
-        <property name="orderConfigDefault" value="SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT" />
+        <property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT" />
     </bean>
 
     <bean id="pluggableAPIAuthenticatorsRegistry"
@@ -47,7 +47,7 @@
         class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
         <property name="orderConfigKey" value="user.password.encoders.order" />
         <property name="excludeKey" value="user.password.encoders.exclude" />
-        <property name="orderConfigDefault" value="SHA256SALT,MD5,LDAP,PLAINTEXT" />
+        <property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT" />
     </bean>
 
     <bean id="securityCheckersRegistry"

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 76119dc..962ce46 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -71,6 +71,7 @@
     <module>storage-allocators/random</module>
     <module>user-authenticators/ldap</module>
     <module>user-authenticators/md5</module>
+    <module>user-authenticators/pbkdf2</module>
     <module>user-authenticators/plain-text</module>
     <module>user-authenticators/saml2</module>
     <module>user-authenticators/sha256salted</module>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/user-authenticators/pbkdf2/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/pbkdf2/pom.xml b/plugins/user-authenticators/pbkdf2/pom.xml
new file mode 100644
index 0000000..e656045
--- /dev/null
+++ b/plugins/user-authenticators/pbkdf2/pom.xml
@@ -0,0 +1,29 @@
+<!--
+  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.
+-->
+<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>
+  <artifactId>cloud-plugin-user-authenticator-pbkdf2</artifactId>
+  <name>Apache CloudStack Plugin - User Authenticator PBKDF2-SHA-256</name>
+  <parent>
+    <groupId>org.apache.cloudstack</groupId>
+    <artifactId>cloudstack-plugins</artifactId>
+    <version>4.5.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+</project>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/module.properties
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/module.properties b/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/module.properties
new file mode 100644
index 0000000..7c2b38d
--- /dev/null
+++ b/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/module.properties
@@ -0,0 +1,18 @@
+# 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.
+name=pbkdf2
+parent=api

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/spring-pbkdf2-context.xml
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/spring-pbkdf2-context.xml b/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/spring-pbkdf2-context.xml
new file mode 100644
index 0000000..a6272dd
--- /dev/null
+++ b/plugins/user-authenticators/pbkdf2/resources/META-INF/cloudstack/pbkdf2/spring-pbkdf2-context.xml
@@ -0,0 +1,32 @@
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+                      >
+    <bean id="PBKDF2UserAuthenticator" class="org.apache.cloudstack.server.auth.PBKDF2UserAuthenticator">
+        <property name="name" value="PBKDF2"/>
+    </bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/user-authenticators/pbkdf2/src/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/pbkdf2/src/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java b/plugins/user-authenticators/pbkdf2/src/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java
new file mode 100644
index 0000000..43c32c7
--- /dev/null
+++ b/plugins/user-authenticators/pbkdf2/src/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java
@@ -0,0 +1,143 @@
+//  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.cloudstack.server.auth;
+
+import com.cloud.server.auth.DefaultUserAuthenticator;
+import com.cloud.server.auth.UserAuthenticator;
+import com.cloud.user.UserAccount;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.utils.ConstantTimeComparator;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.encoders.Base64;
+
+import javax.ejb.Local;
+import javax.inject.Inject;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
+
+import static java.lang.String.format;
+
+@Local({UserAuthenticator.class})
+public class PBKDF2UserAuthenticator extends DefaultUserAuthenticator {
+    public static final Logger s_logger = Logger.getLogger(PBKDF2UserAuthenticator.class);
+    private static final int s_saltlen = 64;
+    private static final int s_rounds = 100000;
+    private static final int s_keylen = 512;
+
+    @Inject
+    private UserAccountDao _userAccountDao;
+
+    public Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Retrieving user: " + username);
+        }
+        boolean isValidUser = false;
+        UserAccount user = this._userAccountDao.getUserAccount(username, domainId);
+        if (user != null) {
+            isValidUser = true;
+        } else {
+            s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
+        }
+
+        byte[] salt = new byte[0];
+        int rounds = s_rounds;
+        try {
+            if (isValidUser) {
+                String[] storedPassword = user.getPassword().split(":");
+                if ((storedPassword.length != 3) || (!StringUtils.isNumeric(storedPassword[2]))) {
+                    s_logger.warn("The stored password for " + username + " isn't in the right format for this authenticator");
+                    isValidUser = false;
+                } else {
+                    // Encoding format = <salt>:<password hash>:<rounds>
+                    salt = decode(storedPassword[0]);
+                    rounds = Integer.parseInt(storedPassword[2]);
+                }
+            }
+            boolean result = false;
+            if (isValidUser && validateCredentials(password, salt)) {
+                result = ConstantTimeComparator.compareStrings(user.getPassword(), encode(password, salt, rounds));
+            }
+
+            UserAuthenticator.ActionOnFailedAuthentication action = null;
+            if ((!result) && (isValidUser)) {
+                action = UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT;
+            }
+            return new Pair(Boolean.valueOf(result), action);
+        } catch (NumberFormatException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        } catch (UnsupportedEncodingException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        } catch (InvalidKeySpecException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        }
+    }
+
+    public String encode(String password)
+    {
+        try
+        {
+            return encode(password, makeSalt(), s_rounds);
+        } catch (NoSuchAlgorithmException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        } catch (UnsupportedEncodingException e) {
+            throw new CloudRuntimeException("Unable to hash password", e);
+        } catch (InvalidKeySpecException e) {
+            s_logger.error("Exception in EncryptUtil.createKey ", e);
+            throw new CloudRuntimeException("Unable to hash password", e);
+        }
+    }
+
+    public String encode(String password, byte[] salt, int rounds)
+            throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException {
+        PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator();
+        generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(
+                        password.toCharArray()),
+                salt,
+                rounds);
+        return format("%s:%s:%d", encode(salt),
+                encode(((KeyParameter)generator.generateDerivedParameters(s_keylen)).getKey()), rounds);
+    }
+
+    public static byte[] makeSalt() throws NoSuchAlgorithmException {
+        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
+        byte[] salt = new byte[s_saltlen];
+        sr.nextBytes(salt);
+        return salt;
+    }
+
+    private static boolean validateCredentials(String plainPassword, byte[] hash) {
+        return !(plainPassword == null || plainPassword.isEmpty() || hash == null || hash.length == 0);
+    }
+
+    private static String encode(byte[] input) {
+        return new String(Base64.encode(input));
+    }
+
+    private static byte[] decode(String input) throws UnsupportedEncodingException {
+        return Base64.decode(input.getBytes("UTF-8"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6f4db0ce/plugins/user-authenticators/pbkdf2/test/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/pbkdf2/test/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java b/plugins/user-authenticators/pbkdf2/test/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java
new file mode 100644
index 0000000..f401416
--- /dev/null
+++ b/plugins/user-authenticators/pbkdf2/test/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java
@@ -0,0 +1,75 @@
+//  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.cloudstack.server.auth;
+
+import com.cloud.server.auth.UserAuthenticator;
+import com.cloud.user.UserAccountVO;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.utils.Pair;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.lang.reflect.Field;
+import java.security.NoSuchAlgorithmException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PBKD2UserAuthenticatorTest {
+    @Mock
+    UserAccountDao dao;
+
+    @Test
+    public void encodePasswordTest() {
+        PBKDF2UserAuthenticator authenticator = new PBKDF2UserAuthenticator();
+        String encodedPassword = authenticator.encode("password123ABCS!@#$%");
+        Assert.assertTrue(encodedPassword.length() < 255 && encodedPassword.length() >= 182);
+    }
+
+    @Test
+    public void saltTest() throws NoSuchAlgorithmException {
+        byte[] salt = new PBKDF2UserAuthenticator().makeSalt();
+        Assert.assertTrue(salt.length > 16);
+    }
+
+    @Test
+    public void authenticateValidTest() throws IllegalAccessException, NoSuchFieldException {
+        PBKDF2UserAuthenticator authenticator = new PBKDF2UserAuthenticator();
+        Field daoField = PBKDF2UserAuthenticator.class.getDeclaredField("_userAccountDao");
+        daoField.setAccessible(true);
+        daoField.set(authenticator, dao);
+        UserAccountVO account = new UserAccountVO();
+        account.setPassword("FMDMdx/2QjrZniqNRAgOAC1ai/CY/C+2kmKhp3vo+98pkqhO+AR6hCyUl0bOXtkq3XWqNiSQTwbi7KTiwuWhyw==:+u8T5LzCtikCPvKnUDn6JDezf1Hg2bood/ke5Oo93pz9s1eD9k/JLsa497Z3h9QWfOQfq0zvCRmkzfXMF913vQ==:4096");
+        Mockito.when(dao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(account);
+        Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null);
+        Assert.assertTrue(pair.first());
+    }
+
+    @Test
+    public void authenticateInValidTest() throws IllegalAccessException, NoSuchFieldException {
+        PBKDF2UserAuthenticator authenticator = new PBKDF2UserAuthenticator();
+        Field daoField = PBKDF2UserAuthenticator.class.getDeclaredField("_userAccountDao");
+        daoField.setAccessible(true);
+        daoField.set(authenticator, dao);
+        UserAccountVO account = new UserAccountVO();
+        account.setPassword("5f4dcc3b5aa765d61d8327deb882cf99");
+        Mockito.when(dao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(account);
+        Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null);
+        Assert.assertFalse(pair.first());
+    }
+}