| /* |
| * Copyright 2004,2005 The Apache Software Foundation. |
| * |
| * Licensed 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.apache.rahas.client; |
| |
| import org.apache.axiom.om.OMElement; |
| import org.apache.axiom.om.OMNode; |
| import org.apache.axiom.om.impl.builder.StAXOMBuilder; |
| import org.apache.axiom.om.impl.dom.DOOMAbstractFactory; |
| import org.apache.axiom.om.util.Base64; |
| import org.apache.axiom.soap.SOAP12Constants; |
| import org.apache.axis2.AxisFault; |
| import org.apache.axis2.addressing.AddressingConstants; |
| import org.apache.axis2.addressing.EndpointReference; |
| import org.apache.axis2.client.Options; |
| import org.apache.axis2.client.ServiceClient; |
| import org.apache.axis2.context.ConfigurationContext; |
| import org.apache.axis2.description.AxisOperation; |
| import org.apache.axis2.description.AxisService; |
| import org.apache.axis2.description.OutInAxisOperation; |
| import org.apache.axiom.om.util.UUIDGenerator; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.neethi.Assertion; |
| import org.apache.neethi.Policy; |
| import org.apache.rahas.RahasConstants; |
| import org.apache.rahas.Token; |
| import org.apache.rahas.TrustException; |
| import org.apache.rahas.TrustUtil; |
| import org.apache.ws.secpolicy.model.AlgorithmSuite; |
| import org.apache.ws.secpolicy.model.Binding; |
| import org.apache.ws.secpolicy.model.Trust10; |
| import org.apache.ws.security.WSConstants; |
| import org.apache.ws.security.WSPasswordCallback; |
| import org.apache.ws.security.WSSecurityException; |
| import org.apache.ws.security.components.crypto.Crypto; |
| import org.apache.ws.security.conversation.ConversationException; |
| import org.apache.ws.security.conversation.dkalgo.P_SHA1; |
| import org.apache.ws.security.message.token.Reference; |
| import org.apache.ws.security.processor.EncryptedKeyProcessor; |
| import org.apache.ws.security.util.WSSecurityUtil; |
| import org.w3c.dom.Element; |
| |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.xml.namespace.QName; |
| |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Vector; |
| |
| public class STSClient { |
| |
| private static final String RAMPART_POLICY = "rampartPolicy"; |
| |
| private static Log log = LogFactory.getLog(STSClient.class); |
| |
| private String action; |
| |
| private OMElement rstTemplate; |
| |
| private int version = RahasConstants.VERSION_05_02; |
| |
| private Options options; |
| |
| private Trust10 trust10; |
| |
| private AlgorithmSuite algorithmSuite; |
| |
| private byte[] requestorEntropy; |
| |
| private String addressingNs = AddressingConstants.Final.WSA_NAMESPACE; |
| |
| private int keySize; |
| |
| private String soapVersion = SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI; |
| |
| /** |
| * Life time in seconds |
| * Default is 300 seconds (5 mins) |
| */ |
| private int ttl = 300; |
| private Crypto crypto; |
| private CallbackHandler cbHandler; |
| private ConfigurationContext configCtx; |
| |
| public STSClient(ConfigurationContext configCtx) throws TrustException { |
| if (configCtx != null) { |
| this.configCtx = configCtx; |
| } else { |
| throw new TrustException("stsClientCfgCtxNull"); |
| } |
| } |
| |
| public Token requestSecurityToken(Policy servicePolicy, |
| String issuerAddress, |
| Policy issuerPolicy, |
| String appliesTo) throws TrustException { |
| try { |
| QName rstQn = new QName("requestSecurityToken"); |
| String requestType = |
| TrustUtil.getWSTNamespace(version) + RahasConstants.REQ_TYPE_ISSUE; |
| |
| ServiceClient client = getServiceClient(rstQn, issuerAddress); |
| |
| client.getOptions().setProperty(RAMPART_POLICY, issuerPolicy); |
| client.getOptions().setSoapVersionURI(this.soapVersion); |
| |
| //Process the STS and service policy policy |
| this.processPolicy(issuerPolicy, servicePolicy); |
| OMElement response = client.sendReceive(rstQn, |
| createIssueRequest(requestType, appliesTo)); |
| |
| return processIssueResponse(version, response); |
| } catch (AxisFault e) { |
| e.printStackTrace(); |
| log.error("errorInObtainingToken", e); |
| throw new TrustException("errorInObtainingToken", new String[]{issuerAddress}); |
| } |
| } |
| |
| /** |
| * Cancel a particular security token |
| * |
| * @param issuerAddress |
| * @param tokenId |
| * @return true is the Token was successfully cancelled. False otherwise. |
| * @throws TrustException |
| */ |
| public boolean cancelToken(String issuerAddress, |
| String tokenId, |
| String action) throws TrustException { |
| try { |
| QName rstQn = new QName("cancelSecurityToken"); |
| ServiceClient client = getServiceClient(rstQn, issuerAddress); |
| if(action != null) { |
| client.getOptions().setAction(action); |
| } |
| |
| return processCancelResponse(client.sendReceive(rstQn, |
| createCancelRequest(tokenId))); |
| } catch (AxisFault e) { |
| log.error("errorInCancelingToken", e); |
| throw new TrustException("errorInCancelingToken", e); |
| } |
| } |
| |
| private ServiceClient getServiceClient(QName rstQn, |
| String issuerAddress) throws AxisFault { |
| AxisService axisService = |
| new AxisService("SecurityTokenService" + UUIDGenerator.getUUID()); |
| axisService.setClientSide(true); |
| AxisOperation operation = new OutInAxisOperation(rstQn); |
| axisService.addOperation(operation); |
| ServiceClient client = new ServiceClient(this.configCtx, axisService); |
| |
| if (this.options != null) { |
| client.setOptions(options); |
| } |
| |
| //Set the action |
| client.getOptions().setAction(action); |
| client.getOptions().setTo(new EndpointReference(issuerAddress)); |
| client.engageModule(new QName("rampart")); |
| return client; |
| } |
| |
| /** |
| * @param result |
| * @return Token |
| */ |
| private Token processIssueResponse(int version, OMElement result) throws TrustException { |
| OMElement rstr = result; |
| if (version == RahasConstants.VERSION_05_12) { |
| //The WS-SX result will be an RSTRC |
| rstr = result.getFirstElement(); |
| } |
| |
| String ns = TrustUtil.getWSTNamespace(version); |
| |
| //Get the RequestedAttachedReference |
| OMElement reqAttElem = rstr.getFirstChildWithName(new QName( |
| ns, RahasConstants.IssuanceBindingLocalNames.REQUESTED_ATTACHED_REFERENCE)); |
| OMElement reqAttRef = reqAttElem == null ? null : reqAttElem.getFirstElement(); |
| |
| //Get the RequestedUnattachedReference |
| OMElement reqUnattElem = |
| rstr.getFirstChildWithName(new QName(ns, |
| RahasConstants.IssuanceBindingLocalNames. |
| REQUESTED_UNATTACHED_REFERENCE)); |
| OMElement reqUnattRef = reqUnattElem == null ? null : reqUnattElem.getFirstElement(); |
| |
| //Get the security token |
| OMElement reqSecTok = |
| rstr.getFirstChildWithName(new QName(ns, |
| RahasConstants.IssuanceBindingLocalNames. |
| REQUESTED_SECURITY_TOKEN)); |
| if (reqSecTok == null) { |
| throw new TrustException("reqestedSecTokMissing"); |
| } |
| |
| OMElement tokenElem = reqSecTok.getFirstElement(); |
| |
| String id = this.findIdentifier(reqAttRef, reqUnattRef, tokenElem); |
| |
| if (id == null) { |
| throw new TrustException("cannotObtainTokenIdentifier"); |
| } |
| |
| OMElement lifeTimeEle = |
| rstr.getFirstChildWithName(new QName(ns, |
| RahasConstants.IssuanceBindingLocalNames. |
| LIFETIME)); |
| |
| Token token = new Token(id, tokenElem, lifeTimeEle); |
| token.setAttachedReference(reqAttRef); |
| token.setUnattachedReference(reqUnattRef); |
| |
| //Handle proof token |
| OMElement rpt = |
| rstr.getFirstChildWithName(new QName(ns, |
| RahasConstants.LocalNames. |
| REQUESTED_PROOF_TOKEN)); |
| |
| byte[] secret = null; |
| |
| if (rpt != null) { |
| OMElement child = rpt.getFirstElement(); |
| if (child == null) { |
| throw new TrustException("invalidRPT"); |
| } |
| if (child.getQName().equals(new QName(ns, |
| RahasConstants.LocalNames. |
| BINARY_SECRET))) { |
| //First check for the binary secret |
| String b64Secret = child.getText(); |
| secret = Base64.decode(b64Secret); |
| } else if (child.getQName().equals(new QName(ns, WSConstants.ENC_KEY_LN))) { |
| try { |
| Element domChild = (Element) new StAXOMBuilder( |
| DOOMAbstractFactory.getOMFactory(), child |
| .getXMLStreamReader()).getDocumentElement(); |
| |
| EncryptedKeyProcessor processor = new EncryptedKeyProcessor(); |
| |
| processor.handleToken(domChild, null, this.crypto, |
| this.cbHandler, null, new Vector(), |
| null); |
| |
| secret = processor.getDecryptedBytes(); |
| } catch (WSSecurityException e) { |
| throw new TrustException("errorInProcessingEncryptedKey", e); |
| } |
| } else if (child.getQName().equals(new QName(ns, |
| RahasConstants.IssuanceBindingLocalNames. |
| COMPUTED_KEY))) { |
| //Handle the computed key |
| |
| //Get service entropy |
| OMElement serviceEntrElem = rstr |
| .getFirstChildWithName(new QName(ns, |
| RahasConstants.IssuanceBindingLocalNames. |
| ENTROPY)); |
| |
| OMElement binSecElem = serviceEntrElem.getFirstElement(); |
| |
| if (binSecElem != null && binSecElem.getText() != null |
| && !"".equals(binSecElem.getText().trim())) { |
| |
| byte[] serviceEntr = Base64.decode(binSecElem.getText()); |
| |
| //Right now we only use PSHA1 as the computed key algo |
| P_SHA1 p_sha1 = new P_SHA1(); |
| |
| int length = (this.keySize > 0) ? keySize |
| : this.algorithmSuite |
| .getMaximumSymmetricKeyLength(); |
| try { |
| secret = p_sha1.createKey(this.requestorEntropy, serviceEntr, 0, length/8); |
| } catch (ConversationException e) { |
| throw new TrustException("keyDerivationError", e); |
| } |
| } else { |
| //Service entropy missing |
| throw new TrustException("serviceEntropyMissing"); |
| } |
| } |
| |
| } else { |
| if (this.requestorEntropy != null) { |
| //Use requestor entropy as the key |
| secret = this.requestorEntropy; |
| } |
| } |
| token.setSecret(secret); |
| return token; |
| } |
| |
| private boolean processCancelResponse(OMElement response) { |
| /* |
| <wst:RequestSecurityTokenResponse> |
| <wst:RequestedTokenCancelled/> |
| </wst:RequestSecurityTokenResponse> |
| */ |
| return response. |
| getFirstChildWithName(new QName(RahasConstants. |
| CancelBindingLocalNames.REQUESTED_TOKEN_CANCELED)) != null; |
| } |
| |
| /** |
| * Find the token identifier. |
| * |
| * @param reqAttRef |
| * @param reqUnattRef |
| * @param token |
| * @return id |
| */ |
| private String findIdentifier(OMElement reqAttRef, |
| OMElement reqUnattRef, |
| OMElement token) { |
| String id; |
| if (reqAttRef != null) { |
| //First try the attached ref |
| id = this.getIdFromSTR(reqAttRef); |
| } else if (reqUnattRef != null) { |
| //then try the unattached ref |
| id = this.getIdFromSTR(reqUnattRef); |
| } else { |
| //Return wsu:Id of the token element |
| id = token.getAttributeValue(new QName(WSConstants.WSU_NS, "Id")); |
| } |
| return id; |
| } |
| |
| |
| /** |
| * Process the given STR to find the id it refers to |
| * |
| * @param refElem |
| * @return id |
| */ |
| private String getIdFromSTR(OMElement refElem) { |
| //ASSUMPTION:SecurityTokenReference/KeyIdentifier |
| OMElement child = refElem.getFirstElement(); |
| if(child == null) { |
| return null; |
| } |
| |
| if (child.getQName().equals(new QName(WSConstants.SIG_NS, "KeyInfo"))) { |
| return child.getText(); |
| } else if(child.getQName().equals(Reference.TOKEN)) { |
| return child.getAttributeValue(new QName("URI")); |
| } else { |
| return null; |
| } |
| |
| } |
| |
| /** |
| * Process the goven service policy and extract the info required to create |
| * the RST. |
| * |
| * @param servicePolicy |
| */ |
| private void processPolicy(Policy issuerPolicy, Policy servicePolicy) { |
| //Get the policy assertions |
| //Assumption: there's only one alternative |
| |
| if (issuerPolicy != null) { |
| log.debug("Processing Issuer policy"); |
| |
| List issuerAssertions = (List) issuerPolicy.getAlternatives().next(); |
| |
| for (Iterator iter = issuerAssertions.iterator(); iter.hasNext();) { |
| Assertion tempAssertion = (Assertion) iter.next(); |
| //find the AlgorithmSuite assertion |
| if (tempAssertion instanceof Binding) { |
| |
| log.debug("Extracting algo suite from issuer " + |
| "policy binding"); |
| |
| this.algorithmSuite = ((Binding) tempAssertion) |
| .getAlgorithmSuite(); |
| } |
| } |
| } |
| |
| if (servicePolicy != null) { |
| |
| log.debug("Processing service policy to find Trust10 assertion"); |
| |
| List assertions = (List) servicePolicy.getAlternatives().next(); |
| |
| for (Iterator iter = assertions.iterator(); iter.hasNext();) { |
| Assertion tempAssertion = (Assertion) iter.next(); |
| //find the Trust10 assertion |
| if (tempAssertion instanceof Trust10) { |
| log.debug("Extracting Trust10 assertion from " + |
| "service policy"); |
| this.trust10 = (Trust10) tempAssertion; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Create the RST request. |
| * |
| * @param requestType |
| * @param appliesTo |
| * @return OMElement |
| * @throws TrustException |
| */ |
| private OMElement createIssueRequest(String requestType, |
| String appliesTo) throws TrustException { |
| |
| log.debug("Creating request with request type: " + requestType + |
| " and applies to: " + appliesTo); |
| |
| OMElement rst = TrustUtil.createRequestSecurityTokenElement(version); |
| |
| TrustUtil.createRequestTypeElement(this.version, rst, requestType); |
| if (appliesTo != null) { |
| TrustUtil.createAppliesToElement(rst, appliesTo, this.addressingNs); |
| } |
| TrustUtil.createLifetimeElement(this.version, rst, this.ttl * 1000); |
| |
| //Copy over the elements from the template |
| if (this.rstTemplate != null) { |
| |
| log.debug("Using RSTTemplate: " + this.rstTemplate.toString()); |
| |
| Iterator templateChildren = rstTemplate.getChildElements(); |
| while (templateChildren.hasNext()) { |
| OMNode child = (OMNode) templateChildren.next(); |
| rst.addChild(child); |
| //Look for the key size element |
| if (child instanceof OMElement |
| && ((OMElement) child).getQName().equals( |
| new QName(TrustUtil.getWSTNamespace(this.version), |
| RahasConstants.IssuanceBindingLocalNames.KEY_SIZE))) { |
| log.debug("Extracting key size from the RSTTemplate: "); |
| OMElement childElem = (OMElement) child; |
| this.keySize = |
| (childElem.getText() != null && !"".equals(childElem.getText())) ? |
| Integer.parseInt(childElem.getText()) : |
| -1; |
| log.debug("Key size from RSTTemplate: " + this.keySize); |
| } |
| } |
| } |
| |
| try { |
| // Handle entropy |
| if (this.trust10 != null) { |
| |
| log.debug("Processing Trust10 assertion"); |
| |
| if (this.trust10.isRequireClientEntropy()) { |
| |
| log.debug("Requires client entropy"); |
| |
| // setup requestor entropy |
| OMElement ent = TrustUtil.createEntropyElement(this.version, rst); |
| OMElement binSec = |
| TrustUtil.createBinarySecretElement(this.version, |
| ent, |
| RahasConstants.BIN_SEC_TYPE_NONCE); |
| this.requestorEntropy = |
| WSSecurityUtil.generateNonce(this.algorithmSuite. |
| getMaximumSymmetricKeyLength()); |
| binSec.setText(Base64.encode(this.requestorEntropy)); |
| |
| log.debug("Clien entropy : " |
| + Base64.encode(this.requestorEntropy)); |
| |
| // Add the ComputedKey element |
| TrustUtil.createComputedKeyAlgorithm(this.version, rst, |
| RahasConstants.COMPUTED_KEY_PSHA1); |
| |
| } |
| } |
| } catch (Exception e) { |
| throw new TrustException("errorSettingUpRequestorEntropy", e); |
| } |
| |
| |
| return rst; |
| |
| } |
| |
| private OMElement createCancelRequest(String tokenId) throws TrustException { |
| |
| return TrustUtil.createCancelRequest(tokenId, version); |
| } |
| |
| /** |
| * Set this to set the entropy configurations. |
| * If this is provided in the given policy it will be overridden. |
| * |
| * @param trust10 The trust10 to set. |
| */ |
| public void setTrust10(Trust10 trust10) { |
| this.trust10 = trust10; |
| } |
| |
| /** |
| * This can be used in the case where the AlgorithmSuite is not specified in |
| * the given policy. |
| * If the AlgorithmSuite exists in a binding in the policy then the value |
| * set will be overridden. |
| * |
| * @param algorithmSuite The algorithmSuite to set. |
| */ |
| public void setAlgorithmSuite(AlgorithmSuite algorithmSuite) { |
| this.algorithmSuite = algorithmSuite; |
| } |
| |
| /** |
| * @param addressingNs The addressingNs to set. |
| */ |
| public void setAddressingNs(String addressingNs) { |
| this.addressingNs = addressingNs; |
| } |
| |
| /** |
| * @param ttl The ttl to set. |
| */ |
| public void setTtl(int ttl) { |
| this.ttl = ttl; |
| } |
| |
| /** |
| * Sets the crypto information required to process the RSTR. |
| * |
| * @param crypto Crypto information |
| * @param cbHandler Callback handler to provide the private key password to |
| * decrypt |
| */ |
| public void setCryptoInfo(Crypto crypto, CallbackHandler cbHandler) { |
| this.crypto = crypto; |
| this.cbHandler = cbHandler; |
| } |
| |
| /** |
| * Sets the crypto information required to process the RSTR. |
| * |
| * @param crypto The crypto information |
| * @param privKeyPasswd Private key password to decrypt |
| */ |
| public void setCryptoInfo(Crypto crypto, String privKeyPasswd) { |
| this.crypto = crypto; |
| this.cbHandler = new CBHandler(privKeyPasswd); |
| } |
| |
| /** |
| * @param action The action to set. |
| */ |
| public void setAction(String action) { |
| this.action = action; |
| } |
| |
| /** |
| * @param options The options to set. |
| */ |
| public void setOptions(Options options) { |
| this.options = options; |
| } |
| |
| /** |
| * @param rstTemplate The rstTemplate to set. |
| */ |
| public void setRstTemplate(OMElement rstTemplate) { |
| this.rstTemplate = rstTemplate; |
| } |
| |
| private class CBHandler implements CallbackHandler { |
| |
| private String passwd; |
| |
| private CBHandler(String passwd) { |
| this.passwd = passwd; |
| } |
| |
| public void handle(Callback[] cb) throws IOException, |
| UnsupportedCallbackException { |
| ((WSPasswordCallback) cb[0]).setPassword(this.passwd); |
| } |
| |
| } |
| |
| /** |
| * @param version The version to set. |
| */ |
| public void setVersion(int version) { |
| this.version = version; |
| } |
| |
| public void setSoapVersion(String soapVersion) { |
| this.soapVersion = soapVersion; |
| } |
| |
| } |