/*
* Copyright (c) 2007, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.security.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSPasswordCallback;
import org.wso2.carbon.core.RegistryResources;
import org.wso2.carbon.core.util.CryptoException;
import org.wso2.carbon.core.util.CryptoUtil;
import org.wso2.carbon.core.util.KeyStoreManager;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.security.SecurityConfigException;
import org.wso2.carbon.security.SecurityConfigParams;
import org.wso2.carbon.security.SecurityConstants;
import org.wso2.carbon.security.SecurityServiceHolder;
import org.wso2.carbon.security.UserCredentialRetriever;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.security.KeyStore;
/**
* The password callback handler to be used to enable UsernameToken
* authentication for services.
*/
public class ServicePasswordCallbackHandler implements CallbackHandler {
private static final Log log = LogFactory.getLog(ServicePasswordCallbackHandler.class);
private String serviceGroupId = null;
private String serviceId = null;
private Registry registry = null;
private UserRealm realm = null;
private SecurityConfigParams configParams;
//todo there's a API change here. apparently only security component uses this. If not, change the invocations accordingly.
public ServicePasswordCallbackHandler(SecurityConfigParams configParams, String serviceGroupId,
String serviceId,
Registry registry, UserRealm realm)
throws RegistryException, SecurityConfigException {
this.registry = registry;
this.serviceId = serviceId;
this.serviceGroupId = serviceGroupId;
this.realm = realm;
this.configParams = configParams;
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
try {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof WSPasswordCallback) {
WSPasswordCallback passwordCallback = (WSPasswordCallback) callbacks[i];
String username = passwordCallback.getIdentifer();
String receivedPasswd = null;
switch (passwordCallback.getUsage()) {
case WSPasswordCallback.SIGNATURE:
case WSPasswordCallback.DECRYPT:
String password = getPrivateKeyPassword(username);
if (password == null) {
throw new UnsupportedCallbackException(callbacks[i],
"User not available " + "in a trusted store");
}
passwordCallback.setPassword(password);
break;
case WSPasswordCallback.KERBEROS_TOKEN:
passwordCallback.setPassword(getServicePrincipalPassword());
break;
case WSPasswordCallback.USERNAME_TOKEN_UNKNOWN:
receivedPasswd = passwordCallback.getPassword();
try {
if (receivedPasswd != null
&& this.authenticateUser(username, receivedPasswd)) {
// do nothing things are fine
} else {
throw new UnsupportedCallbackException(callbacks[i], "check failed");
}
} catch (Exception e) {
throw new UnsupportedCallbackException(callbacks[i],
"Check failed : System error");
}
break;
case WSPasswordCallback.USERNAME_TOKEN:
// In username token scenario, if user sends the digested password, callback handler needs to provide plain text password.
// We get plain text password through UserCredentialRetriever interface, which is implemented by custom user store managers.
// we expect username with domain name if user resides in a secondary user store, eg, WSO2.Test/fooUser.
// Additionally, secondary user stores needs to implement UserCredentialRetriever interface too
UserCredentialRetriever userCredentialRetriever;
String storedPassword = null;
String domainName = IdentityUtil.extractDomainFromName(username);
if (UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME.equals(domainName)) {
if (realm.getUserStoreManager() instanceof UserCredentialRetriever) {
userCredentialRetriever = (UserCredentialRetriever) realm.getUserStoreManager();
storedPassword = userCredentialRetriever.getPassword(username);
} else {
if (log.isDebugEnabled()) {
log.debug("Can not set user password in callback because primary userstore class" +
" has not implemented UserCredentialRetriever interface.");
}
}
} else {
if (realm.getUserStoreManager().getSecondaryUserStoreManager(domainName) instanceof UserCredentialRetriever) {
userCredentialRetriever = (UserCredentialRetriever) realm.getUserStoreManager().getSecondaryUserStoreManager(domainName);
storedPassword = userCredentialRetriever.getPassword(UserCoreUtil.removeDomainFromName(username));
} else {
if (log.isDebugEnabled()) {
log.debug("Can not set user password in callback because secondary userstore " +
"for domain:" + domainName + " has not implemented UserCredentialRetriever interface.");
}
}
}
if (storedPassword != null) {
try {
if (this.authenticateUser(username, storedPassword)) {
// do nothing things are fine
} else {
if (log.isDebugEnabled()) {
log.debug("User is not authorized!");
}
throw new UnsupportedCallbackException(callbacks[i], "check failed");
}
} catch (Exception e) {
throw new UnsupportedCallbackException(callbacks[i],
"Check failed : System error");
}
passwordCallback.setPassword(storedPassword);
break;
}
default:
/*
* When the password is null WS4J reports an error
* saying no password available for the user. But its
* better if we simply report authentication failure
* Therefore setting the password to be the empty string
* in this situation.
*/
passwordCallback.setPassword(receivedPasswd);
break;
}
} else {
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
} catch (UnsupportedCallbackException | IOException e) {
if (log.isDebugEnabled()) {
log.debug("Error in handling ServicePasswordCallbackHandler", e); //logging invlaid passwords and attempts
throw e;
}
throw e;
} catch (UserStoreException | SecurityConfigException e) {
log.error("Error in handling ServicePasswordCallbackHandler", e);
throw new UnsupportedCallbackException(null, e.getMessage());
} catch (Exception e) {
log.error("Error in handling ServicePasswordCallbackHandler", e);
//can't build an unsupported exception.
throw new UnsupportedCallbackException(null, e.getMessage());
}
}
private String getServicePrincipalPassword()
throws SecurityConfigException {
String password = configParams.getServerPrincipalPassword();
if (password != null) {
if (configParams.isServerPrincipalPasswordEncrypted()) {
password = getDecryptedPassword(password);
}
return password;
} else {
String msg = "Service principal password param not found";
log.error(msg);
throw new SecurityConfigException(msg);
}
}
private String getDecryptedPassword(String encryptedString) throws SecurityConfigException {
CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil();
try {
return new String(cryptoUtil.base64DecodeAndDecrypt(encryptedString));
} catch (CryptoException e) {
String msg = "Unable to decode and decrypt password string.";
log.error(msg, e);
throw new SecurityConfigException(msg, e);
}
}
public boolean authenticateUser(String user, String password) throws Exception {
boolean isAuthenticated = false;
boolean isAuthorized = false;
// verify whether user is in same tenant that service has been deployed.
if (realm.getUserStoreManager().getTenantId() !=
SecurityServiceHolder.getRealmService().getTenantManager().getTenantId(MultitenantUtils.getTenantDomain(user))) {
if (log.isDebugEnabled()) {
log.debug("User : " + user + " trying access service which is deployed in different tenant domain");
}
return false;
}
String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(user);
try {
isAuthenticated = realm.getUserStoreManager().authenticate(
tenantAwareUserName, password);
if (isAuthenticated) {
int index = tenantAwareUserName.indexOf("/");
if (index < 0) {
String domain = UserCoreUtil.getDomainFromThreadLocal();
if (domain != null) {
tenantAwareUserName = domain + "/" + tenantAwareUserName;
}
}
isAuthorized = realm.getAuthorizationManager()
.isUserAuthorized(tenantAwareUserName,
serviceGroupId + "/" + serviceId,
UserCoreConstants.INVOKE_SERVICE_PERMISSION);
}
return isAuthorized;
} catch (Exception e) {
log.error("Error in authenticating user.", e);
throw e;
}
}
private String getPrivateKeyPassword(String username) throws IOException, Exception {
String password = null;
int tenantId = ((UserRegistry) registry).getTenantId();
UserRegistry govRegistry = SecurityServiceHolder.getRegistryService().
getGovernanceSystemRegistry(tenantId);
try {
KeyStoreManager keyMan = KeyStoreManager.getInstance(tenantId);
if (govRegistry.resourceExists(SecurityConstants.KEY_STORES)) {
Collection collection = (Collection) govRegistry.get(SecurityConstants.KEY_STORES);
String[] ks = collection.getChildren();
for (int i = 0; i < ks.length; i++) {
String fullname = ks[i];
//get the primary keystore, only if it is super tenant.
if (tenantId == MultitenantConstants.SUPER_TENANT_ID && fullname
.equals(RegistryResources.SecurityManagement.PRIMARY_KEYSTORE_PHANTOM_RESOURCE)) {
KeyStore store = keyMan.getPrimaryKeyStore();
if (store.containsAlias(username)) {
password = keyMan.getPrimaryPrivateKeyPasssword();
break;
}
} else {
String name = fullname.substring(fullname.lastIndexOf("/") + 1);
KeyStore store = null;
//Not all the keystores encrypted using primary keystore password. So, some of the keystores will fail while loading
try {
store = keyMan.getKeyStore(name);
} catch (Exception e) {
log.debug("Failed to load keystore " + name, e);
}
if (store.containsAlias(username)) {
Resource resource = (Resource) govRegistry.get(ks[i]);
CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil();
String encryptedPassword = resource
.getProperty(SecurityConstants.PROP_PRIVATE_KEY_PASS);
password = new String(cryptoUtil
.base64DecodeAndDecrypt(encryptedPassword));
break;
}
}
}
}
} catch (IOException e) {
log.error("Error when getting PrivateKeyPassword.", e);
throw e;
} catch (Exception e) {
log.error("Error when getting PrivateKeyPassword.", e);
throw e;
}
return password;
}
}