/*
* 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.sts;
import java.io.ByteArrayInputStream;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.util.UUIDGenerator;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axis2.context.MessageContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.rahas.RahasConstants;
import org.apache.rahas.RahasData;
import org.apache.rahas.Token;
import org.apache.rahas.TokenIssuer;
import org.apache.rahas.TrustException;
import org.apache.rahas.TrustUtil;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.message.WSSecEncryptedKey;
import org.apache.ws.security.message.token.SecurityTokenReference;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.ws.security.util.XmlSchemaDateFormat;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.keys.KeyInfo;
import org.joda.time.DateTime;
import org.opensaml.SAMLException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.persistence.IdentityPersistenceManager;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.provider.GenericIdentityProviderData;
import org.wso2.carbon.identity.provider.IdentityProviderException;
import org.wso2.carbon.identity.provider.IdentityProviderUtil;
import org.wso2.carbon.identity.provider.saml.SAML1TokenBuilder;
import org.wso2.carbon.identity.provider.saml.SAML2TokenBuilder;
import org.wso2.carbon.identity.provider.saml.SAMLTokenBuilder;
import org.wso2.carbon.identity.provider.saml.SAMLTokenDirector;
public class GenericTokenIssuer implements TokenIssuer {
private static Log log = LogFactory.getLog(IdentityTokenIssuer.class);
private final static String WSS_SAML_NS = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#";
protected GenericIdentityProviderData ipData = null;
public final static String ISSUER_SELF = IdentityConstants.NS + "/issuer/self";
private static Log tokenIssuerLog = LogFactory.getLog(IdentityConstants.TOKEN_ISSUSER_LOG);
private boolean isTokenLogDebug = false;
public GenericTokenIssuer() {
isTokenLogDebug = tokenIssuerLog.isDebugEnabled();
}
/**
* {@inheritDoc}
*/
public String getResponseAction(RahasData data) throws TrustException {
return RahasConstants.WST_NS_05_02 + RahasConstants.RSTR_ACTION_ISSUE;
}
/**
* {@inheritDoc}
*/
public void setConfigurationElement(OMElement configElement) {
// Nothing to do
}
/**
* {@inheritDoc}
*/
public void setConfigurationFile(String configFile) {
// Nothing to do
}
/**
* {@inheritDoc}
*/
public void setConfigurationParamName(String configParamName) {
// Nothing to do
}
/**
* {@inheritDoc}
*/
public SOAPEnvelope issue(RahasData data) throws TrustException {
try {
if (log.isDebugEnabled()) {
log.debug("Request: \n" + data.getRstElement().toString() + "\n\n");
}
ipData = getIdentityProviderData(data);
if (isTokenLogDebug) {
tokenIssuerLog.debug("validInfoCard");
}
return createResponse(data);
} catch (Exception e) {
throw new TrustException(TrustException.REQUEST_FAILED, e);
} finally {
log.info("Issued token");
}
}
/**
* Create the response SOAP envelope.
*
* @param data WS-Trust information in the issue request.
* @return response SOAP envelope.
* @throws TrustException
*/
protected SOAPEnvelope createResponse(RahasData rahasData) throws TrustException {
MessageContext inMsgCtx = null;
SOAPEnvelope envelope = null;
Document doc = null;
WSSecEncryptedKey encryptedKey = null;
X509Certificate serviceCert = null;
try {
inMsgCtx = rahasData.getInMessageContext();
envelope = TrustUtil.createSOAPEnvelope(inMsgCtx.getEnvelope().getNamespace()
.getNamespaceURI());
doc = ((Element) envelope).getOwnerDocument();
// Create EncryptedKey
serviceCert = ipData.getRpCert();
if (serviceCert != null) {
Element encrKeyElem = null;
Element keyInfoElem = null;
encryptedKey = new WSSecEncryptedKey();
encryptedKey.setUseThisCert(serviceCert);
encryptedKey.setKeySize(256);
encryptedKey.setKeyEncAlgo(WSConstants.KEYTRANSPORT_RSAOEP);
encryptedKey.setKeyIdentifierType(WSConstants.THUMBPRINT_IDENTIFIER);
encryptedKey.prepare(doc, null);
encrKeyElem = encryptedKey.getEncryptedKeyElement();
// Create KeyInfo structure - START
keyInfoElem = doc.createElementNS(WSConstants.SIG_NS, "KeyInfo");
((OMElement) encrKeyElem).declareNamespace(WSConstants.SIG_NS,
WSConstants.SIG_PREFIX);
((OMElement) encrKeyElem).declareNamespace(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX);
keyInfoElem.appendChild(encrKeyElem);
// Create KeyInfo structure - END
}
if (!checkIsValidTokenType(ipData)) {
throw new IdentityProviderException("invalidTokenType");
}
DateTime notBefore = null;
DateTime notAfter = null;
String assertionId = null;
Element assertionNode = null;
OMElement rstrElem = null;
notBefore = new DateTime();
notAfter = new DateTime(notBefore.getMillis() + (300 * 1000));
assertionId = UUIDGenerator.getUUID();
if (isTokenLogDebug) {
tokenIssuerLog.debug("startSAMLTokenCreation");
}
assertionNode = createSAMLAssertionAsDOM(ipData, rahasData, notBefore, notAfter,
assertionId);
if (isTokenLogDebug) {
tokenIssuerLog.debug("finishSAMLTokenCreation");
}
rstrElem = createRSTR(rahasData, notBefore.toDate(), notAfter.toDate(), envelope, doc,
assertionNode, assertionId, encryptedKey);
if (isTokenLogDebug) {
tokenIssuerLog.debug("RSTRCreationDone");
}
if (log.isDebugEnabled()) {
log.debug("Response created");
log.debug("Response body : \n" + rstrElem.toString() + "\n\n");
}
return envelope;
} catch (Exception e) {
log.error(e.getMessage());
throw new TrustException(TrustException.REQUEST_FAILED, e);
}
}
/**
* Create the <code>wst:RequstedSecurityTokenRespoonse</code> element.
*
* @param data WS-Trust information in the issue request
* @param notBefore Created time
* @param notAfter Expiration time
* @param env Response SOAP envelope
* @param doc <code>org.w3.dom.Document</code> instance of the response SOAP envelope
* @param assertion SAML Assertion to be sent in the response.
* @param encryptedKey Key used to encrypt the SAML assertion.
* @return <code>wst:RequstedSecurityTokenRespoonse</code> element.
* @throws TrustException
* @throws SAMLException
*/
protected OMElement createRSTR(RahasData data, Date notBefore, Date notAfter, SOAPEnvelope env,
Document doc, Node assertionElem, String assertionId, WSSecEncryptedKey encryptedKey)
throws TrustException, SAMLException, IdentityProviderException {
if (log.isDebugEnabled()) {
log.debug("Begin RSTR Element creation.");
}
int wstVersion;
MessageContext inMsgCtx = null;
OMElement rstrElem = null;
OMElement appliesToEpr = null;
wstVersion = data.getVersion();
inMsgCtx = data.getInMessageContext();
rstrElem = TrustUtil.createRequestSecurityTokenResponseElement(wstVersion, env.getBody());
TrustUtil.createTokenTypeElement(wstVersion, rstrElem).setText(data.getTokenType());
createDisplayToken(rstrElem, ipData);
if (encryptedKey != null) {
OMElement incomingAppliesToEpr = null;
OMElement appliesToElem = null;
int keysize = data.getKeysize();
if (keysize == -1) {
keysize = encryptedKey.getEphemeralKey().length * 8;
}
TrustUtil.createKeySizeElement(wstVersion, rstrElem, keysize);
incomingAppliesToEpr = data.getAppliesToEpr();
try {
Document eprDoc = null;
eprDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new ByteArrayInputStream(incomingAppliesToEpr.toString().getBytes()));
appliesToEpr = (OMElement) doc.importNode(eprDoc.getDocumentElement(), true);
} catch (Exception e) {
throw new TrustException(TrustException.REQUEST_FAILED, e);
}
appliesToElem = rstrElem.getOMFactory().createOMElement(
new QName(RahasConstants.WSP_NS,
RahasConstants.IssuanceBindingLocalNames.APPLIES_TO,
RahasConstants.WSP_PREFIX), rstrElem);
appliesToElem.addChild(appliesToEpr);
}
DateFormat zulu = null;
OMElement reqSecTokenElem = null;
Node assertionElement = null;
Token assertionToken = null;
// Use GMT time in milliseconds
zulu = new XmlSchemaDateFormat();
// Add the Lifetime element
TrustUtil.createLifetimeElement(wstVersion, rstrElem, zulu.format(notBefore), zulu
.format(notAfter));
reqSecTokenElem = TrustUtil.createRequestedSecurityTokenElement(wstVersion, rstrElem);
assertionElement = doc.importNode(assertionElem, true);
reqSecTokenElem.addChild((OMNode) assertionElement);
if (log.isDebugEnabled()) {
log.debug(assertionElement.toString());
}
if (encryptedKey != null) {
encryptSAMLAssertion(doc, (Element) assertionElement, encryptedKey);
}
createAttachedRef(rstrElem, assertionId);
createUnattachedRef(rstrElem, assertionId);
// Store the Token
assertionToken = new Token(assertionId, (OMElement) doc.importNode(assertionElem, true),
notBefore, notAfter);
// At this point we definitely have the secret
// Otherwise it should fail with an exception earlier
assertionToken.setSecret(data.getEphmeralKey());
TrustUtil.getTokenStore(inMsgCtx).add(assertionToken);
// Creating the ReqProoftoken - END
if (log.isDebugEnabled()) {
log.debug("RSTR Elem created.");
}
return rstrElem;
}
/**
* Create and add wst:AttachedReference element
*
* @param rstrElem wst:RequestSecurityToken element
* @param id Token identifier
*/
protected void createAttachedRef(OMElement rstrElem, String id) {
OMFactory fac = null;
OMElement rar = null;
OMElement str = null;
OMElement ki = null;
fac = rstrElem.getOMFactory();
rar = fac.createOMElement(new QName(RahasConstants.WST_NS_05_02,
RahasConstants.IssuanceBindingLocalNames.REQUESTED_ATTACHED_REFERENCE,
RahasConstants.WST_PREFIX), rstrElem);
str = fac.createOMElement(new QName(WSConstants.WSSE_NS,
SecurityTokenReference.SECURITY_TOKEN_REFERENCE, WSConstants.WSSE_PREFIX), rar);
ki = fac.createOMElement(new QName(WSConstants.WSSE_NS, "KeyIdentifier",
WSConstants.WSSE_PREFIX), str);
ki.addAttribute("ValueType", WSS_SAML_NS + WSConstants.SAML_ASSERTION_ID, null);
ki.setText(id);
}
/**
* Create and add wst:UnattachedReference element
*
* @param rstrElem wst:RequestSecurityToken element
* @param id Token identifier
*/
protected void createUnattachedRef(OMElement rstrElem, String id) {
OMFactory fac = null;
OMElement rar = null;
OMElement str = null;
OMElement ki = null;
fac = rstrElem.getOMFactory();
rar = fac.createOMElement(new QName(RahasConstants.WST_NS_05_02,
RahasConstants.IssuanceBindingLocalNames.REQUESTED_UNATTACHED_REFERENCE,
RahasConstants.WST_PREFIX), rstrElem);
str = fac.createOMElement(new QName(WSConstants.WSSE_NS,
SecurityTokenReference.SECURITY_TOKEN_REFERENCE, WSConstants.WSSE_PREFIX), rar);
ki = fac.createOMElement(new QName(WSConstants.WSSE_NS, "KeyIdentifier",
WSConstants.WSSE_PREFIX), str);
ki.addAttribute("ValueType", WSS_SAML_NS + WSConstants.SAML_ASSERTION_ID, null);
ki.setText(id);
}
/**
* Create the DisplayToken element according to CardSpace specifications.
*
* @param rstrElem Information from the WS-Trust request.
* @param ipData CardSpace specific meta-data for this issuance.
* @return The DisplayToken element.
*/
protected OMElement createDisplayToken(OMElement rstrElem, GenericIdentityProviderData ipData)
throws IdentityProviderException {
return null;
}
/**
*
* @param rahasData
* @return
* @throws Exception
*/
protected GenericIdentityProviderData getIdentityProviderData(RahasData rahasData)
throws Exception {
return new GenericIdentityProviderData(rahasData);
}
/**
*
* @param ipData
* @param rahasData
* @param notBefore
* @param notAfter
* @param assertionId
* @return
* @throws IdentityProviderException
*/
protected Element createSAMLAssertionAsDOM(GenericIdentityProviderData ipData,
RahasData rahasData, DateTime notBefore, DateTime notAfter, String assertionId)
throws IdentityProviderException {
Element elem = null;
SAMLTokenBuilder builder = null;
final String requiredTokenType = ipData.getRequiredTokenType();
if (requiredTokenType.equals(IdentityConstants.SAML10_URL)
|| requiredTokenType.equals(IdentityConstants.SAML11_URL)) {
builder = new SAML1TokenBuilder();
} else if (requiredTokenType.equals(IdentityConstants.SAML20_URL)) {
builder = new SAML2TokenBuilder();
}
SAMLTokenDirector director = new SAMLTokenDirector(builder, rahasData, ipData);
elem = director.createSAMLToken(notBefore, notAfter, assertionId);
return elem;
}
/**
*
* @param data
* @return
* @throws IdentityProviderException
*/
protected boolean checkIsValidTokenType(GenericIdentityProviderData data)
throws IdentityProviderException {
boolean isValid = false;
String type = data.getRequiredTokenType();
IdentityPersistenceManager admin = null;
String types = null;
String[] arrTypes = null;
try {
admin = IdentityPersistenceManager.getPersistanceManager();
types = admin.getParameterValue(IdentityTenantUtil.getRegistry(null, data
.getUserIdentifier()), IdentityConstants.PARAM_SUPPORTED_TOKEN_TYPES);
} catch (IdentityException e) {
throw new IdentityProviderException(e.getMessage(), e);
}
arrTypes = types.split(",");
for (int i = 0; i < arrTypes.length; i++) {
if (arrTypes[i].equals(type)) {
isValid = true;
break;
}
}
return isValid;
}
/**
* Encrypt the given SAML Assertion element with the given key information.
*
* @param doc
* @param assertionElement
* @param encryptedKey
*/
private void encryptSAMLAssertion(Document doc, Element assertionElement,
WSSecEncryptedKey encryptedKey) throws TrustException {
XMLCipher xmlCipher = null;
SecretKey secretKey = null;
String xencEncryptedDataId = null;
KeyInfo keyInfo = null;
EncryptedData encData = null;
try {
xmlCipher = XMLCipher.getInstance(WSConstants.AES_256);
secretKey = WSSecurityUtil.prepareSecretKey(WSConstants.AES_256, encryptedKey
.getEphemeralKey());
xmlCipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
xencEncryptedDataId = "EncDataId-" + assertionElement.hashCode();
keyInfo = new KeyInfo(doc);
keyInfo.addUnknownElement(encryptedKey.getEncryptedKeyElement());
encData = xmlCipher.getEncryptedData();
encData.setId(xencEncryptedDataId);
encData.setKeyInfo(keyInfo);
xmlCipher.doFinal(doc, assertionElement, false);
} catch (Exception e) {
throw new TrustException(TrustException.REQUEST_FAILED, e);
}
}
}