Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.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.kerberos.KrbSession; import org.apache.ws.security.kerberos.KrbSessionCache; 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. * * @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. * * @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. * * @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); } /** * set the reference. * * @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 SecurityTokenReference * @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 = findTokenElement(doc, docInfo, cb, uri, ref.getValueType()); if (tokElement == null) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "noToken", new Object[] { uri }); } return tokElement; } /** * Gets the signing token element, which may be a <code>BinarySecurityToken * </code> or a SAML token. * * The method gets the value of the KeyIdentifier 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 */ public Element getKeyIdentifierTokenElement(Document doc, WSDocInfo docInfo, CallbackHandler cb) throws WSSecurityException { String value = getKeyIdentifierValue(); String type = getKeyIdentifierValueType(); if (doDebug) { log.debug("Token reference uri: " + value); } if (value == null) { throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, "badReferenceURI"); } Element tokElement = findTokenElement(doc, docInfo, cb, value, type); if (tokElement == null) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "noToken", new Object[] { value }); } return tokElement; } private Element findTokenElement(Document doc, WSDocInfo docInfo, CallbackHandler cb, String uri, String type) { Element tokElement = null; String id = uri; if (id.charAt(0) == '#') { id = id.substring(1); } // // If the type is a SAMLAssertionID then find the SAML assertion - first check // if it has been previously processed, else search the header for it // String assertionStr = WSConstants.WSS_SAML_NS + WSConstants.ASSERTION_LN; if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(type) || assertionStr.equals(type)) { Element sa = docInfo.getAssertion(); if (sa == null) { sa = (Element) WSSecurityUtil.findElement(docInfo.getDocument().getDocumentElement(), WSConstants.ASSERTION_LN, WSConstants.SAML_NS); } if (sa != null) { String saID = sa.getAttribute("AssertionID"); if (doDebug) { log.debug("SAML token ID: " + saID); } if (saID.equals(id)) { tokElement = sa; } } if (tokElement == null) { Node assertion = WSSecurityUtil.findSAMLAssertionElementById(doc.getDocumentElement(), id); if (assertion != null) { tokElement = (Element) assertion; } } } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(type) || assertionStr.equals(type)) { Element sa = docInfo.getAssertion(); if (sa == null) { sa = (Element) WSSecurityUtil.findElement(docInfo.getDocument().getDocumentElement(), "Assertion", WSConstants.SAML2_NS); } if (sa != null) { String saID = sa.getAttribute("ID"); if (doDebug) log.debug((new StringBuilder()).append("SAML token ID: ").append(saID).toString()); if (saID.equals(id)) tokElement = sa; } if (tokElement == null) { Node assertion = WSSecurityUtil.findSAMLAssertionElementById(doc.getDocumentElement(), id); if (assertion != null) tokElement = (Element) assertion; } } // // Try to find a custom token // if (tokElement == null && cb != null && (WSConstants.WSC_SCT.equals(type) || WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(type) || WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(type) || assertionStr.equals(type))) { // try to find a custom token WSPasswordCallback pwcb = new WSPasswordCallback(id, WSPasswordCallback.CUSTOM_TOKEN); try { cb.handle(new Callback[] { pwcb }); Element assertionElem = pwcb.getCustomToken(); if (assertionElem != null) { tokElement = (Element) doc.importNode(assertionElem, true); } } catch (Exception e) { log.debug(e.getMessage(), e); // Consume this failure } } // // Finally try to find the element by its Id // if (tokElement == null) { tokElement = WSSecurityUtil.getElementByWsuId(doc, uri); // In some scenarios id is used rather than wsu:Id if (tokElement == null) { tokElement = WSSecurityUtil.getElementByGenId(doc, uri); } } return tokElement; } /** * 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)); createKeyIdentifier(doc, X509Security.X509_V3_TYPE, text, true); } /** * 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, true); } /** * 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, true); } 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, true); } public void setSAMLKeyIdentifier(String keyIdVal) throws WSSecurityException { Document doc = this.element.getOwnerDocument(); createKeyIdentifier(doc, SAML_ID_URI, doc.createTextNode(keyIdVal), false); } public void setKeyIdentifier(String valueType, String keyIdVal) throws WSSecurityException { Document doc = this.element.getOwnerDocument(); createKeyIdentifier(doc, valueType, doc.createTextNode(keyIdVal), false); } private void createKeyIdentifier(Document doc, String uri, Node node, boolean base64) { Element keyId = doc.createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier"); keyId.setAttributeNS(null, "ValueType", uri); if (base64) { 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); } } /** * 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 { Element elem = getFirstElement(); String value = elem.getAttribute("ValueType"); String alias = null; if (X509Security.X509_V3_TYPE.equals(value)) { X509Security 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; } /** * 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; } /** * 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 result = 0; for (int i = 0; i < childNodes.getLength(); i++) { Node n = childNodes.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { String ns = n.getNamespaceURI(); String name = n.getLocalName(); if ((((namespace != null) && namespace.equals(ns)) || ((namespace == null) && (ns == null))) && (localname.equals(name))) { result++; } } } return result; } /** * get the dom element. * * @return TODO */ public Element getElement() { return this.element; } /** * set the id. * * @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. * * @return TODO */ public String toString() { return DOM2Writer.nodeToString((Node) this.element); } /** * * @param session */ public void setKerberosIdentifierThumb(KrbSession session) { Document doc = element.getOwnerDocument(); Text node = doc.createTextNode(session.getThumbPrintEncoded()); createKeyIdentifier(doc, KerberosSecurity.GSS_KERBEROSv5_AP_REQ_SHA1, node, false); } public boolean containsKerberosThumbprint() { NodeList childNodes = element.getChildNodes(); int maxLength = childNodes.getLength(); for (int i = 0; i < maxLength; i++) { Node n = childNodes.item(i); if (n.getNodeType() != 1) continue; Node valueType = n.getAttributes().getNamedItem("ValueType"); if (valueType != null && valueType.getNodeValue().equals( "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#Kerberosv5APREQSHA1")) return true; } return false; } public KrbSession getKerberosSession() { KrbSession result = null; Element elem = (Element) WSSecurityUtil.findElement(element, "KeyIdentifier", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); String thumbPrint = elem.toString(); thumbPrint = thumbPrint.substring(thumbPrint.indexOf(">") + 1); thumbPrint = thumbPrint.substring(0, thumbPrint.indexOf("<")); result = KrbSessionCache.getInstance().getSession(thumbPrint); return result; } }