| package org.apache.ws.security.saml; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.ws.security.SOAPConstants; |
| import org.apache.ws.security.WSConstants; |
| import org.apache.ws.security.WSDocInfo; |
| import org.apache.ws.security.WSDocInfoStore; |
| import org.apache.ws.security.WSEncryptionPart; |
| import org.apache.ws.security.WSSecurityException; |
| import org.apache.ws.security.components.crypto.Crypto; |
| import org.apache.ws.security.message.EnvelopeIdResolver; |
| import org.apache.ws.security.message.WSSecHeader; |
| import org.apache.ws.security.message.WSSecSignature; |
| import org.apache.ws.security.message.token.Reference; |
| import org.apache.ws.security.message.token.SecurityTokenReference; |
| import org.apache.ws.security.message.token.X509Security; |
| import org.apache.ws.security.transform.STRTransform; |
| import org.apache.ws.security.util.WSSecurityUtil; |
| import org.apache.xml.security.algorithms.SignatureAlgorithm; |
| import org.apache.xml.security.exceptions.XMLSecurityException; |
| import org.apache.xml.security.keys.KeyInfo; |
| import org.apache.xml.security.keys.content.X509Data; |
| import org.apache.xml.security.keys.content.x509.XMLX509Certificate; |
| import org.apache.xml.security.signature.XMLSignature; |
| import org.apache.xml.security.signature.XMLSignatureException; |
| import org.apache.xml.security.transforms.TransformationException; |
| import org.apache.xml.security.transforms.Transforms; |
| import org.apache.xml.security.transforms.params.InclusiveNamespaces; |
| import org.apache.xml.security.utils.Constants; |
| import org.apache.xml.security.utils.XMLUtils; |
| import org.opensaml.SAMLAssertion; |
| import org.opensaml.SAMLException; |
| import org.opensaml.SAMLObject; |
| import org.opensaml.SAMLSubject; |
| import org.opensaml.SAMLSubjectStatement; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| import java.security.cert.X509Certificate; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.Vector; |
| |
| public class WSSecSignatureSAML extends WSSecSignature { |
| |
| private static Log log = LogFactory.getLog(WSSecSignatureSAML.class |
| .getName()); |
| |
| private boolean senderVouches = false; |
| |
| private SecurityTokenReference secRefSaml = null; |
| |
| private Element samlToken = null; |
| |
| private Crypto userCrypto = null; |
| |
| private Crypto issuerCrypto = null; |
| |
| private String issuerKeyName = null; |
| |
| private String issuerKeyPW = null; |
| |
| /** |
| * Constructor. |
| */ |
| public WSSecSignatureSAML() { |
| } |
| |
| /** |
| * Builds a signed soap envelope with SAML token. |
| * |
| * <p/> |
| * |
| * The method first gets an appropriate security header. According to the |
| * defined parameters for certificate handling the signature elements are |
| * constructed and inserted into the <code>wsse:Signature</code> |
| * |
| * @param doc |
| * The unsigned SOAP envelope as <code>Document</code> |
| * @param uCrypto |
| * The user's Crypto instance |
| * @param assertion |
| * the complete SAML assertion |
| * @param iCrypto |
| * An instance of the Crypto API to handle keystore SAML token |
| * issuer and to generate certificates |
| * @param iKeyName |
| * Private key to use in case of "sender-Vouches" |
| * @param iKeyPW |
| * Password for issuer private key |
| * @param secHeader |
| * The Security header |
| * @return A signed SOAP envelope as <code>Document</code> |
| * @throws org.apache.ws.security.WSSecurityException |
| */ |
| public Document build(Document doc, Crypto uCrypto, |
| SAMLAssertion assertion, Crypto iCrypto, String iKeyName, |
| String iKeyPW, WSSecHeader secHeader) throws WSSecurityException { |
| |
| prepare(doc, uCrypto, assertion, iCrypto, iKeyName, iKeyPW, secHeader); |
| |
| SOAPConstants soapConstants = WSSecurityUtil.getSOAPConstants(doc |
| .getDocumentElement()); |
| |
| if (parts == null) { |
| parts = new Vector(); |
| WSEncryptionPart encP = new WSEncryptionPart(soapConstants |
| .getBodyQName().getLocalPart(), soapConstants |
| .getEnvelopeURI(), "Content"); |
| parts.add(encP); |
| } |
| addReferencesToSign(parts, secHeader); |
| |
| /* |
| * The order to prepend is: - signature Element - BinarySecurityToken |
| * (depends on mode) - SecurityTokenRefrence (depends on mode) - SAML |
| * token |
| */ |
| |
| prependToHeader(secHeader); |
| |
| /* |
| * if we have a BST prepend it in front of the Signature according to |
| * strict layout rules. |
| */ |
| if (bstToken != null) { |
| prependBSTElementToHeader(secHeader); |
| } |
| |
| prependSAMLElementsToHeader(secHeader); |
| |
| computeSignature(); |
| |
| return doc; |
| } |
| |
| /** |
| * Initialize a WSSec SAML Signature. |
| * |
| * The method sets up and initializes a WSSec SAML Signature structure after |
| * the relevant information was set. After setup of the references to |
| * elements to sign may be added. After all references are added they can be |
| * signed. |
| * |
| * <p/> |
| * |
| * This method does not add the Signature element to the security header. |
| * See <code>prependSignatureElementToHeader()</code> method. |
| * |
| * @param doc |
| * The SOAP envelope as <code>Document</code> |
| * @param uCrypto |
| * The user's Crypto instance |
| * @param assertion |
| * the complete SAML assertion |
| * @param iCrypto |
| * An instance of the Crypto API to handle keystore SAML token |
| * issuer and to generate certificates |
| * @param iKeyName |
| * Private key to use in case of "sender-Vouches" |
| * @param iKeyPW |
| * Password for issuer private key |
| * @param secHeader |
| * The Security header |
| * @throws WSSecurityException |
| */ |
| public void prepare(Document doc, Crypto uCrypto, SAMLAssertion assertion, |
| Crypto iCrypto, String iKeyName, String iKeyPW, |
| WSSecHeader secHeader) throws WSSecurityException { |
| |
| doDebug = log.isDebugEnabled(); |
| if (doDebug) { |
| log.debug("Beginning ST signing..."); |
| } |
| |
| userCrypto = uCrypto; |
| issuerCrypto = iCrypto; |
| document = doc; |
| issuerKeyName = iKeyName; |
| issuerKeyPW = iKeyPW; |
| |
| /* |
| * Get some information about the SAML token content. This controls how |
| * to deal with the whole stuff. First get the Authentication statement |
| * (includes Subject), then get the _first_ confirmation method only |
| * thats if "senderVouches" is true. |
| */ |
| SAMLSubjectStatement samlSubjS = null; |
| Iterator it = assertion.getStatements(); |
| while (it.hasNext()) { |
| SAMLObject so = (SAMLObject) it.next(); |
| if (so instanceof SAMLSubjectStatement) { |
| samlSubjS = (SAMLSubjectStatement) so; |
| break; |
| } |
| } |
| SAMLSubject samlSubj = null; |
| if (samlSubjS != null) { |
| samlSubj = samlSubjS.getSubject(); |
| } |
| if (samlSubj == null) { |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "invalidSAMLToken", new Object[] { "for Signature" }); |
| } |
| |
| String confirmMethod = null; |
| it = samlSubj.getConfirmationMethods(); |
| if (it.hasNext()) { |
| confirmMethod = (String) it.next(); |
| } |
| if (SAMLSubject.CONF_SENDER_VOUCHES.equals(confirmMethod)) { |
| senderVouches = true; |
| } |
| /* |
| * Gather some info about the document to process and store it for |
| * retrieval |
| */ |
| wsDocInfo = new WSDocInfo(doc.hashCode()); |
| |
| X509Certificate[] certs = null; |
| |
| if (senderVouches) { |
| certs = issuerCrypto.getCertificates(issuerKeyName); |
| wsDocInfo.setCrypto(issuerCrypto); |
| } |
| /* |
| * in case of key holder: - get the user's certificate that _must_ be |
| * included in the SAML token. To ensure the cert integrity the SAML |
| * token must be signed (by the issuer). Just check if its signed, but |
| * don't verify this SAML token's signature here (maybe later). |
| */ |
| else { |
| if (userCrypto == null || assertion.isSigned() == false) { |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "invalidSAMLsecurity", |
| new Object[] { "for SAML Signature (Key Holder)" }); |
| } |
| Element e = samlSubj.getKeyInfo(); |
| try { |
| KeyInfo ki = new KeyInfo(e, null); |
| |
| if (ki.containsX509Data()) { |
| X509Data data = ki.itemX509Data(0); |
| XMLX509Certificate certElem = null; |
| if (data != null && data.containsCertificate()) { |
| certElem = data.itemCertificate(0); |
| } |
| if (certElem != null) { |
| X509Certificate cert = certElem.getX509Certificate(); |
| certs = new X509Certificate[1]; |
| certs[0] = cert; |
| } |
| } |
| // TODO: get alias name for cert, check against username set by |
| // caller |
| } catch (XMLSecurityException e3) { |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "invalidSAMLsecurity", |
| new Object[] { "cannot get certificate (key holder)" }, |
| e3); |
| } |
| wsDocInfo.setCrypto(userCrypto); |
| } |
| if (certs == null || certs.length <= 0) { |
| throw new WSSecurityException( |
| WSSecurityException.FAILURE, |
| "noCertsFound", |
| new Object[] { "SAML signature" } |
| ); |
| } |
| if (sigAlgo == null) { |
| String pubKeyAlgo = certs[0].getPublicKey().getAlgorithm(); |
| log.debug("automatic sig algo detection: " + pubKeyAlgo); |
| if (pubKeyAlgo.equalsIgnoreCase("DSA")) { |
| sigAlgo = XMLSignature.ALGO_ID_SIGNATURE_DSA; |
| } else if (pubKeyAlgo.equalsIgnoreCase("RSA")) { |
| sigAlgo = XMLSignature.ALGO_ID_SIGNATURE_RSA; |
| } else { |
| throw new WSSecurityException( |
| WSSecurityException.FAILURE, |
| "unknownSignatureAlgorithm", |
| new Object[] { |
| pubKeyAlgo |
| } |
| ); |
| } |
| } |
| sig = null; |
| if (canonAlgo.equals(WSConstants.C14N_EXCL_OMIT_COMMENTS)) { |
| Element canonElem = XMLUtils.createElementInSignatureSpace(doc, |
| Constants._TAG_CANONICALIZATIONMETHOD); |
| |
| canonElem.setAttributeNS(null, Constants._ATT_ALGORITHM, canonAlgo); |
| |
| if (wssConfig.isWsiBSPCompliant()) { |
| Set prefixes = getInclusivePrefixes(secHeader |
| .getSecurityHeader(), false); |
| |
| InclusiveNamespaces inclusiveNamespaces = new InclusiveNamespaces( |
| doc, prefixes); |
| |
| canonElem.appendChild(inclusiveNamespaces.getElement()); |
| } |
| try { |
| SignatureAlgorithm signatureAlgorithm = new SignatureAlgorithm( |
| doc, sigAlgo); |
| sig = new XMLSignature(doc, null, signatureAlgorithm |
| .getElement(), canonElem); |
| } catch (XMLSecurityException e) { |
| log.error("", e); |
| throw new WSSecurityException( |
| WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, e |
| ); |
| } |
| } else { |
| try { |
| sig = new XMLSignature(doc, null, sigAlgo, canonAlgo); |
| } catch (XMLSecurityException e) { |
| log.error("", e); |
| throw new WSSecurityException( |
| WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, e |
| ); |
| } |
| } |
| |
| sig.addResourceResolver(EnvelopeIdResolver.getInstance()); |
| String sigUri = "Signature-" + sig.hashCode(); |
| sig.setId(sigUri); |
| |
| keyInfo = sig.getKeyInfo(); |
| keyInfoUri = "KeyId-" + keyInfo.hashCode(); |
| keyInfo.setId(keyInfoUri); |
| |
| secRef = new SecurityTokenReference(doc); |
| strUri = "STRId-" + secRef.hashCode(); |
| secRef.setID(strUri); |
| |
| certUri = "CertId-" + certs[0].hashCode(); |
| |
| /* |
| * If the sender vouches, then we must sign the SAML token _and_ at |
| * least one part of the message (usually the SOAP body). To do so we |
| * need to - put in a reference to the SAML token. Thus we create a STR |
| * and insert it into the wsse:Security header - set a reference of the |
| * created STR to the signature and use STR Transfrom during the |
| * signature |
| */ |
| Transforms transforms = null; |
| |
| try { |
| if (senderVouches) { |
| secRefSaml = new SecurityTokenReference(doc); |
| String strSamlUri = "STRSAMLId-" + secRefSaml.hashCode(); |
| secRefSaml.setID(strSamlUri); |
| |
| // Decouple Reference/KeyInfo setup - quick shot here |
| Reference ref = new Reference(doc); |
| ref.setURI("#" + assertion.getId()); |
| ref.setValueType(WSConstants.WSS_SAML_NS |
| + WSConstants.WSS_SAML_ASSERTION); |
| secRefSaml.setReference(ref); |
| // up to here |
| |
| Element ctx = createSTRParameter(doc); |
| transforms = new Transforms(doc); |
| transforms.addTransform(STRTransform.implementedTransformURI, |
| ctx); |
| sig.addDocument("#" + strSamlUri, transforms); |
| } |
| } catch (TransformationException e1) { |
| throw new WSSecurityException(WSSecurityException.FAILED_SIGNATURE, |
| "noXMLSig", null, e1); |
| } catch (XMLSignatureException e1) { |
| throw new WSSecurityException(WSSecurityException.FAILED_SIGNATURE, |
| "noXMLSig", null, e1); |
| } |
| |
| switch (keyIdentifierType) { |
| case WSConstants.BST_DIRECT_REFERENCE: |
| Reference ref = new Reference(doc); |
| if (senderVouches) { |
| ref.setURI("#" + certUri); |
| bstToken = new X509Security(doc); |
| ((X509Security) bstToken).setX509Certificate(certs[0]); |
| bstToken.setID(certUri); |
| wsDocInfo.setBst(bstToken.getElement()); |
| ref.setValueType(bstToken.getValueType()); |
| } else { |
| ref.setURI("#" + assertion.getId()); |
| ref.setValueType(WSConstants.WSS_SAML_NS |
| + WSConstants.WSS_SAML_ASSERTION); |
| } |
| secRef.setReference(ref); |
| break; |
| // |
| // case WSConstants.ISSUER_SERIAL : |
| // XMLX509IssuerSerial data = |
| // new XMLX509IssuerSerial(doc, certs[0]); |
| // secRef.setX509IssuerSerial(data); |
| // break; |
| // |
| // case WSConstants.X509_KEY_IDENTIFIER : |
| // secRef.setKeyIdentifier(certs[0]); |
| // break; |
| // |
| // case WSConstants.SKI_KEY_IDENTIFIER : |
| // secRef.setKeyIdentifierSKI(certs[0], crypto); |
| // break; |
| // |
| default: |
| throw new WSSecurityException(WSSecurityException.FAILURE, |
| "unsupportedKeyId"); |
| } |
| |
| keyInfo.addUnknownElement(secRef.getElement()); |
| |
| Element keyInfoElement = keyInfo.getElement(); |
| keyInfoElement.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:" |
| + WSConstants.SIG_PREFIX, WSConstants.SIG_NS); |
| |
| try { |
| samlToken = (Element) assertion.toDOM(doc); |
| } catch (SAMLException e2) { |
| throw new WSSecurityException(WSSecurityException.FAILED_SIGNATURE, |
| "noSAMLdoc", null, e2); |
| } |
| wsDocInfo.setAssertion(samlToken); |
| } |
| |
| /** |
| * Prepend the SAML elements to the elements already in the Security header. |
| * |
| * The method can be called any time after <code>prepare()</code>. This |
| * allows to insert the SAML elements at any position in the Security |
| * header. |
| * |
| * <p/> |
| * |
| * This methods first prepends the SAML security reference if mode is |
| * <code>senderVouches</code>, then the SAML token itself, |
| * |
| * @param secHeader |
| * The security header that holds the BST element. |
| */ |
| public void prependSAMLElementsToHeader(WSSecHeader secHeader) { |
| if (senderVouches) { |
| WSSecurityUtil.prependChildElement(document, secHeader |
| .getSecurityHeader(), secRefSaml.getElement(), true); |
| } |
| |
| WSSecurityUtil.prependChildElement(document, secHeader |
| .getSecurityHeader(), samlToken, true); |
| } |
| |
| /** |
| * This method adds references to the Signature. |
| * |
| * The added references are signed when calling |
| * <code>computeSignature()</code>. This method can be called several |
| * times to add references as required. <code>addReferencesToSign()</code> |
| * can be called anytime after <code>prepare</code>. |
| * |
| * @param references |
| * A vector containing <code>WSEncryptionPart</code> objects |
| * that define the parts to sign. |
| * @param secHeader |
| * Used to compute namespaces to be inserted by |
| * InclusiveNamespaces to be WSI compliant. |
| * @throws WSSecurityException |
| */ |
| public void addReferencesToSign(Vector references, WSSecHeader secHeader) |
| throws WSSecurityException { |
| Transforms transforms = null; |
| |
| Element envelope = document.getDocumentElement(); |
| for (int part = 0; part < parts.size(); part++) { |
| WSEncryptionPart encPart = (WSEncryptionPart) references.get(part); |
| |
| String idToSign = encPart.getId(); |
| |
| String elemName = encPart.getName(); |
| String nmSpace = encPart.getNamespace(); |
| |
| /* |
| * Set up the elements to sign. There are two resevered element |
| * names: "Token" and "STRTransform" "Token": Setup the Signature to |
| * either sign the information that points to the security token or |
| * the token itself. If its a direct reference sign the token, |
| * otherwise sign the KeyInfo Element. "STRTransform": Setup the |
| * ds:Reference to use STR Transform |
| * |
| */ |
| transforms = new Transforms(document); |
| try { |
| if (idToSign != null) { |
| Element toSignById = WSSecurityUtil.findElementById( |
| document.getDocumentElement(), idToSign, |
| WSConstants.WSU_NS); |
| if (toSignById == null) { |
| toSignById = WSSecurityUtil.findElementById(document |
| .getDocumentElement(), idToSign, null); |
| } |
| transforms |
| .addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); |
| if (wssConfig.isWsiBSPCompliant()) { |
| transforms.item(0).getElement().appendChild( |
| new InclusiveNamespaces(document, |
| getInclusivePrefixes(toSignById)) |
| .getElement()); |
| } |
| sig.addDocument("#" + idToSign, transforms); |
| } else if (elemName.equals("Token")) { |
| transforms |
| .addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); |
| if (keyIdentifierType == WSConstants.BST_DIRECT_REFERENCE) { |
| if (wssConfig.isWsiBSPCompliant()) { |
| transforms.item(0).getElement().appendChild( |
| new InclusiveNamespaces(document, |
| getInclusivePrefixes(secHeader |
| .getSecurityHeader())) |
| .getElement()); |
| } |
| sig.addDocument("#" + certUri, transforms); |
| } else { |
| if (wssConfig.isWsiBSPCompliant()) { |
| transforms.item(0).getElement().appendChild( |
| new InclusiveNamespaces(document, |
| getInclusivePrefixes(keyInfo |
| .getElement())) |
| .getElement()); |
| } |
| sig.addDocument("#" + keyInfoUri, transforms); |
| } |
| } else if (elemName.equals("STRTransform")) { // STRTransform |
| Element ctx = createSTRParameter(document); |
| transforms.addTransform( |
| STRTransform.implementedTransformURI, ctx); |
| sig.addDocument("#" + strUri, transforms); |
| } else { |
| Element body = (Element) WSSecurityUtil.findElement( |
| envelope, elemName, nmSpace); |
| if (body == null) { |
| throw new WSSecurityException( |
| WSSecurityException.FAILURE, "noEncElement", |
| new Object[] { nmSpace + ", " + elemName }); |
| } |
| transforms |
| .addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); |
| if (wssConfig.isWsiBSPCompliant()) { |
| transforms.item(0).getElement().appendChild( |
| new InclusiveNamespaces(document, |
| getInclusivePrefixes(body)) |
| .getElement()); |
| } |
| sig.addDocument("#" + setWsuId(body), transforms); |
| } |
| } catch (TransformationException e1) { |
| throw new WSSecurityException( |
| WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, |
| e1); |
| } catch (XMLSignatureException e1) { |
| throw new WSSecurityException( |
| WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, |
| e1); |
| } |
| } |
| } |
| |
| /** |
| * Compute the Signature over the references. |
| * |
| * After references are set this method computes the Signature for them. |
| * This method can be called anytime after the references were set. See |
| * <code>addReferencesToSign()</code>. |
| * |
| * @throws WSSecurityException |
| */ |
| public void computeSignature() throws WSSecurityException { |
| |
| WSDocInfoStore.store(wsDocInfo); |
| |
| try { |
| if (senderVouches) { |
| sig |
| .sign(issuerCrypto.getPrivateKey(issuerKeyName, |
| issuerKeyPW)); |
| } else { |
| sig.sign(userCrypto.getPrivateKey(user, password)); |
| } |
| signatureValue = sig.getSignatureValue(); |
| } catch (XMLSignatureException e1) { |
| throw new WSSecurityException(WSSecurityException.FAILED_SIGNATURE, |
| null, null, e1); |
| } catch (Exception e1) { |
| throw new WSSecurityException(WSSecurityException.FAILED_SIGNATURE, |
| null, null, e1); |
| } finally { |
| WSDocInfoStore.delete(wsDocInfo); |
| } |
| } |
| } |