| /** |
| * 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.wss4j.dom.saml; |
| |
| import java.security.PublicKey; |
| import java.security.cert.X509Certificate; |
| import java.util.List; |
| |
| import javax.xml.crypto.dsig.SignatureMethod; |
| import javax.xml.crypto.dsig.SignedInfo; |
| import javax.xml.crypto.dsig.XMLSignContext; |
| import javax.xml.crypto.dsig.dom.DOMSignContext; |
| import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; |
| import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec; |
| |
| import org.apache.wss4j.common.SignatureActionToken; |
| import org.apache.wss4j.common.WSEncryptionPart; |
| import org.apache.wss4j.common.crypto.Crypto; |
| import org.apache.wss4j.common.crypto.CryptoType; |
| import org.apache.wss4j.common.ext.WSSecurityException; |
| import org.apache.wss4j.common.saml.OpenSAMLUtil; |
| import org.apache.wss4j.common.saml.SAMLKeyInfo; |
| import org.apache.wss4j.common.saml.SAMLUtil; |
| import org.apache.wss4j.common.saml.SamlAssertionWrapper; |
| import org.apache.wss4j.common.token.BinarySecurity; |
| import org.apache.wss4j.common.token.DOMX509Data; |
| import org.apache.wss4j.common.token.DOMX509IssuerSerial; |
| import org.apache.wss4j.common.token.Reference; |
| import org.apache.wss4j.common.token.SecurityTokenReference; |
| import org.apache.wss4j.common.token.X509Security; |
| import org.apache.wss4j.common.util.KeyUtils; |
| import org.apache.wss4j.dom.WSConstants; |
| import org.apache.wss4j.dom.WSDocInfo; |
| import org.apache.wss4j.dom.handler.RequestData; |
| import org.apache.wss4j.dom.message.WSSecHeader; |
| import org.apache.wss4j.dom.message.WSSecSignature; |
| import org.apache.wss4j.dom.transform.STRTransform; |
| import org.apache.wss4j.dom.util.WSSecurityUtil; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| public class WSSecSignatureSAML extends WSSecSignature { |
| |
| private static final org.slf4j.Logger LOG = |
| org.slf4j.LoggerFactory.getLogger(WSSecSignatureSAML.class); |
| private boolean senderVouches; |
| private SecurityTokenReference secRefSaml; |
| private String secRefID; |
| private Element samlToken; |
| private Crypto userCrypto; |
| private Crypto issuerCrypto; |
| private String issuerKeyName; |
| private String issuerKeyPW; |
| private boolean useDirectReferenceToAssertion; |
| |
| /** |
| * Constructor. |
| */ |
| public WSSecSignatureSAML(WSSecHeader securityHeader) { |
| super(securityHeader); |
| } |
| |
| public WSSecSignatureSAML(Document doc) { |
| super(doc); |
| } |
| |
| /** |
| * Builds a signed soap envelope with SAML token. |
| * |
| * 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 uCrypto |
| * The user's Crypto instance |
| * @param samlAssertion |
| * 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 |
| * @return A signed SOAP envelope as <code>Document</code> |
| * @throws WSSecurityException |
| */ |
| public Document build( |
| Crypto uCrypto, SamlAssertionWrapper samlAssertion, |
| Crypto iCrypto, String iKeyName, String iKeyPW |
| ) throws WSSecurityException { |
| |
| prepare(uCrypto, samlAssertion, iCrypto, iKeyName, iKeyPW); |
| |
| if (getParts().isEmpty()) { |
| getParts().add(WSSecurityUtil.getDefaultEncryptionPart(getDocument())); |
| } else { |
| for (WSEncryptionPart part : getParts()) { |
| if ("STRTransform".equals(part.getName()) && part.getId() == null) { |
| part.setId(strUri); |
| } |
| } |
| } |
| |
| // |
| // Add the STRTransform for the SecurityTokenReference to the SAML assertion |
| // if it exists |
| // |
| if (secRefID != null) { |
| String soapNamespace = |
| WSSecurityUtil.getSOAPNamespace(getDocument().getDocumentElement()); |
| WSEncryptionPart encP = |
| new WSEncryptionPart("STRTransform", soapNamespace, "Content"); |
| encP.setId(secRefID); |
| getParts().add(encP); |
| } |
| |
| List<javax.xml.crypto.dsig.Reference> referenceList = addReferencesToSign(getParts()); |
| |
| prependSAMLElementsToHeader(); |
| |
| if (senderVouches) { |
| computeSignature(referenceList, secRefSaml.getElement()); |
| } else { |
| computeSignature(referenceList, samlToken); |
| } |
| |
| // |
| // if we have a BST prepend it in front of the Signature according to |
| // strict layout rules. |
| // |
| if (bstToken != null) { |
| prependBSTElementToHeader(); |
| } |
| |
| return getDocument(); |
| } |
| |
| /** |
| * 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. |
| * |
| * This method does not add the Signature element to the security header. |
| * See <code>prependSignatureElementToHeader()</code> method. |
| * |
| * @param uCrypto |
| * The user's Crypto instance |
| * @param samlAssertion |
| * 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 |
| * @throws WSSecurityException |
| */ |
| public void prepare( |
| Crypto uCrypto, SamlAssertionWrapper samlAssertion, Crypto iCrypto, |
| String iKeyName, String iKeyPW |
| ) throws WSSecurityException { |
| |
| LOG.debug("Beginning ST signing..."); |
| |
| userCrypto = uCrypto; |
| issuerCrypto = iCrypto; |
| issuerKeyName = iKeyName; |
| issuerKeyPW = iKeyPW; |
| |
| samlToken = samlAssertion.toDOM(getDocument()); |
| |
| // |
| // 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. |
| // |
| String confirmMethod = null; |
| List<String> methods = samlAssertion.getConfirmationMethods(); |
| if (methods != null && !methods.isEmpty()) { |
| confirmMethod = methods.get(0); |
| } |
| if (OpenSAMLUtil.isMethodSenderVouches(confirmMethod)) { |
| senderVouches = true; |
| } |
| // |
| // Gather some info about the document to process and store it for |
| // retrieval |
| // |
| if (super.getWsDocInfo() == null) { |
| WSDocInfo wsDocInfo = new WSDocInfo(getDocument()); |
| super.setWsDocInfo(wsDocInfo); |
| } |
| |
| |
| X509Certificate[] certs = null; |
| PublicKey publicKey = null; |
| |
| if (senderVouches) { |
| CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); |
| cryptoType.setAlias(issuerKeyName); |
| certs = issuerCrypto.getX509Certificates(cryptoType); |
| getWsDocInfo().setCrypto(issuerCrypto); |
| } else { |
| // |
| // 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). |
| // |
| if (userCrypto == null || !samlAssertion.isSigned()) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, |
| "invalidSAMLsecurity", |
| new Object[] {"for SAML Signature (Key Holder)"}); |
| } |
| if (secretKey == null) { |
| RequestData data = new RequestData(); |
| data.setWsDocInfo(getWsDocInfo()); |
| SignatureActionToken actionToken = new SignatureActionToken(); |
| data.setSignatureToken(actionToken); |
| actionToken.setCrypto(userCrypto); |
| SAMLKeyInfo samlKeyInfo = |
| SAMLUtil.getCredentialFromSubject( |
| samlAssertion, new WSSSAMLKeyInfoProcessor(data), |
| userCrypto, data.getCallbackHandler() |
| ); |
| if (samlKeyInfo != null) { |
| publicKey = samlKeyInfo.getPublicKey(); |
| certs = samlKeyInfo.getCerts(); |
| getWsDocInfo().setCrypto(userCrypto); |
| } |
| } |
| } |
| if ((certs == null || certs.length == 0 || certs[0] == null) |
| && publicKey == null && secretKey == null) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, |
| "noCertsFound", |
| new Object[] {"SAML signature"}); |
| } |
| |
| if (getSignatureAlgorithm() == null) { |
| PublicKey key = null; |
| if (certs != null && certs[0] != null) { |
| key = certs[0].getPublicKey(); |
| } else if (publicKey != null) { |
| key = publicKey; |
| } else { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, "unknownSignatureAlgorithm" |
| ); |
| } |
| |
| String pubKeyAlgo = key.getAlgorithm(); |
| LOG.debug("automatic sig algo detection: {}", pubKeyAlgo); |
| if (pubKeyAlgo.equalsIgnoreCase("DSA")) { |
| setSignatureAlgorithm(WSConstants.DSA); |
| } else if (pubKeyAlgo.equalsIgnoreCase("RSA")) { |
| setSignatureAlgorithm(WSConstants.RSA); |
| } else { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, |
| "unknownSignatureAlgorithm", |
| new Object[] {pubKeyAlgo}); |
| } |
| } |
| sig = null; |
| |
| try { |
| C14NMethodParameterSpec c14nSpec = null; |
| if (isAddInclusivePrefixes() && getSigCanonicalization().equals(WSConstants.C14N_EXCL_OMIT_COMMENTS)) { |
| Element securityHeaderElement = getSecurityHeader().getSecurityHeaderElement(); |
| List<String> prefixes = getInclusivePrefixes(securityHeaderElement, false); |
| c14nSpec = new ExcC14NParameterSpec(prefixes); |
| } |
| |
| c14nMethod = |
| signatureFactory.newCanonicalizationMethod(getSigCanonicalization(), c14nSpec); |
| } catch (Exception ex) { |
| LOG.error("", ex); |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILED_SIGNATURE, ex, "noXMLSig" |
| ); |
| } |
| |
| keyInfoUri = getIdAllocator().createSecureId("KeyId-", keyInfo); |
| SecurityTokenReference secRef = new SecurityTokenReference(getDocument()); |
| strUri = getIdAllocator().createSecureId("STRId-", secRef); |
| secRef.setID(strUri); |
| setSecurityTokenReference(secRef); |
| |
| if (certs != null && certs.length != 0) { |
| certUri = getIdAllocator().createSecureId("CertId-", certs[0]); |
| } |
| |
| // |
| // 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 Transform during the |
| // signature |
| // |
| try { |
| if (senderVouches) { |
| secRefSaml = new SecurityTokenReference(getDocument()); |
| secRefID = getIdAllocator().createSecureId("STRSAMLId-", secRefSaml); |
| secRefSaml.setID(secRefID); |
| |
| if (useDirectReferenceToAssertion) { |
| Reference ref = new Reference(getDocument()); |
| ref.setURI("#" + samlAssertion.getId()); |
| if (samlAssertion.getSaml1() != null) { |
| ref.setValueType(WSConstants.WSS_SAML_KI_VALUE_TYPE); |
| secRefSaml.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE); |
| } else if (samlAssertion.getSaml2() != null) { |
| secRefSaml.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE); |
| } |
| secRefSaml.setReference(ref); |
| } else { |
| Element keyId = getDocument().createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier"); |
| String valueType = null; |
| if (samlAssertion.getSaml1() != null) { |
| valueType = WSConstants.WSS_SAML_KI_VALUE_TYPE; |
| secRefSaml.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE); |
| } else if (samlAssertion.getSaml2() != null) { |
| valueType = WSConstants.WSS_SAML2_KI_VALUE_TYPE; |
| secRefSaml.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE); |
| } |
| keyId.setAttributeNS( |
| null, "ValueType", valueType |
| ); |
| keyId.appendChild(getDocument().createTextNode(samlAssertion.getId())); |
| Element elem = secRefSaml.getElement(); |
| elem.appendChild(keyId); |
| } |
| getWsDocInfo().addTokenElement(secRefSaml.getElement(), false); |
| } |
| } catch (Exception ex) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILED_SIGNATURE, ex, "noXMLSig" |
| ); |
| } |
| |
| X509Certificate cert = certs != null ? certs[0] : null; |
| configureKeyInfo(secRef, cert, iCrypto != null ? iCrypto : uCrypto, samlAssertion); |
| |
| getWsDocInfo().addTokenElement(samlToken, false); |
| } |
| |
| private void configureKeyInfo( |
| SecurityTokenReference secRef, X509Certificate cert, |
| Crypto crypto, SamlAssertionWrapper samlAssertion |
| ) throws WSSecurityException { |
| if (getCustomKeyInfoElement() == null) { |
| if (senderVouches) { |
| switch (keyIdentifierType) { |
| case WSConstants.BST_DIRECT_REFERENCE: |
| Reference ref = new Reference(getDocument()); |
| ref.setURI("#" + certUri); |
| BinarySecurity binarySecurity = new X509Security(getDocument()); |
| ((X509Security) binarySecurity).setX509Certificate(cert); |
| binarySecurity.setID(certUri); |
| bstToken = binarySecurity.getElement(); |
| getWsDocInfo().addTokenElement(bstToken, false); |
| ref.setValueType(binarySecurity.getValueType()); |
| secRef.setReference(ref); |
| break; |
| |
| case WSConstants.X509_KEY_IDENTIFIER : |
| secRef.setKeyIdentifier(cert); |
| break; |
| |
| case WSConstants.SKI_KEY_IDENTIFIER: |
| secRef.setKeyIdentifierSKI(cert, crypto); |
| break; |
| |
| case WSConstants.THUMBPRINT_IDENTIFIER: |
| secRef.setKeyIdentifierThumb(cert); |
| break; |
| |
| case WSConstants.ISSUER_SERIAL: |
| final String issuer = cert.getIssuerDN().getName(); |
| final java.math.BigInteger serialNumber = cert.getSerialNumber(); |
| final DOMX509IssuerSerial domIssuerSerial = |
| new DOMX509IssuerSerial(getDocument(), issuer, serialNumber); |
| final DOMX509Data domX509Data = new DOMX509Data(getDocument(), domIssuerSerial); |
| secRef.setUnknownElement(domX509Data.getElement()); |
| break; |
| |
| default: |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId" |
| ); |
| } |
| } else if (useDirectReferenceToAssertion) { |
| Reference ref = new Reference(getDocument()); |
| ref.setURI("#" + samlAssertion.getId()); |
| if (samlAssertion.getSaml1() != null) { |
| ref.setValueType(WSConstants.WSS_SAML_KI_VALUE_TYPE); |
| secRef.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE); |
| } else if (samlAssertion.getSaml2() != null) { |
| secRef.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE); |
| } |
| secRef.setReference(ref); |
| } else { |
| Element keyId = getDocument().createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier"); |
| String valueType = null; |
| if (samlAssertion.getSaml1() != null) { |
| valueType = WSConstants.WSS_SAML_KI_VALUE_TYPE; |
| secRef.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE); |
| } else if (samlAssertion.getSaml2() != null) { |
| valueType = WSConstants.WSS_SAML2_KI_VALUE_TYPE; |
| secRef.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE); |
| } |
| keyId.setAttributeNS( |
| null, "ValueType", valueType |
| ); |
| keyId.appendChild(getDocument().createTextNode(samlAssertion.getId())); |
| Element elem = secRef.getElement(); |
| elem.appendChild(keyId); |
| } |
| } |
| |
| marshalKeyInfo(getWsDocInfo()); |
| } |
| |
| /** |
| * 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. |
| * |
| * This methods first prepends the SAML security reference if mode is |
| * <code>senderVouches</code>, then the SAML token itself, |
| */ |
| public void prependSAMLElementsToHeader() { |
| Element securityHeaderElement = getSecurityHeader().getSecurityHeaderElement(); |
| if (senderVouches) { |
| WSSecurityUtil.prependChildElement(securityHeaderElement, secRefSaml.getElement()); |
| } |
| |
| WSSecurityUtil.prependChildElement(securityHeaderElement, samlToken); |
| } |
| |
| |
| /** |
| * Compute the Signature over the references. |
| * |
| * After references are set this method computes the Signature for them. |
| * This method can be called any time after the references were set. See |
| * <code>addReferencesToSign()</code>. |
| * |
| * @throws WSSecurityException |
| */ |
| public void computeSignature( |
| List<javax.xml.crypto.dsig.Reference> referenceList, |
| Element siblingElement |
| ) throws WSSecurityException { |
| try { |
| java.security.Key key; |
| if (senderVouches) { |
| key = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPW); |
| } else if (secretKey != null) { |
| key = KeyUtils.prepareSecretKey(getSignatureAlgorithm(), secretKey); |
| } else { |
| key = userCrypto.getPrivateKey(user, password); |
| } |
| SignatureMethod signatureMethod = |
| signatureFactory.newSignatureMethod(getSignatureAlgorithm(), null); |
| SignedInfo signedInfo = |
| signatureFactory.newSignedInfo(c14nMethod, signatureMethod, referenceList); |
| |
| sig = signatureFactory.newXMLSignature( |
| signedInfo, |
| keyInfo, |
| null, |
| getIdAllocator().createId("SIG-", null), |
| null); |
| |
| Element securityHeaderElement = getSecurityHeader().getSecurityHeaderElement(); |
| // |
| // Prepend the signature element to the security header (after the assertion) |
| // |
| XMLSignContext signContext = null; |
| if (siblingElement != null && siblingElement.getNextSibling() != null) { |
| signContext = |
| new DOMSignContext(key, securityHeaderElement, siblingElement.getNextSibling()); |
| } else { |
| signContext = new DOMSignContext(key, securityHeaderElement); |
| } |
| if (getSignatureProvider() != null) { |
| signContext.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", getSignatureProvider()); |
| } |
| |
| signContext.putNamespacePrefix(WSConstants.SIG_NS, WSConstants.SIG_PREFIX); |
| if (WSConstants.C14N_EXCL_OMIT_COMMENTS.equals(getSigCanonicalization())) { |
| signContext.putNamespacePrefix( |
| WSConstants.C14N_EXCL_OMIT_COMMENTS, |
| WSConstants.C14N_EXCL_OMIT_COMMENTS_PREFIX |
| ); |
| } |
| signContext.setProperty(STRTransform.TRANSFORM_WS_DOC_INFO, getWsDocInfo()); |
| getWsDocInfo().setCallbackLookup(callbackLookup); |
| |
| // Add the elements to sign to the Signature Context |
| getWsDocInfo().setTokensOnContext((DOMSignContext)signContext); |
| |
| sig.sign(signContext); |
| |
| signatureValue = sig.getSignatureValue().getValue(); |
| |
| } catch (Exception ex) { |
| LOG.error(ex.getMessage(), ex); |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILED_SIGNATURE, ex |
| ); |
| } |
| } |
| |
| /** |
| * Return whether a Direct Reference is to be used to reference the assertion. The |
| * default is false. |
| * @return whether a Direct Reference is to be used to reference the assertion |
| */ |
| public boolean isUseDirectReferenceToAssertion() { |
| return useDirectReferenceToAssertion; |
| } |
| |
| /** |
| * Set whether a Direct Reference is to be used to reference the assertion. The |
| * default is false. |
| * @param useDirectReferenceToAssertion whether a Direct Reference is to be used |
| * to reference the assertion |
| */ |
| public void setUseDirectReferenceToAssertion(boolean useDirectReferenceToAssertion) { |
| this.useDirectReferenceToAssertion = useDirectReferenceToAssertion; |
| } |
| |
| } |