From a18aaed097e977ca65c0fc4a956beb11058ad179 Mon Sep 17 00:00:00 2001 From: Chip Childers Date: Mon, 1 Apr 2013 16:34:41 -0400 Subject: [PATCH] CLOUDSTACK-1870: Adding a SHA512Salted user authenticator plugin Signed-off-by: Chip Childers --- plugins/pom.xml | 1 + .../user-authenticators/sha512salted/pom.xml | 29 +++++ .../auth/SHA512SaltedUserAuthenticator.java | 120 ++++++++++++++++++ .../server/auth/test/AuthenticatorTest.java | 63 +++++++++ 4 files changed, 213 insertions(+) create mode 100644 plugins/user-authenticators/sha512salted/pom.xml create mode 100644 plugins/user-authenticators/sha512salted/src/com/cloud/server/auth/SHA512SaltedUserAuthenticator.java create mode 100644 plugins/user-authenticators/sha512salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java diff --git a/plugins/pom.xml b/plugins/pom.xml index d7e8debbf02..c5d3a2c9b78 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -54,6 +54,7 @@ user-authenticators/md5 user-authenticators/plain-text user-authenticators/sha256salted + user-authenticators/sha513salted network-elements/dns-notifier storage/image/s3 storage/volume/solidfire diff --git a/plugins/user-authenticators/sha512salted/pom.xml b/plugins/user-authenticators/sha512salted/pom.xml new file mode 100644 index 00000000000..8ff1454888f --- /dev/null +++ b/plugins/user-authenticators/sha512salted/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + cloud-plugin-user-authenticator-sha512salted + Apache CloudStack Plugin - User Authenticator SHA512 Salted + + org.apache.cloudstack + cloudstack-plugins + 4.2.0-SNAPSHOT + ../../pom.xml + + diff --git a/plugins/user-authenticators/sha512salted/src/com/cloud/server/auth/SHA512SaltedUserAuthenticator.java b/plugins/user-authenticators/sha512salted/src/com/cloud/server/auth/SHA512SaltedUserAuthenticator.java new file mode 100644 index 00000000000..abb38c1a390 --- /dev/null +++ b/plugins/user-authenticators/sha512salted/src/com/cloud/server/auth/SHA512SaltedUserAuthenticator.java @@ -0,0 +1,120 @@ +// 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 com.cloud.server.auth; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; + +import com.cloud.user.UserAccount; +import com.cloud.user.dao.UserAccountDao; +import com.cloud.utils.exception.CloudRuntimeException; + +@Local(value={UserAuthenticator.class}) +public class SHA512SaltedUserAuthenticator extends DefaultUserAuthenticator { + public static final Logger s_logger = Logger.getLogger(SHA512SaltedUserAuthenticator.class); + + @Inject + private UserAccountDao _userAccountDao; + private static int s_saltlen = 20; + + @Override + public boolean configure(String name, Map params) + throws ConfigurationException { + super.configure(name, params); + return true; + } + + /* (non-Javadoc) + * @see com.cloud.server.auth.UserAuthenticator#authenticate(java.lang.String, java.lang.String, java.lang.Long, java.util.Map) + */ + @Override + public boolean authenticate(String username, String password, + Long domainId, Map requestParameters) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Retrieving user: " + username); + } + UserAccount user = _userAccountDao.getUserAccount(username, domainId); + if (user == null) { + s_logger.debug("Unable to find user with " + username + " in domain " + domainId); + return false; + } + + try { + String storedPassword[] = user.getPassword().split(":"); + if (storedPassword.length != 2) { + s_logger.warn("The stored password for " + username + " isn't in the right format for this authenticator"); + return false; + } + byte salt[] = Base64.decode(storedPassword[0]); + String hashedPassword = encode(password, salt); + return storedPassword[1].equals(hashedPassword); + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } catch (UnsupportedEncodingException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } + } + + /* (non-Javadoc) + * @see com.cloud.server.auth.UserAuthenticator#encode(java.lang.String) + */ + @Override + public String encode(String password) { + // 1. Generate the salt + SecureRandom randomGen; + try { + randomGen = SecureRandom.getInstance("SHA1PRNG"); + + byte salt[] = new byte[s_saltlen]; + randomGen.nextBytes(salt); + + String saltString = new String(Base64.encode(salt)); + String hashString = encode(password, salt); + + // 3. concatenate the two and return + return saltString + ":" + hashString; + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } catch (UnsupportedEncodingException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } + } + + public String encode(String password, byte[] salt) throws UnsupportedEncodingException, NoSuchAlgorithmException { + byte[] passwordBytes = password.getBytes("UTF-8"); + byte[] hashSource = new byte[passwordBytes.length + s_saltlen]; + System.arraycopy(passwordBytes, 0, hashSource, 0, passwordBytes.length); + System.arraycopy(salt, 0, hashSource, passwordBytes.length, s_saltlen); + + // 2. Hash the password with the salt + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(hashSource); + byte[] digest = md.digest(); + + return new String(Base64.encode(digest)); + } +} diff --git a/plugins/user-authenticators/sha512salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java b/plugins/user-authenticators/sha512salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java new file mode 100644 index 00000000000..c4c82e903ce --- /dev/null +++ b/plugins/user-authenticators/sha512salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java @@ -0,0 +1,63 @@ +// 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 src.com.cloud.server.auth.test; + +import static org.junit.Assert.*; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; + +import javax.naming.ConfigurationException; + +import org.bouncycastle.util.encoders.Base64; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.server.auth.SHA512SaltedUserAuthenticator; + +public class AuthenticatorTest { + + @Before + public void setUp() throws Exception { + } + + @Test + public void testEncode() throws UnsupportedEncodingException, NoSuchAlgorithmException { + SHA512SaltedUserAuthenticator authenticator = + new SHA512SaltedUserAuthenticator(); + + try { + authenticator.configure("SHA512", Collections.emptyMap()); + } catch (ConfigurationException e) { + fail(e.toString()); + } + + String encodedPassword = authenticator.encode("password"); + + String storedPassword[] = encodedPassword.split(":"); + assertEquals ("hash must consist of two components", storedPassword.length, 2); + + byte salt[] = Base64.decode(storedPassword[0]); + String hashedPassword = authenticator.encode("password", salt); + + assertEquals("compare hashes", storedPassword[1], hashedPassword); + + } + +}