mirror of https://github.com/apache/cloudstack.git
CLOUDSTACK-1870: Adding a SHA512Salted user authenticator plugin
Signed-off-by: Chip Childers <chip.childers@gmail.com>
This commit is contained in:
parent
6eac422943
commit
a18aaed097
|
|
@ -54,6 +54,7 @@
|
|||
<module>user-authenticators/md5</module>
|
||||
<module>user-authenticators/plain-text</module>
|
||||
<module>user-authenticators/sha256salted</module>
|
||||
<module>user-authenticators/sha513salted</module>
|
||||
<module>network-elements/dns-notifier</module>
|
||||
<module>storage/image/s3</module>
|
||||
<module>storage/volume/solidfire</module>
|
||||
|
|
|
|||
|
|
@ -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-sha512salted</artifactId>
|
||||
<name>Apache CloudStack Plugin - User Authenticator SHA512 Salted</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
</project>
|
||||
|
|
@ -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<String, Object> 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<String, Object[]> 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<String,Object>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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue