| /* |
| * Copyright 2003-2004 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.ws.security.message.token; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.ws.security.WSConstants; |
| import org.apache.ws.security.WSDocInfo; |
| 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.util.DOM2Writer; |
| import org.apache.ws.security.util.WSSecurityUtil; |
| import org.apache.xml.security.exceptions.XMLSecurityException; |
| import org.apache.xml.security.keys.content.x509.XMLX509IssuerSerial; |
| import org.apache.xml.security.keys.content.X509Data; |
| import org.apache.ws.security.util.Base64; |
| import org.apache.xml.security.utils.Constants; |
| import org.w3c.dom.*; |
| |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| |
| /** |
| * Security Token Reference. |
| * <p/> |
| * |
| * @author Davanum Srinivas (dims@yahoo.com). |
| */ |
| public class SecurityTokenReference { |
| private static Log log = |
| LogFactory.getLog(SecurityTokenReference.class.getName()); |
| public static final String SECURITY_TOKEN_REFERENCE = "SecurityTokenReference"; |
| public static final String KEY_NAME = "KeyName"; |
| public static final String SKI_URI = WSConstants.X509TOKEN_NS + "#X509SubjectKeyIdentifier"; |
| public static final String THUMB_URI = WSConstants.SOAPMESSAGE_NS11 + "#" + WSConstants.THUMBPRINT; |
| public static final String SAML_ID_URI = WSConstants.SAMLTOKEN_NS + "#" + WSConstants.SAML_ASSERTION_ID; |
| public static final String ENC_KEY_SHA1_URI = WSConstants.SOAPMESSAGE_NS11 + "#" + WSConstants.ENC_KEY_SHA1_URI; |
| protected Element element = null; |
| private XMLX509IssuerSerial issuerSerial = null; |
| private byte[] skiBytes = null; |
| |
| private static boolean doDebug = false; |
| |
| /** |
| * Constructor. |
| * <p/> |
| * |
| * @param elem TODO |
| * @throws WSSecurityException |
| */ |
| public SecurityTokenReference(Element elem) throws WSSecurityException { |
| doDebug = log.isDebugEnabled(); |
| this.element = elem; |
| boolean goodElement = false; |
| if (SECURITY_TOKEN_REFERENCE.equals(element.getLocalName())) { |
| goodElement = WSConstants.WSSE_NS.equals(element.getNamespaceURI()); |
| // } else if (KEY_NAME.equals(element.getLocalName())) { |
| // goodElement = WSConstants.SIG_NS.equals(element.getNamespaceURI()); |
| } |
| if (!goodElement) { |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "badElement", |
| null); |
| } |
| } |
| |
| /** |
| * Constructor. |
| * <p/> |
| * |
| * @param doc TODO |
| */ |
| public SecurityTokenReference(Document doc) { |
| doDebug = log.isDebugEnabled(); |
| this.element = |
| doc.createElementNS(WSConstants.WSSE_NS, |
| "wsse:SecurityTokenReference"); |
| WSSecurityUtil.setNamespace(this.element, WSConstants.WSSE_NS, WSConstants.WSSE_PREFIX); |
| } |
| |
| /* |
| * Here the methods that handle the direct reference inside |
| * a SecurityTokenReference |
| */ |
| |
| /** |
| * set the reference. |
| * <p/> |
| * |
| * @param ref |
| */ |
| public void setReference(Reference ref) { |
| Element elem = getFirstElement(); |
| if (elem != null) { |
| this.element.replaceChild(ref.getElement(), elem); |
| } else { |
| this.element.appendChild(ref.getElement()); |
| } |
| } |
| |
| /** |
| * Gets the Reference. |
| * |
| * @return the <code>Reference</code> element contained in this |
| * SecurityTokeneReference |
| * @throws WSSecurityException |
| */ |
| public Reference getReference() throws WSSecurityException { |
| Element elem = getFirstElement(); |
| return new Reference(elem); |
| } |
| |
| /** |
| * Gets the signing token element, which maybe a <code>BinarySecurityToken |
| * </code> or a SAML token. |
| * |
| * The method gets the URI attribute of the {@link Reference} contained in |
| * the {@link SecurityTokenReference} and tries to find the referenced |
| * Element in the document. |
| * |
| * @param doc the document that contains the binary security token |
| * element. This could be different from the document |
| * that contains the SecurityTokenReference (STR). See |
| * STRTransform.derefenceBST() method |
| * @return Element containing the signing token, must be a BinarySecurityToken |
| * @throws WSSecurityException When either no <code>Reference</code> element, or the found |
| * reference contains no URI, or the referenced signing not found. |
| */ |
| public Element getTokenElement(Document doc, WSDocInfo docInfo, CallbackHandler cb) |
| throws WSSecurityException { |
| Reference ref = getReference(); |
| String uri = ref.getURI(); |
| if (doDebug) { |
| log.debug("Token reference uri: " + uri); |
| } |
| if (uri == null) { |
| throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, |
| "badReferenceURI"); |
| } |
| Element tokElement = null; |
| String tmpS = WSConstants.WSS_SAML_NS + WSConstants.WSS_SAML_ASSERTION; |
| String saml10 = WSConstants.WSS_SAML_NS + WSConstants.SAML_ASSERTION_ID; |
| |
| if (tmpS.equals(ref.getValueType()) |
| || saml10.equals(ref.getValueType()) |
| || WSConstants.WSC_SCT.equals(ref.getValueType())) { |
| Element sa = docInfo.getAssertion(); |
| String saID = null; |
| if (sa != null) { |
| saID = sa.getAttribute("AssertionID"); |
| } |
| if (doDebug) { |
| log.debug("SAML token ID: " + saID); |
| } |
| String id = uri.substring(1); |
| if (saID == null || !saID.equals(id)) { |
| |
| if(cb != null) { |
| //try to find a custom token |
| WSPasswordCallback pwcb = new WSPasswordCallback(id, |
| WSPasswordCallback.CUSTOM_TOKEN); |
| try { |
| cb.handle(new Callback[]{pwcb}); |
| } catch (Exception e) { |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "noPassword", new Object[] { id }, e); |
| } |
| |
| Element assertionElem = pwcb.getCustomToken(); |
| if(assertionElem != null) { |
| sa = (Element)doc.importNode(assertionElem, true); |
| } |
| else { |
| throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, |
| "badReferenceURI", |
| new Object[]{"uri:" + uri + ", saID: " + saID}); |
| } |
| } else { |
| throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, |
| "badReferenceURI", |
| new Object[]{"uri:" + uri + ", saID: " + saID}); |
| } |
| } |
| tokElement = sa; |
| } else { |
| |
| tokElement = WSSecurityUtil.getElementByWsuId(doc, uri); |
| |
| // In some scenarios id is used rather than wsu:Id |
| if (tokElement == null) { |
| tokElement = WSSecurityUtil.getElementByGenId(doc, uri); |
| } |
| |
| } |
| if (tokElement == null) { |
| throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, |
| "noToken", |
| new Object[]{uri}); |
| } |
| return tokElement; |
| } |
| |
| /* |
| * Here the methods that handle the various key identifier types |
| * such as KeyIdentifier, SubjectKeyIdentifier (SKI) |
| */ |
| |
| /** |
| * Sets the KeyIdentifier Element as a X509 certificate. |
| * Takes a X509 certificate, converts its data into base 64 and inserts |
| * it into a <code>wsse:KeyIdentifier</code> element, which is placed |
| * in the <code>wsse:SecurityTokenReference</code> element. |
| * |
| * @param cert is the X509 certificate to be inserted as key identifier |
| */ |
| public void setKeyIdentifier(X509Certificate cert) |
| throws WSSecurityException { |
| Document doc = this.element.getOwnerDocument(); |
| byte data[] = null; |
| try { |
| data = cert.getEncoded(); |
| } catch (CertificateEncodingException e) { |
| throw new WSSecurityException( |
| WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError", null, e); |
| } |
| Text text = doc.createTextNode(Base64.encode(data)); |
| |
| if (cert.getVersion() == 1) { |
| createKeyIdentifier(doc, X509Security.X509_V1_TYPE, text); |
| } else { |
| createKeyIdentifier(doc, X509Security.X509_V3_TYPE, text); |
| } |
| } |
| |
| /** |
| * Sets the KeyIdentifier Element as a X509 Subject-Key-Identifier (SKI). |
| * Takes a X509 certificate, gets it SKI data, converts into base 64 and |
| * inserts it into a <code>wsse:KeyIdentifier</code> element, which is placed |
| * in the <code>wsse:SecurityTokenReference</code> element. |
| * |
| * @param cert is the X509 certificate to get the SKI |
| * @param crypto is the Crypto implementation. Used to read SKI info bytes from certificate |
| */ |
| public void setKeyIdentifierSKI(X509Certificate cert, Crypto crypto) |
| throws WSSecurityException { |
| // |
| // As per the 1.1 specification, SKI can only be used for a V3 certificate |
| // |
| if (cert.getVersion() != 3) { |
| throw new WSSecurityException( |
| WSSecurityException.UNSUPPORTED_SECURITY_TOKEN, |
| "invalidCertForSKI", |
| new Object[]{new Integer(cert.getVersion())} |
| ); |
| } |
| |
| Document doc = this.element.getOwnerDocument(); |
| byte data[] = crypto.getSKIBytesFromCert(cert); |
| |
| org.w3c.dom.Text text = doc.createTextNode(Base64.encode(data)); |
| createKeyIdentifier(doc, SKI_URI, text); |
| } |
| |
| /** |
| * Sets the KeyIdentifier Element as a Thumbprint. |
| * |
| * Takes a X509 certificate, computes its thumbprint using SHA-1, converts |
| * into base 64 and inserts it into a <code>wsse:KeyIdentifier</code> |
| * element, which is placed in the <code>wsse:SecurityTokenReference</code> |
| * element. |
| * |
| * @param cert |
| * is the X509 certificate to get the thumbprint |
| */ |
| public void setKeyIdentifierThumb(X509Certificate cert) |
| throws WSSecurityException { |
| Document doc = this.element.getOwnerDocument(); |
| MessageDigest sha = null; |
| try { |
| sha = MessageDigest.getInstance("SHA-1"); |
| } catch (NoSuchAlgorithmException e1) { |
| throw new WSSecurityException( |
| WSSecurityException.FAILURE, "noSHA1availabe", null, e1 |
| ); |
| } |
| sha.reset(); |
| try { |
| sha.update(cert.getEncoded()); |
| } catch (CertificateEncodingException e1) { |
| throw new WSSecurityException( |
| WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError", null, e1 |
| ); |
| } |
| byte[] data = sha.digest(); |
| |
| org.w3c.dom.Text text = doc.createTextNode(Base64.encode(data)); |
| createKeyIdentifier(doc, THUMB_URI, text); |
| } |
| |
| |
| public void setKeyIdentifierEncKeySHA1(String value) |
| throws WSSecurityException { |
| Document doc = this.element.getOwnerDocument(); |
| org.w3c.dom.Text text = doc.createTextNode(value); |
| createKeyIdentifier(doc, ENC_KEY_SHA1_URI, text); |
| |
| } |
| |
| public void setSAMLKeyIdentifier(String keyIdVal) |
| throws WSSecurityException { |
| Document doc = this.element.getOwnerDocument(); |
| createKeyIdentifier(doc, SAML_ID_URI, doc.createTextNode(keyIdVal)); |
| } |
| |
| private void createKeyIdentifier(Document doc, String uri, Node node) { |
| |
| Element keyId = doc.createElementNS(WSConstants.WSSE_NS, |
| "wsse:KeyIdentifier"); |
| keyId.setAttributeNS(null, "ValueType", uri); |
| keyId.setAttributeNS(null, "EncodingType", |
| BinarySecurity.BASE64_ENCODING); |
| |
| keyId.appendChild(node); |
| Element elem = getFirstElement(); |
| if (elem != null) { |
| this.element.replaceChild(keyId, elem); |
| } else { |
| this.element.appendChild(keyId); |
| } |
| } |
| |
| /* |
| * Several helper and utility methods. |
| */ |
| |
| /** |
| * get the first child element. |
| * |
| * @return the first <code>Element</code> child node |
| */ |
| public Element getFirstElement() { |
| for (Node currentChild = this.element.getFirstChild(); |
| currentChild != null; |
| currentChild = currentChild.getNextSibling()) { |
| if (currentChild instanceof Element) { |
| return (Element) currentChild; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the KeyIdentifier. |
| * |
| * @return the the X509 certificate or zero if a unknown key identifier |
| * type was detected. |
| */ |
| public X509Certificate[] getKeyIdentifier(Crypto crypto) |
| throws WSSecurityException { |
| X509Security token = null; |
| Element elem = getFirstElement(); |
| String value = elem.getAttribute("ValueType"); |
| String alias = null; |
| |
| if (X509Security.X509_V3_TYPE.equals(value) |
| || X509Security.X509_V1_TYPE.equals(value)) { |
| token = new X509Security(elem); |
| if (token != null) { |
| X509Certificate cert = token.getX509Certificate(crypto); |
| X509Certificate[] certs = new X509Certificate[1]; |
| certs[0] = cert; |
| return certs; |
| } |
| } else if (SKI_URI.equals(value)) { |
| alias = getX509SKIAlias(crypto); |
| } else if (THUMB_URI.equals(value)) { |
| Node node = getFirstElement().getFirstChild(); |
| if (node == null) { |
| return null; |
| } |
| if (node.getNodeType() == Node.TEXT_NODE) { |
| byte[] thumb = Base64.decode(((Text) node).getData()); |
| alias = crypto.getAliasForX509CertThumb(thumb); |
| } |
| } |
| |
| if (alias != null) { |
| return crypto.getCertificates(alias); |
| } |
| return null; |
| } |
| |
| public String getKeyIdentifierValue() { |
| if(containsKeyIdentifier()) { |
| Node node = getFirstElement().getFirstChild(); |
| if (node == null) { |
| return null; |
| } |
| if (node.getNodeType() == Node.TEXT_NODE) { |
| return ((Text) node).getData(); |
| } |
| } |
| return null; |
| } |
| |
| public String getKeyIdentifierValueType() { |
| if(containsKeyIdentifier()) { |
| Element elem = getFirstElement(); |
| return elem.getAttribute("ValueType"); |
| |
| } |
| return null; |
| } |
| |
| |
| |
| public String getX509SKIAlias(Crypto crypto) throws WSSecurityException { |
| if (skiBytes == null) { |
| skiBytes = getSKIBytes(); |
| if (skiBytes == null) { |
| return null; |
| } |
| } |
| String alias = crypto.getAliasForX509Cert(skiBytes); |
| if (doDebug) { |
| log.info("X509 SKI alias: " + alias); |
| } |
| return alias; |
| } |
| |
| public byte[] getSKIBytes() { |
| if (skiBytes != null) { |
| return skiBytes; |
| } |
| Node node = getFirstElement().getFirstChild(); |
| if (node == null) { |
| return null; |
| } |
| if (node.getNodeType() == Node.TEXT_NODE) { |
| try { |
| skiBytes = Base64.decode(((Text) node).getData()); |
| } catch (WSSecurityException e) { |
| return null; |
| } |
| } |
| return skiBytes; |
| } |
| |
| /* |
| * Here the methods that handle the IssuerSerial key identification |
| */ |
| |
| /** |
| * Sets the X509 IssuerSerial data. |
| * |
| * @param ref the {@link XMLX509IssuerSerial} to put into this |
| * SecurityTokenReference |
| */ |
| public void setX509IssuerSerial(X509Data ref) { |
| Element elem = getFirstElement(); |
| if (elem != null) { |
| this.element.replaceChild(ref.getElement(), elem); |
| } else { |
| this.element.appendChild(ref.getElement()); |
| } |
| } |
| |
| /** |
| * Gets the certificate identified with X509 issuerSerial data. |
| * This method first tries to get the embedded certificate. |
| * If this fails it checks if the certificate is in the |
| * keystore. |
| * |
| * @return a certificate array or null if nothing found |
| */ |
| public X509Certificate[] getX509IssuerSerial(Crypto crypto) |
| throws WSSecurityException { |
| String alias = getX509IssuerSerialAlias(crypto); |
| if (alias != null) { |
| return crypto.getCertificates(alias); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the alias name of the certificate identified with X509 issuerSerial data. |
| * The keystore identifies the certificate and the key with this alias name. |
| * |
| * @return the alias name for the certificate or null if nothing found |
| */ |
| public String getX509IssuerSerialAlias(Crypto crypto) |
| throws WSSecurityException { |
| if (issuerSerial == null) { |
| issuerSerial = getIssuerSerial(); |
| if (issuerSerial == null) { |
| return null; |
| } |
| } |
| |
| String alias = crypto.getAliasForX509Cert(issuerSerial.getIssuerName(), |
| issuerSerial.getSerialNumber()); |
| |
| if (doDebug) { |
| log.info("X509IssuerSerial alias: " + alias); |
| } |
| return alias; |
| } |
| |
| private XMLX509IssuerSerial getIssuerSerial() throws WSSecurityException { |
| if (issuerSerial != null) { |
| return issuerSerial; |
| } |
| Element elem = getFirstElement(); |
| if (elem == null) { |
| return null; |
| } |
| try { |
| if (Constants._TAG_X509DATA.equals(elem.getLocalName())) { |
| elem = (Element)WSSecurityUtil.findElement(elem, Constants._TAG_X509ISSUERSERIAL, Constants.SignatureSpecNS); |
| } |
| issuerSerial = new XMLX509IssuerSerial(elem, ""); |
| } catch (XMLSecurityException e) { |
| throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, |
| "noToken", |
| new Object[]{"Issuer/Serial data element missing"}, |
| e); |
| } |
| return issuerSerial; |
| } |
| |
| /* |
| * Several helper and utility methods. |
| */ |
| |
| /** |
| * Method containsReference |
| * |
| * @return true if the <code>SecurtityTokenReference</code> contains |
| * a <code>wsse:Reference</code> element |
| */ |
| public boolean containsReference() { |
| return this.lengthReference() > 0; |
| } |
| |
| /** |
| * Method lengthReference. |
| * |
| * @return number of <code>wsse:Reference</code> elements in |
| * the <code>SecurtityTokenReference</code> |
| */ |
| public int lengthReference() { |
| return this.length(WSConstants.WSSE_NS, "Reference"); |
| } |
| |
| /** |
| * Method containsX509IssuerSerial |
| * |
| * @return true if the <code>SecurtityTokenReference</code> contains |
| * a <code>ds:IssuerSerial</code> element |
| */ |
| public boolean containsX509IssuerSerial() { |
| return this.lengthX509IssuerSerial() > 0; |
| } |
| |
| /** |
| * Method containsX509Data |
| * |
| * @return true if the <code>SecurtityTokenReference</code> contains |
| * a <code>ds:X509Data</code> element |
| */ |
| public boolean containsX509Data() { |
| return this.lengthX509Data() > 0; |
| } |
| /** |
| * Method lengthX509IssuerSerial. |
| * |
| * @return number of <code>ds:IssuerSerial</code> elements in |
| * the <code>SecurtityTokenReference</code> |
| */ |
| public int lengthX509IssuerSerial() { |
| return this.length(WSConstants.SIG_NS, Constants._TAG_X509ISSUERSERIAL); |
| } |
| |
| /** |
| * Method lengthX509Data. |
| * |
| * @return number of <code>ds:IssuerSerial</code> elements in |
| * the <code>SecurtityTokenReference</code> |
| */ |
| public int lengthX509Data() { |
| return this.length(WSConstants.SIG_NS, Constants._TAG_X509DATA); |
| } |
| /** |
| * Method containsKeyIdentifier. |
| * |
| * @return true if the <code>SecurtityTokenReference</code> contains |
| * a <code>wsse:KeyIdentifier</code> element |
| */ |
| public boolean containsKeyIdentifier() { |
| return this.lengthKeyIdentifier() > 0; |
| } |
| |
| /** |
| * Method lengthKeyIdentifier. |
| * |
| * @return number of <code>wsse:KeyIdentifier</code> elements in |
| * the <code>SecurtityTokenReference</code> |
| */ |
| public int lengthKeyIdentifier() { |
| return this.length(WSConstants.WSSE_NS, "KeyIdentifier"); |
| } |
| |
| /** |
| * Method length. |
| * |
| * @param namespace |
| * @param localname |
| * @return number of elements with matching localname and namespace |
| */ |
| public int length(String namespace, String localname) { |
| NodeList childNodes = this.element.getChildNodes(); |
| int maxLength = childNodes.getLength(); |
| int result = 0; |
| for (int i = 0; i < maxLength; i++) { |
| Node n = childNodes.item(i); |
| if (n.getNodeType() == Node.ELEMENT_NODE) { |
| String ns = n.getNamespaceURI(); |
| String name = n.getLocalName(); |
| if (((namespace != null) |
| && (ns != null) |
| && namespace.equals(ns)) |
| || ((namespace == null) && (ns == null))) { |
| if (localname.equals(name)) { |
| result++; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * get the dom element. |
| * <p/> |
| * |
| * @return TODO |
| */ |
| public Element getElement() { |
| return this.element; |
| } |
| |
| /** |
| * set the id. |
| * <p/> |
| * |
| * @param id |
| */ |
| public void setID(String id) { |
| String prefix = |
| WSSecurityUtil.setNamespace(this.element, |
| WSConstants.WSU_NS, |
| WSConstants.WSU_PREFIX); |
| this.element.setAttributeNS(WSConstants.WSU_NS, prefix + ":Id", id); |
| } |
| |
| /** |
| * return the string representation. |
| * <p/> |
| * |
| * @return TODO |
| */ |
| public String toString() { |
| return DOM2Writer.nodeToString((Node) this.element); |
| } |
| } |