/*
* Copyright (c) 2005-2010, 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.identity.provider;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.dom.factory.OMDOMFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.rahas.RahasData;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.components.crypto.X509NameTokenizer;
import org.apache.xml.security.utils.Base64;
import org.apache.xml.security.utils.Constants;
import org.w3c.dom.Element;
import org.wso2.carbon.base.ServerConfiguration;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.IdentityClaimManager;
import org.wso2.carbon.identity.core.model.RelyingPartyDO;
import org.wso2.carbon.identity.core.model.UserTrustedRPDO;
import org.wso2.carbon.identity.core.persistence.IdentityPersistenceManager;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.core.util.KeyUtil;
import org.wso2.carbon.identity.provider.admin.RelyingPartyAdmin;
import org.wso2.carbon.identity.provider.internal.IdentityProviderServiceComponent;
import org.wso2.carbon.security.keystore.KeyStoreAdmin;
import org.wso2.carbon.security.keystore.service.CertData;
import org.wso2.carbon.security.keystore.service.CertDataDetail;
import org.wso2.carbon.security.keystore.service.KeyStoreData;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.user.core.claim.Claim;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
public class GenericIdentityProviderData {
private static Log log = LogFactory.getLog(GenericIdentityProviderData.class);
public static final String USERMAN_SERVICE = "UserManServiceURL";
public static final String USER_CLASS = "UserClass";
public String userClass = null;
protected String cardID = null;
protected Map<String, RequestedClaimData> requestedClaims = new HashMap<String, RequestedClaimData>();
protected Map<String, Claim> supportedClaims = new HashMap<String, Claim>();
protected String displayTokenLang = null;
protected int authMechanism = -1;
protected X509Certificate rpCert;
private static KeyStore cacerts;
protected String userIdentifier = null;
protected String requiredTokenType = null;
/**
* Populate CardSpace specific meta-data.
*
* @param data WS-Trust information in the issue request.
* @throws IdentityProviderException
* @throws ClassNotFoundException
*/
public GenericIdentityProviderData(RahasData data) throws IdentityProviderException,
ClassNotFoundException {
OMElement rstElem = null;
OMElement claimElem = null;
rstElem = data.getRstElement();
claimElem = data.getClaimElem();
readAuthenticationMechanism(data);
processUserIdentifier(data);
loadClaims();
processClaimData(data, claimElem);
processInfoCardReference(rstElem);
readRequestedTokenType(data);
populateClaimValues(data);
extracAndValidatetRPCert(data);
}
/**
*
* @param requestedClaims
*/
public void setRequestedClaims(Map<String, RequestedClaimData> requestedClaims) {
this.requestedClaims = requestedClaims;
}
public String getRequiredTokenType() {
return requiredTokenType;
}
public void setRequiredTokenType(String requiredTokenType) {
if (requiredTokenType == null || requiredTokenType.trim().length() == 0) {
requiredTokenType = getDefautTokenType();
}
this.requiredTokenType = requiredTokenType;
}
/**
* This parameter specifies the type of the token to be requested from the STS as a URI. This
* parameter can be omitted if the STS and the Web site front-end have a mutual understanding
* about what token type will be provided, or if the Web site is willing to accept any token
* type.
*
* @return Default Token Type
*/
public String getDefautTokenType() {
return IdentityConstants.SAML10_URL;
}
public X509Certificate getRpCert() {
return rpCert;
}
public String getUserIdentifier() {
return null;
}
public String getCardID() {
return null;
}
public String getDisplayTokenLang() {
return null;
}
public String getDisplayName(String URI) {
return null;
}
public String getTenantDomain() throws IdentityProviderException {
return null;
}
/**
*
* @throws IdentityProviderException
*/
protected void loadClaims() throws IdentityProviderException {
IdentityClaimManager claimManager = null;
Claim[] claims = null;
if (log.isDebugEnabled()) {
log.debug("Loading claims");
}
try {
claimManager = IdentityClaimManager.getInstance();
claims = claimManager.getAllSupportedClaims(IdentityConstants.INFOCARD_DIALECT,IdentityTenantUtil
.getRealm(null, userIdentifier));
for (int i = 0; i < claims.length; i++) {
Claim temp = claims[i];
supportedClaims.put(temp.getClaimUri(), temp);
}
Claim tenant = new Claim();
tenant.setClaimUri(IdentityConstants.CLAIM_TENANT_DOMAIN);
tenant.setDescription("Tenant");
tenant.setDisplayTag("Tenant");
tenant.setSupportedByDefault(true);
tenant.setDialectURI("http://wso2.org");
supportedClaims.put(tenant.getClaimUri(), tenant);
} catch (IdentityException e) {
log.error("Error while loading claims", e);
throw new IdentityProviderException("Error while loading claims", e);
}
}
/**
*
* @param rahasData
* @param claims
* @throws IdentityProviderException
*/
protected void processClaimData(RahasData rahasData, OMElement claims)
throws IdentityProviderException {
if (claims == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Processing claim data");
}
Iterator iterator = null;
iterator = claims.getChildrenWithName(new QName(IdentityConstants.NS,
IdentityConstants.LocalNames.IDENTITY_CLAIM_TYPE));
while (iterator.hasNext()) {
OMElement omElem = null;
RequestedClaimData claim = null;
String uriClaim = null;
String optional = null;
omElem = (OMElement) iterator.next();
claim = getRequestedClaim();
uriClaim = omElem.getAttributeValue(new QName(null, "Uri"));
if (uriClaim == null) {
log.error("Empty claim uri found while procession claim data");
throw new IdentityProviderException(
"Empty claim uri found while procession claim data");
}
claim.setUri(uriClaim);
optional = (omElem.getAttributeValue(new QName(null, "Optional")));
if (optional != null) {
claim.setBOptional((optional.equals("true")) ? true : false);
} else {
claim.setBOptional(true);
}
requestedClaims.put(claim.getUri(), claim);
}
}
/**
*
* @param rst
* @throws IdentityProviderException
*/
protected void processInfoCardReference(OMElement rst) throws IdentityProviderException {
// In the generic case we have nothing to do here.
}
/**
*
* @param data
* @throws IdentityProviderException
*/
protected void readAuthenticationMechanism(RahasData data) throws IdentityProviderException {
// In the generic case we have nothing to do here.
}
/**
* Extract the relying party certificate and validate it.
*
* @param data Information in the RST extracted by Rahas.
*/
protected void extracAndValidatetRPCert(RahasData data) throws IdentityProviderException {
OMElement eprElem = null;
OMElement idElem = null;
if (log.isDebugEnabled()) {
log.debug("Extracting and validating relying party cert");
}
eprElem = data.getAppliesToEpr();
idElem = eprElem.getFirstChildWithName(new QName(IdentityConstants.IDENTITY_ADDRESSING_NS,
IdentityConstants.LocalNames.IDENTITY));
if (idElem != null) {
OMElement keyInfoElem = null;
keyInfoElem = idElem.getFirstChildWithName(new QName(WSConstants.SIG_NS, "KeyInfo"));
if (keyInfoElem != null) {
OMElement x509DataElem = null;
x509DataElem = keyInfoElem.getFirstChildWithName(new QName(WSConstants.SIG_NS,
"X509Data"));
if (x509DataElem != null) {
OMElement x509CertElem = null;
x509CertElem = x509DataElem.getFirstChildWithName(new QName(WSConstants.SIG_NS,
"X509Certificate"));
if (x509CertElem != null) {
String base64Cert = null;
base64Cert = x509CertElem.getText();
try {
ByteArrayInputStream bais = null;
X509Certificate receivedCert = null;
bais = new ByteArrayInputStream(Base64.decode(base64Cert));
receivedCert = (X509Certificate) CertificateFactory
.getInstance("X.509").generateCertificate(bais);
// Validate the rpCert
if (cacerts == null) {
String cacertsPath = null;
InputStream cacertsIs = null;
cacertsPath = System.getProperty("java.home")
+ "/lib/security/cacerts";
cacertsIs = new FileInputStream(cacertsPath);
cacerts = KeyStore.getInstance(KeyStore.getDefaultType());
cacerts.load(cacertsIs, "changeit".toCharArray());
cacertsIs.close();
}
// Check whether the issuer of the RP certificate is a known CA
Principal issuer = null;
Vector issuerRDN = null;
Enumeration aliasEnum = null;
issuer = receivedCert.getIssuerDN();
issuerRDN = splitAndTrim(issuer.getName());
aliasEnum = cacerts.aliases();
boolean trusted = false;
while (aliasEnum.hasMoreElements()) {
String alilas = null;
alilas = (String) aliasEnum.nextElement();
if (cacerts.isCertificateEntry(alilas)) {
X509Certificate cert = null;
Vector certRDN = null;
cert = (X509Certificate) cacerts.getCertificate(alilas);
certRDN = splitAndTrim(cert.getSubjectDN().getName());
if (certRDN.equals(issuerRDN)) {
RelyingPartyAdmin rpAdmin = null;
String hostName = null;
// We have the issuer certificate in our trusted certificate
trusted = true;
this.rpCert = receivedCert;
// If this relying party is not registered register it in
// the DB
rpAdmin = new RelyingPartyAdmin(userIdentifier);
hostName = IdentityProviderUtil.getAppliesToHostName(data);
if (rpAdmin.getRelyingParty(hostName) == null) {
rpAdmin.createGloabllyTrustedRelyingParty(hostName);
}
break;
}
}
}
if (!trusted) {
// RP certificate is not issued by a trusted CA
X509Certificate cert = this.readRpCertFromStores(data);
if (cert == null) {
// We do not trust the RP
log.error("Relying party not trusted");
throw new IdentityProviderException("Relying party not trusted");
}
// Match the certificate to the incoming certificate to validate
if (cert.equals(receivedCert)) {
this.rpCert = cert;
} else {
log.error("Received cert mismatch with trusted cert");
throw new IdentityProviderException(
"Received cert mismatch with trusted cert");
}
}
} catch (IdentityProviderException e) {
throw e;
} catch (RuntimeException e){
throw e;
} catch (Exception e) {
// all other exception
log.error("Malformed Base64 certificate");
throw new IdentityProviderException("Malformed Base64 certificate");
}
} else {
log.error("Malformed element");
throw new IdentityProviderException("Malformed element");
}
} else {
log.error("Malformed element");
throw new IdentityProviderException("Malformed element");
}
} else {
log.error("Malformed element");
throw new IdentityProviderException("Malformed element");
}
} else {
log.error("Missing identity element");
throw new IdentityProviderException("Missing identity element");
}
}
/**
* Obtain the user identifier depending on the authentication mechanism used.
*
* @param rahasData
*/
protected void processUserIdentifier(RahasData rahasData) throws IdentityProviderException {
if (log.isDebugEnabled()) {
log.debug("Processing user identifier");
}
userIdentifier = rahasData.getPrincipal().getName();
}
/**
*
* @param rahasData
* @throws IdentityProviderException
*/
protected void populateClaimValues(RahasData rahasData) throws IdentityProviderException {
UserStoreManager connector = null;
if (log.isDebugEnabled()) {
log.debug("Populating claim values");
}
try {
connector = IdentityTenantUtil.getRealm(null, userIdentifier).getUserStoreManager();
} catch (Exception e) {
log.error("Error while instantiating IdentityUserStore", e);
throw new IdentityProviderException("Error while instantiating IdentityUserStore", e);
}
// get the column names for the URIs
Iterator<RequestedClaimData> ite = requestedClaims.values().iterator();
List<String> claimList = new ArrayList<String>();
while (ite.hasNext()) {
RequestedClaimData claim = (RequestedClaimData) ite.next();
if (claim != null && !claim.getUri().equals(IdentityConstants.CLAIM_PPID)
&& !claim.getUri().equals(IdentityConstants.CLAIM_TENANT_DOMAIN)) {
claimList.add(claim.getUri());
}
}
String[] claims = new String[claimList.size()];
String userId = UserCoreUtil.getTenantLessUsername(userIdentifier);
Map<String, String> mapValues = null;
try {
mapValues = connector.getUserClaimValues(userId, claimList.toArray(claims), null);
} catch (Exception e) {
throw new IdentityProviderException(e.getMessage(), e);
}
ite = requestedClaims.values().iterator();
while (ite.hasNext()) {
RequestedClaimData claimData = ite.next();
if (IdentityConstants.CLAIM_TENANT_DOMAIN.equals(claimData.getUri())) {
String domainName = null;
try {
domainName = UserCoreUtil.getTenantDomain(IdentityProviderServiceComponent.getRealmService(),userIdentifier);
} catch (UserStoreException e) {
throw new IdentityProviderException(e.getMessage(), e);
}
if (domainName == null) {
domainName = IdentityConstants.DEFAULT_SUPER_TENAT;
}
claimData.setValue(domainName);
} else {
claimData.setValue(mapValues.get(claimData.getUri()));
}
}
}
/**
* Validate the given ds:KeyInfo element against the stored ds:KeyInfo element.
*
* @param issuerInfo Stored ds:KeyInfo element as a <code>java.lang.String</code>.
* @param keyInfo The incoming ds:KeyInfo element as a <code>org.w3c.dom.Element</code>.
* @return true if the information matches, otherwise false.
*/
protected boolean validateKeyInfo(String issuerInfo, Element keyInfo)
throws IdentityProviderException {
if (log.isDebugEnabled()) {
log.debug("Validating key info");
}
try {
OMElement elem = new StAXOMBuilder(new ByteArrayInputStream(issuerInfo.getBytes()))
.getDocumentElement();
OMElement keyValueElem = elem.getFirstElement();
if (keyValueElem != null
&& keyValueElem.getQName().equals(
new QName(WSConstants.SIG_NS, Constants._TAG_KEYVALUE))) {
// KeyValue structure : expect an RSAKeyValue
OMElement rsaKeyValueElem = keyValueElem.getFirstElement();
if (rsaKeyValueElem != null
&& rsaKeyValueElem.getQName().equals(
new QName(WSConstants.SIG_NS, Constants._TAG_RSAKEYVALUE))) {
String modulus = rsaKeyValueElem.getFirstChildWithName(
new QName(WSConstants.SIG_NS, Constants._TAG_MODULUS)).getText().trim();
String exponent = rsaKeyValueElem.getFirstChildWithName(
new QName(WSConstants.SIG_NS, Constants._TAG_EXPONENT)).getText()
.trim();
// Now process the incoming element to check for ds:RSAKeyValue
OMElement receivedKeyInfoElem = (OMElement) new OMDOMFactory().getDocument()
.importNode(keyInfo, true);
OMElement receivedKeyValueElem = receivedKeyInfoElem.getFirstElement();
if (receivedKeyValueElem != null
&& receivedKeyValueElem.getQName().equals(
new QName(WSConstants.SIG_NS, Constants._TAG_KEYVALUE))) {
OMElement receivedRsaKeyValueElem = receivedKeyValueElem
.getFirstChildWithName(new QName(WSConstants.SIG_NS,
Constants._TAG_RSAKEYVALUE));
if (receivedRsaKeyValueElem != null) {
// Obtain incoming mod and exp
String receivedModulus = receivedRsaKeyValueElem.getFirstChildWithName(
new QName(WSConstants.SIG_NS, Constants._TAG_MODULUS))
.getText().trim();
String receivedExponent = receivedRsaKeyValueElem
.getFirstChildWithName(
new QName(WSConstants.SIG_NS, Constants._TAG_EXPONENT))
.getText().trim();
// Compare
return modulus.equals(receivedModulus)
&& exponent.equals(receivedExponent);
} else {
log.error("Unknown received KeyInfo type");
throw new IdentityProviderException("Unknown received KeyInfo type");
}
} else {
log.error("Unknown received KeyInfo type");
throw new IdentityProviderException("Unknown received KeyInfo type");
}
} else {
log.error("Error while instantiating IdentityUserStore");
throw new IdentityProviderException("Unknown received KeyInfo type");
}
} else {
log.error("Unknown stored KeyInfo type");
throw new IdentityProviderException("Unknown stored KeyInfo type");
}
} catch (XMLStreamException e) {
log.error("Error parsing stored KeyInfo");
throw new IdentityProviderException("Error parsing stored KeyInfo");
}
}
/**
* Retrieves the Trusted RP certificate from Key store. This certificate is not signed by an
* accepted CA
*
* @param data
* @return
* @throws IdentityProviderException
*/
protected X509Certificate readRpCertFromStores(RahasData data) throws IdentityProviderException {
X509Certificate cert = null;
IdentityPersistenceManager dbman = null;
String host = null;
RelyingPartyDO rp = null;
String alias = null;
String keyStoreName = null;
ServerConfiguration serverConfig = null;
host = IdentityProviderUtil.getAppliesToHostName(data);
serverConfig = ServerConfiguration.getInstance();
String userName = UserCoreUtil.getTenantLessUsername(userIdentifier);
try {
dbman = IdentityPersistenceManager.getPersistanceManager();
} catch (Exception e) {
log.error("Error while instantiating IdentityUserStore", e);
throw new IdentityProviderException("Error while instantiating IdentityUserStore", e);
}
try {
rp = dbman.getGloballyTrustedRelyingParty(IdentityTenantUtil
.getRegistry(null, userName), host);
} catch (Exception ignore) {
// Lets proceed with the user trusted relying parties.
}
keyStoreName = serverConfig.getFirstProperty("Security.KeyStore.Location");
if (rp != null) {
// This is a globally trusted RP
alias = rp.getAlias();
// Get key store name
try {
cert = KeyUtil.getCertificate(new File(keyStoreName).getName(), alias);
} catch (IdentityException e) {
log.error("Error while retrieving cert from the key store", e);
throw new IdentityProviderException(
"Error while retrieving cert from the key store", e);
}
} else {
// If the RP is not trusted globally then check personal RPs
try {
UserTrustedRPDO userRp = dbman.getUserTrustedRelyingParty(IdentityTenantUtil
.getRegistry(null,userName), userName, host);
if (userRp != null) {
alias = userRp.getIdentifier().getHostName();
cert = getCertificateFromUserTrustedRP(alias);
}
} catch (Exception e) {
log.error("Error while retrieving cert from user trusted relying party", e);
throw new IdentityProviderException(
"Error while retrieving cert from user trusted relying party", e);
}
}
return cert;
}
/**
*
* @param alias
* @return
* @throws IdentityException
*/
protected X509Certificate getCertificateFromUserTrustedRP(String alias)
throws IdentityException {
KeyStoreAdmin keyAdmin = null;
String storeFilePath = null;
KeyStoreData keyStoreData = null;
CertData[] certs = null;
try {
storeFilePath = IdentityUtil
.getProperty(IdentityConstants.ServerConfig.USER_TRUSTED_RP_STORE_LOCATION);
keyAdmin = new KeyStoreAdmin(IdentityTenantUtil.getRegistry(null, null));
keyAdmin.setIncludeCert(true);
keyStoreData = keyAdmin.getKeystoreInfo(new File(storeFilePath).getName());
certs = keyStoreData.getCerts();
for (CertData certData : certs) {
if (certData.getAlias().equals(alias)) {
return ((CertDataDetail) certData).getCertificate();
}
}
} catch (Exception e) {
log.error("Error occured while reading certificate from the user trusted key store", e);
throw new IdentityException(
"Error occured while reading certificate from the user trusted key store", e);
}
return null;
}
protected void readRequestedTokenType(RahasData data) {
requiredTokenType = data.getTokenType();
if (requiredTokenType == null || requiredTokenType.trim().length() == 0) {
requiredTokenType = getDefautTokenType();
}
}
public Map<String, RequestedClaimData> getRequestedClaims() {
return requestedClaims;
}
protected RequestedClaimData getRequestedClaim() {
return new RequestedClaimData();
}
private Vector splitAndTrim(String inString) {
X509NameTokenizer nmTokens = new X509NameTokenizer(inString);
Vector vr = new Vector();
if (log.isDebugEnabled()) {
log.debug("");
}
while (nmTokens.hasMoreTokens()) {
vr.add(nmTokens.nextToken());
}
java.util.Collections.sort(vr);
return vr;
}
}