blob: 0430b9f31f0cb4a57c7a7a7dbee24da2c8a11d7d [file] [log] [blame]
/**
* 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.common.saml;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.callback.CallbackHandler;
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.builder.SAML1ComponentBuilder;
import org.apache.wss4j.common.saml.builder.SAML2ComponentBuilder;
import org.apache.wss4j.common.util.DOM2Writer;
import org.apache.wss4j.common.util.InetAddressUtils;
import org.apache.xml.security.stax.impl.util.IDGenerator;
import org.apache.xml.security.utils.XMLUtils;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.SAMLObjectContentReference;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.common.SignableSAMLObject;
import org.opensaml.saml.saml1.core.AttributeStatement;
import org.opensaml.saml.saml1.core.AuthenticationStatement;
import org.opensaml.saml.saml1.core.AuthorizationDecisionStatement;
import org.opensaml.saml.saml1.core.ConfirmationMethod;
import org.opensaml.saml.saml1.core.Statement;
import org.opensaml.saml.saml1.core.Subject;
import org.opensaml.saml.saml1.core.SubjectConfirmation;
import org.opensaml.saml.saml1.core.SubjectStatement;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.AuthzDecisionStatement;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
import org.opensaml.security.credential.BasicCredential;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.opensaml.xmlsec.signature.support.SignerProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Class SamlAssertionWrapper can generate, sign, and validate both SAML v1.1
* and SAML v2.0 assertions.
*/
public class SamlAssertionWrapper {
/**
* Field LOG
*/
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(SamlAssertionWrapper.class);
/**
* Raw SAML Object
*/
private SAMLObject samlObject;
/**
* Which SAML specification to use (currently, only v1.1 and v2.0 are supported)
*/
private SAMLVersion samlVersion;
/**
* The Assertion as a DOM element
*/
private Element assertionElement;
/**
* The SAMLKeyInfo object associated with the Subject KeyInfo
*/
private SAMLKeyInfo subjectKeyInfo;
/**
* The SAMLKeyInfo object associated with the Signature on the Assertion
*/
private SAMLKeyInfo signatureKeyInfo;
/**
* Default Canonicalization algorithm used for signing.
*/
private final String defaultCanonicalizationAlgorithm = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
/**
* Default RSA Signature algorithm used for signing.
*/
private final String defaultRSASignatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
/**
* Default DSA Signature algorithm used for signing.
*/
private final String defaultDSASignatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_DSA;
/**
* Default ECDSA Signature algorithm used for signing.
*/
private final String defaultECDSASignatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1;
/**
* Default Signature Digest algorithm
*/
private final String defaultSignatureDigestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA1;
/**
* Whether this object was instantiated with a DOM Element or an XMLObject initially
*/
private final boolean fromDOM;
/**
* Constructor SamlAssertionWrapper creates a new SamlAssertionWrapper instance.
*
* @param element of type Element
* @throws WSSecurityException
*/
public SamlAssertionWrapper(Element element) throws WSSecurityException {
OpenSAMLUtil.initSamlEngine();
parseElement(element);
fromDOM = true;
}
/**
* Constructor SamlAssertionWrapper creates a new SamlAssertionWrapper instance.
* This is the primary constructor. All other constructor calls should
* be routed to this method to ensure that the wrapper is initialized
* correctly.
*
* @param samlObject of type SAMLObject
*/
public SamlAssertionWrapper(SAMLObject samlObject) throws WSSecurityException {
OpenSAMLUtil.initSamlEngine();
this.samlObject = samlObject;
if (samlObject instanceof org.opensaml.saml.saml1.core.Assertion) {
samlVersion = SAMLVersion.VERSION_11;
} else if (samlObject instanceof org.opensaml.saml.saml2.core.Assertion) {
samlVersion = SAMLVersion.VERSION_20;
} else {
LOG.error(
"SamlAssertionWrapper: found unexpected type "
+ (samlObject != null ? samlObject.getClass().getName() : null)
);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
new Object[] {"A SAML 2.0 or 1.1 Assertion can only be used with "
+ "SamlAssertionWrapper"});
}
fromDOM = false;
}
/**
* Constructor SamlAssertionWrapper creates a new SamlAssertionWrapper instance.
* This constructor is primarily called on the client side to initialize
* the wrapper from a configuration file. <br>
*
* @param samlCallback of type SAMLCallback
*/
public SamlAssertionWrapper(SAMLCallback samlCallback) throws WSSecurityException {
OpenSAMLUtil.initSamlEngine();
if (samlCallback.getAssertionElement() != null) {
parseElement(samlCallback.getAssertionElement());
fromDOM = true;
} else {
// If not then parse the SAMLCallback object
parseCallback(samlCallback);
fromDOM = false;
}
}
/**
* Method getSaml1 returns the saml1 of this SamlAssertionWrapper object.
*
* @return the saml1 (type Assertion) of this SamlAssertionWrapper object.
*/
public org.opensaml.saml.saml1.core.Assertion getSaml1() {
if (samlVersion == SAMLVersion.VERSION_11) {
return (org.opensaml.saml.saml1.core.Assertion)samlObject;
}
return null;
}
/**
* Method getSaml2 returns the saml2 of this SamlAssertionWrapper object.
*
* @return the saml2 (type Assertion) of this SamlAssertionWrapper object.
*/
public org.opensaml.saml.saml2.core.Assertion getSaml2() {
if (samlVersion == SAMLVersion.VERSION_20) {
return (org.opensaml.saml.saml2.core.Assertion)samlObject;
}
return null;
}
/**
* Method isCreated returns the created of this SamlAssertionWrapper object.
*
* @return the created (type boolean) of this SamlAssertionWrapper object.
*/
public boolean isCreated() {
return samlObject != null;
}
/**
* Create a DOM from the current XMLObject content. If the user-supplied doc is not null,
* reparent the returned Element so that it is compatible with the user-supplied document.
*
* @param doc of type Document
* @return Element
*/
public Element toDOM(Document doc) throws WSSecurityException {
if (fromDOM && assertionElement != null) {
parseElement(assertionElement);
if (doc != null) {
return (Element)doc.importNode(assertionElement, true);
}
return assertionElement;
}
assertionElement = OpenSAMLUtil.toDom(samlObject, doc);
return assertionElement;
}
/**
* Method assertionToString ...
*
* @return String
*/
public String assertionToString() throws WSSecurityException {
if (assertionElement == null) {
Element element = toDOM(null);
return DOM2Writer.nodeToString(element);
}
return DOM2Writer.nodeToString(assertionElement);
}
public Instant getNotBefore() {
if (getSamlVersion().equals(SAMLVersion.VERSION_20)) {
return getSaml2().getConditions().getNotBefore();
} else {
return getSaml1().getConditions().getNotBefore();
}
}
public Instant getNotOnOrAfter() {
if (getSamlVersion().equals(SAMLVersion.VERSION_20)) {
return getSaml2().getConditions().getNotOnOrAfter();
} else {
return getSaml1().getConditions().getNotOnOrAfter();
}
}
/**
* Method getId returns the id of this SamlAssertionWrapper object.
*
* @return the id (type String) of this SamlAssertionWrapper object.
*/
public String getId() {
String id = null;
if (samlVersion == SAMLVersion.VERSION_20) {
id = ((org.opensaml.saml.saml2.core.Assertion)samlObject).getID();
if (id == null || id.length() == 0) {
LOG.error("SamlAssertionWrapper: ID was null, seeting a new ID value");
id = IDGenerator.generateID("_");
((org.opensaml.saml.saml2.core.Assertion)samlObject).setID(id);
}
} else if (samlVersion == SAMLVersion.VERSION_11) {
id = ((org.opensaml.saml.saml1.core.Assertion)samlObject).getID();
if (id == null || id.length() == 0) {
LOG.error("SamlAssertionWrapper: ID was null, seeting a new ID value");
id = IDGenerator.generateID("_");
((org.opensaml.saml.saml1.core.Assertion)samlObject).setID(id);
}
} else {
LOG.error("SamlAssertionWrapper: unable to return ID - no saml assertion object");
}
return id;
}
/**
* Method getIssuerString returns the issuerString of this SamlAssertionWrapper object.
*
* @return the issuerString (type String) of this SamlAssertionWrapper object.
*/
public String getIssuerString() {
if (samlVersion == SAMLVersion.VERSION_20
&& ((org.opensaml.saml.saml2.core.Assertion)samlObject).getIssuer() != null) {
return ((org.opensaml.saml.saml2.core.Assertion)samlObject).getIssuer().getValue();
} else if (samlVersion == SAMLVersion.VERSION_11
&& ((org.opensaml.saml.saml1.core.Assertion)samlObject).getIssuer() != null) {
return ((org.opensaml.saml.saml1.core.Assertion)samlObject).getIssuer();
}
LOG.error(
"SamlAssertionWrapper: unable to return Issuer string - no saml assertion "
+ "object or issuer is null"
);
return null;
}
/**
* Method getSubjectName returns the Subject name value
* @return the subjectName of this SamlAssertionWrapper object
*/
public String getSubjectName() {
if (samlVersion == SAMLVersion.VERSION_20) {
org.opensaml.saml.saml2.core.Subject subject =
((org.opensaml.saml.saml2.core.Assertion)samlObject).getSubject();
if (subject != null && subject.getNameID() != null) {
return subject.getNameID().getValue();
}
} else if (samlVersion == SAMLVersion.VERSION_11) {
Subject samlSubject = null;
for (Statement stmt : ((org.opensaml.saml.saml1.core.Assertion)samlObject).getStatements()) {
if (stmt instanceof AttributeStatement) {
AttributeStatement attrStmt = (AttributeStatement) stmt;
samlSubject = attrStmt.getSubject();
} else if (stmt instanceof AuthenticationStatement) {
AuthenticationStatement authStmt = (AuthenticationStatement) stmt;
samlSubject = authStmt.getSubject();
} else {
AuthorizationDecisionStatement authzStmt =
(AuthorizationDecisionStatement)stmt;
samlSubject = authzStmt.getSubject();
}
if (samlSubject != null) {
break;
}
}
if (samlSubject != null && samlSubject.getNameIdentifier() != null) {
return samlSubject.getNameIdentifier().getValue();
}
}
LOG.error(
"SamlAssertionWrapper: unable to return SubjectName - no saml assertion "
+ "object or subject is null"
);
return null;
}
/**
* Method getConfirmationMethods returns the confirmationMethods of this
* SamlAssertionWrapper object.
*
* @return the confirmationMethods of this SamlAssertionWrapper object.
*/
public List<String> getConfirmationMethods() {
List<String> methods = new ArrayList<>();
if (samlVersion == SAMLVersion.VERSION_20) {
org.opensaml.saml.saml2.core.Subject subject =
((org.opensaml.saml.saml2.core.Assertion)samlObject).getSubject();
List<org.opensaml.saml.saml2.core.SubjectConfirmation> confirmations =
subject.getSubjectConfirmations();
for (org.opensaml.saml.saml2.core.SubjectConfirmation confirmation : confirmations) {
methods.add(confirmation.getMethod());
}
} else if (samlVersion == SAMLVersion.VERSION_11) {
List<SubjectStatement> subjectStatements = new ArrayList<>();
org.opensaml.saml.saml1.core.Assertion saml1 =
(org.opensaml.saml.saml1.core.Assertion)samlObject;
subjectStatements.addAll(saml1.getSubjectStatements());
subjectStatements.addAll(saml1.getAuthenticationStatements());
subjectStatements.addAll(saml1.getAttributeStatements());
subjectStatements.addAll(saml1.getAuthorizationDecisionStatements());
for (SubjectStatement subjectStatement : subjectStatements) {
Subject subject = subjectStatement.getSubject();
if (subject != null) {
SubjectConfirmation confirmation = subject.getSubjectConfirmation();
if (confirmation != null) {
XMLObject data = confirmation.getSubjectConfirmationData();
if (data instanceof ConfirmationMethod) {
ConfirmationMethod method = (ConfirmationMethod) data;
methods.add(method.getConfirmationMethod());
}
List<ConfirmationMethod> confirmationMethods =
confirmation.getConfirmationMethods();
for (ConfirmationMethod confirmationMethod : confirmationMethods) {
methods.add(confirmationMethod.getConfirmationMethod());
}
}
}
}
}
return methods;
}
/**
* Method isSigned returns the signed of this SamlAssertionWrapper object.
*
* @return the signed (type boolean) of this SamlAssertionWrapper object.
*/
public boolean isSigned() {
if (samlObject instanceof SignableSAMLObject
&& (((SignableSAMLObject)samlObject).isSigned()
|| ((SignableSAMLObject)samlObject).getSignature() != null)) {
return true;
}
return false;
}
/**
* Method setSignature sets the signature of this SamlAssertionWrapper object.
*
* @param signature the signature of this SamlAssertionWrapper object.
*/
public void setSignature(Signature signature) {
setSignature(signature, defaultSignatureDigestAlgorithm);
}
/**
* Method setSignature sets the signature of this SamlAssertionWrapper object.
*
* @param signature the signature of this SamlAssertionWrapper object.
* @param signatureDigestAlgorithm the signature digest algorithm to use
*/
public void setSignature(Signature signature, String signatureDigestAlgorithm) {
if (samlObject instanceof SignableSAMLObject) {
SignableSAMLObject signableObject = (SignableSAMLObject) samlObject;
signableObject.setSignature(signature);
String digestAlg = signatureDigestAlgorithm;
if (digestAlg == null) {
digestAlg = defaultSignatureDigestAlgorithm;
}
SAMLObjectContentReference contentRef =
(SAMLObjectContentReference)signature.getContentReferences().get(0);
contentRef.setDigestAlgorithm(digestAlg);
signableObject.releaseDOM();
signableObject.releaseChildrenDOM(true);
} else {
LOG.error("Attempt to sign an unsignable object " + samlObject.getClass().getName());
}
}
/**
* Create an enveloped signature on the assertion that has been created.
*
* @param issuerKeyName the Issuer KeyName to use with the issuerCrypto argument
* @param issuerKeyPassword the Issuer Password to use with the issuerCrypto argument
* @param issuerCrypto the Issuer Crypto instance
* @param sendKeyValue whether to send the key value or not
* @throws WSSecurityException
*/
public void signAssertion(String issuerKeyName, String issuerKeyPassword,
Crypto issuerCrypto, boolean sendKeyValue)
throws WSSecurityException {
signAssertion(issuerKeyName, issuerKeyPassword, issuerCrypto,
sendKeyValue, defaultCanonicalizationAlgorithm,
null, defaultSignatureDigestAlgorithm);
}
/**
* Create an enveloped signature on the assertion that has been created.
*
* @param issuerKeyName the Issuer KeyName to use with the issuerCrypto argument
* @param issuerKeyPassword the Issuer Password to use with the issuerCrypto argument
* @param issuerCrypto the Issuer Crypto instance
* @param sendKeyValue whether to send the key value or not
* @param canonicalizationAlgorithm the canonicalization algorithm to be used for signing
* @param signatureAlgorithm the signature algorithm to be used for signing
* @throws WSSecurityException
*/
public void signAssertion(String issuerKeyName, String issuerKeyPassword,
Crypto issuerCrypto, boolean sendKeyValue,
String canonicalizationAlgorithm, String signatureAlgorithm)
throws WSSecurityException {
signAssertion(issuerKeyName, issuerKeyPassword, issuerCrypto, sendKeyValue,
canonicalizationAlgorithm, signatureAlgorithm, defaultSignatureDigestAlgorithm);
}
/**
* Create an enveloped signature on the assertion that has been created.
*
* @param issuerKeyName the Issuer KeyName to use with the issuerCrypto argument
* @param issuerKeyPassword the Issuer Password to use with the issuerCrypto argument
* @param issuerCrypto the Issuer Crypto instance
* @param sendKeyValue whether to send the key value or not
* @param canonicalizationAlgorithm the canonicalization algorithm to be used for signing
* @param signatureAlgorithm the signature algorithm to be used for signing
* @param signatureDigestAlgorithm the signature Digest algorithm to use
* @throws WSSecurityException
*/
public void signAssertion(String issuerKeyName, String issuerKeyPassword,
Crypto issuerCrypto, boolean sendKeyValue,
String canonicalizationAlgorithm, String signatureAlgorithm,
String signatureDigestAlgorithm)
throws WSSecurityException {
//
// Create the signature
//
Signature signature = OpenSAMLUtil.buildSignature();
String c14nAlgo = canonicalizationAlgorithm;
if (c14nAlgo == null) {
c14nAlgo = defaultCanonicalizationAlgorithm;
}
signature.setCanonicalizationAlgorithm(c14nAlgo);
LOG.debug("Using Canonicalization algorithm {}", c14nAlgo);
// prepare to sign the SAML token
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
cryptoType.setAlias(issuerKeyName);
X509Certificate[] issuerCerts = null;
if (issuerCrypto != null) {
issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
}
if (issuerCerts == null || issuerCerts.length == 0) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
new Object[] {"No issuer certs were found to sign the SAML Assertion using issuer name: "
+ issuerKeyName});
}
String sigAlgo = signatureAlgorithm;
if (sigAlgo == null) {
sigAlgo = defaultRSASignatureAlgorithm;
String pubKeyAlgo = issuerCerts[0].getPublicKey().getAlgorithm();
LOG.debug("automatic sig algo detection: {}", pubKeyAlgo);
if (pubKeyAlgo.equalsIgnoreCase("DSA")) {
sigAlgo = defaultDSASignatureAlgorithm;
} else if (pubKeyAlgo.equalsIgnoreCase("EC")) {
sigAlgo = defaultECDSASignatureAlgorithm;
}
}
LOG.debug("Using Signature algorithm {}", sigAlgo);
PrivateKey privateKey;
try {
privateKey = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPassword);
} catch (Exception ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
}
if (privateKey == null) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
new Object[] {"No private key was found using issuer name: " + issuerKeyName});
}
signature.setSignatureAlgorithm(sigAlgo);
BasicX509Credential signingCredential =
new BasicX509Credential(issuerCerts[0], privateKey);
signature.setSigningCredential(signingCredential);
X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory();
if (sendKeyValue) {
kiFactory.setEmitPublicKeyValue(true);
} else {
kiFactory.setEmitEntityCertificate(true);
}
try {
KeyInfo keyInfo = kiFactory.newInstance().generate(signingCredential);
signature.setKeyInfo(keyInfo);
} catch (org.opensaml.security.SecurityException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
new Object[] {"Error generating KeyInfo from signing credential"});
}
// add the signature to the assertion
setSignature(signature, signatureDigestAlgorithm);
}
/**
* Verify the signature of this assertion
*
* @throws WSSecurityException
*/
public void verifySignature(
SAMLKeyInfoProcessor keyInfoProcessor, Crypto sigCrypto
) throws WSSecurityException {
Signature sig = getSignature();
if (sig != null) {
KeyInfo keyInfo = sig.getKeyInfo();
if (keyInfo == null) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
new Object[] {"cannot get certificate or key"}
);
}
SAMLKeyInfo samlKeyInfo =
SAMLUtil.getCredentialFromKeyInfo(keyInfo.getDOM(), keyInfoProcessor, sigCrypto);
verifySignature(samlKeyInfo);
} else {
LOG.debug("SamlAssertionWrapper: no signature to validate");
}
}
/**
* Verify the signature of this assertion
*
* @throws WSSecurityException
*/
public void verifySignature(SAMLKeyInfo samlKeyInfo) throws WSSecurityException {
Signature sig = getSignature();
if (sig != null) {
if (samlKeyInfo == null) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
new Object[] {"cannot get certificate or key"}
);
}
BasicCredential credential = null;
if (samlKeyInfo.getCerts() != null) {
credential = new BasicX509Credential(samlKeyInfo.getCerts()[0]);
} else if (samlKeyInfo.getPublicKey() != null) {
credential = new BasicCredential(samlKeyInfo.getPublicKey());
} else {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
new Object[] {"cannot get certificate or key"}
);
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(SignerProvider.class.getClassLoader());
SignatureValidator.validate(sig, credential);
} catch (SignatureException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex,
"empty", new Object[] {"SAML signature validation failed"});
} finally {
Thread.currentThread().setContextClassLoader(loader);
}
signatureKeyInfo = samlKeyInfo;
} else {
LOG.debug("SamlAssertionWrapper: no signature to validate");
}
}
/**
* Validate the signature of the Assertion against the Profile. This does not actually
* verify the signature itself (see the verifySignature method for this)
* @throws WSSecurityException
*/
public void validateSignatureAgainstProfile() throws WSSecurityException {
Signature sig = getSignature();
if (sig != null) {
SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator();
try {
validator.validate(sig);
} catch (SignatureException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex,
"empty", new Object[] {"SAML signature validation failed"});
}
}
}
/**
* This method parses the KeyInfo of the Subject. It then stores the SAMLKeyInfo object that
* has been obtained for future processing.
* @throws WSSecurityException
*/
public void parseSubject(
SAMLKeyInfoProcessor keyInfoProcessor,
Crypto sigCrypto,
CallbackHandler callbackHandler
) throws WSSecurityException {
if (samlVersion == SAMLVersion.VERSION_11) {
subjectKeyInfo =
SAMLUtil.getCredentialFromSubject(
(org.opensaml.saml.saml1.core.Assertion)samlObject, keyInfoProcessor,
sigCrypto, callbackHandler
);
} else if (samlVersion == SAMLVersion.VERSION_20) {
subjectKeyInfo =
SAMLUtil.getCredentialFromSubject(
(org.opensaml.saml.saml2.core.Assertion)samlObject, keyInfoProcessor,
sigCrypto, callbackHandler
);
}
}
/**
* Method getSamlVersion returns the samlVersion of this SamlAssertionWrapper object.
*
* @return the samlVersion (type SAMLVersion) of this SamlAssertionWrapper object.
*/
public SAMLVersion getSamlVersion() {
if (samlVersion == null) {
// Try to set the version.
LOG.debug(
"The SAML version was null in getSamlVersion(). Recomputing SAML version...");
if (samlObject instanceof org.opensaml.saml.saml1.core.Assertion) {
samlVersion = SAMLVersion.VERSION_11;
} else if (samlObject instanceof org.opensaml.saml.saml2.core.Assertion) {
samlVersion = SAMLVersion.VERSION_20;
} else {
// We are only supporting SAML v1.1 or SAML v2.0 at this time.
throw new IllegalStateException(
"Could not determine the SAML version number. Check your "
+ "configuration and try again."
);
}
}
return samlVersion;
}
/**
* Get the Assertion as a DOM Element.
* @return the assertion as a DOM Element
*/
public Element getElement() {
return assertionElement;
}
/**
* Get the SAMLKeyInfo associated with the signature of the assertion
* @return the SAMLKeyInfo associated with the signature of the assertion
*/
public SAMLKeyInfo getSignatureKeyInfo() {
return signatureKeyInfo;
}
/**
* Get the SAMLKeyInfo associated with the Subject KeyInfo
* @return the SAMLKeyInfo associated with the Subject KeyInfo
*/
public SAMLKeyInfo getSubjectKeyInfo() {
return subjectKeyInfo;
}
/**
* Get the SignatureValue bytes of the signed SAML Assertion
* @return the SignatureValue bytes of the signed SAML Assertion
* @throws WSSecurityException
*/
public byte[] getSignatureValue() throws WSSecurityException {
Signature sig = null;
if (samlObject instanceof SignableSAMLObject) {
sig = ((SignableSAMLObject)samlObject).getSignature();
}
if (sig != null) {
return getSignatureValue(sig);
}
return null;
}
private byte[] getSignatureValue(Signature signature) throws WSSecurityException {
Element signatureElement = signature.getDOM();
if (signatureElement != null) {
Element signedInfoElem = XMLUtils.getNextElement(signatureElement.getFirstChild());
if (signedInfoElem != null) {
Element signatureValueElement =
XMLUtils.getNextElement(signedInfoElem.getNextSibling());
if (signatureValueElement != null) {
String base64Input = XMLUtils.getFullTextChildrenFromNode(signatureValueElement);
return XMLUtils.decode(base64Input);
}
}
}
return null;
}
public Signature getSignature() throws WSSecurityException {
if (samlObject instanceof SignableSAMLObject) {
return ((SignableSAMLObject)samlObject).getSignature();
}
return null;
}
public SAMLObject getSamlObject() {
return samlObject;
}
/**
* Check the Conditions of the Assertion.
*/
public void checkConditions(int futureTTL) throws WSSecurityException {
Instant validFrom = null;
Instant validTill = null;
if (getSamlVersion().equals(SAMLVersion.VERSION_20)
&& getSaml2().getConditions() != null) {
validFrom = getSaml2().getConditions().getNotBefore();
validTill = getSaml2().getConditions().getNotOnOrAfter();
} else if (getSamlVersion().equals(SAMLVersion.VERSION_11)
&& getSaml1().getConditions() != null) {
validFrom = getSaml1().getConditions().getNotBefore();
validTill = getSaml1().getConditions().getNotOnOrAfter();
}
if (validFrom != null) {
Instant currentTime = Instant.now();
currentTime = currentTime.plusSeconds(futureTTL);
if (validFrom.isAfter(currentTime)) {
LOG.warn("SAML Token condition (Not Before) not met");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
if (validTill != null && validTill.isBefore(Instant.now())) {
LOG.warn("SAML Token condition (Not On Or After) not met");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
/**
* Check the IssueInstant value of the Assertion.
*/
public void checkIssueInstant(int futureTTL, int ttl) throws WSSecurityException {
Instant issueInstant = null;
Instant validTill = null;
if (getSamlVersion().equals(SAMLVersion.VERSION_20)
&& getSaml2().getConditions() != null) {
validTill = getSaml2().getConditions().getNotOnOrAfter();
issueInstant = getSaml2().getIssueInstant();
} else if (getSamlVersion().equals(SAMLVersion.VERSION_11)
&& getSaml1().getConditions() != null) {
validTill = getSaml1().getConditions().getNotOnOrAfter();
issueInstant = getSaml1().getIssueInstant();
}
// Check the IssueInstant is not in the future, subject to the future TTL
if (issueInstant != null) {
Instant currentTime = Instant.now().plusSeconds(futureTTL);
if (issueInstant.isAfter(currentTime)) {
LOG.warn("SAML Token IssueInstant not met");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
// If there is no NotOnOrAfter, then impose a TTL on the IssueInstant.
if (validTill == null) {
currentTime = currentTime.minusSeconds(ttl);
if (issueInstant.isBefore(currentTime)) {
LOG.warn("SAML Token IssueInstant not met. The assertion was created too long ago.");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
}
}
/**
* Check the AudienceRestrictions of the Assertion
*/
public void checkAudienceRestrictions(List<String> audienceRestrictions) throws WSSecurityException {
// Now check the audience restriction conditions
if (audienceRestrictions == null || audienceRestrictions.isEmpty()) {
return;
}
if (getSamlVersion().equals(SAMLVersion.VERSION_20) && getSaml2().getConditions() != null) {
org.opensaml.saml.saml2.core.Conditions conditions = getSaml2().getConditions();
if (conditions != null && conditions.getAudienceRestrictions() != null
&& !conditions.getAudienceRestrictions().isEmpty()) {
boolean foundAddress = false;
for (org.opensaml.saml.saml2.core.AudienceRestriction audienceRestriction
: conditions.getAudienceRestrictions()) {
if (audienceRestriction.getAudiences() != null) {
List<org.opensaml.saml.saml2.core.Audience> audiences =
audienceRestriction.getAudiences();
for (org.opensaml.saml.saml2.core.Audience audience : audiences) {
String audienceURI = audience.getAudienceURI();
if (audienceRestrictions.contains(audienceURI)) {
foundAddress = true;
break;
}
}
}
}
if (!foundAddress) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
} else if (getSamlVersion().equals(SAMLVersion.VERSION_11) && getSaml1().getConditions() != null) {
org.opensaml.saml.saml1.core.Conditions conditions = getSaml1().getConditions();
if (conditions != null && conditions.getAudienceRestrictionConditions() != null
&& !conditions.getAudienceRestrictionConditions().isEmpty()) {
boolean foundAddress = false;
for (org.opensaml.saml.saml1.core.AudienceRestrictionCondition audienceRestriction
: conditions.getAudienceRestrictionConditions()) {
if (audienceRestriction.getAudiences() != null) {
List<org.opensaml.saml.saml1.core.Audience> audiences =
audienceRestriction.getAudiences();
for (org.opensaml.saml.saml1.core.Audience audience : audiences) {
String audienceURI = audience.getUri();
if (audienceRestrictions.contains(audienceURI)) {
foundAddress = true;
break;
}
}
}
}
if (!foundAddress) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
}
}
/**
* Check the various attributes of the AuthnStatements of the assertion (if any)
*/
public void checkAuthnStatements(int futureTTL) throws WSSecurityException {
if (getSamlVersion().equals(SAMLVersion.VERSION_20)
&& getSaml2().getAuthnStatements() != null) {
List<AuthnStatement> authnStatements = getSaml2().getAuthnStatements();
for (AuthnStatement authnStatement : authnStatements) {
Instant authnInstant = authnStatement.getAuthnInstant();
Instant sessionNotOnOrAfter = authnStatement.getSessionNotOnOrAfter();
String subjectLocalityAddress = null;
if (authnStatement.getSubjectLocality() != null
&& authnStatement.getSubjectLocality().getAddress() != null) {
subjectLocalityAddress = authnStatement.getSubjectLocality().getAddress();
}
validateAuthnStatement(authnInstant, sessionNotOnOrAfter,
subjectLocalityAddress, futureTTL);
}
} else if (getSamlVersion().equals(SAMLVersion.VERSION_11)
&& getSaml1().getAuthenticationStatements() != null) {
List<AuthenticationStatement> authnStatements =
getSaml1().getAuthenticationStatements();
for (AuthenticationStatement authnStatement : authnStatements) {
Instant authnInstant = authnStatement.getAuthenticationInstant();
String subjectLocalityAddress = null;
if (authnStatement.getSubjectLocality() != null
&& authnStatement.getSubjectLocality().getIPAddress() != null) {
subjectLocalityAddress = authnStatement.getSubjectLocality().getIPAddress();
}
validateAuthnStatement(authnInstant, null,
subjectLocalityAddress, futureTTL);
}
}
}
private void validateAuthnStatement(
Instant authnInstant, Instant sessionNotOnOrAfter, String subjectLocalityAddress,
int futureTTL
) throws WSSecurityException {
// AuthnInstant in the future
Instant currentTime = Instant.now();
currentTime = currentTime.plusSeconds(futureTTL);
if (authnInstant.isAfter(currentTime)) {
LOG.warn("SAML Token AuthnInstant not met");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
// Stale SessionNotOnOrAfter
if (sessionNotOnOrAfter != null && sessionNotOnOrAfter.isBefore(Instant.now())) {
LOG.warn("SAML Token SessionNotOnOrAfter not met");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
// Check that the SubjectLocality address is an IP address
if (subjectLocalityAddress != null
&& !(InetAddressUtils.isIPv4Address(subjectLocalityAddress)
|| InetAddressUtils.isIPv6Address(subjectLocalityAddress))) {
LOG.warn("SAML Token SubjectLocality address is not valid: " + subjectLocalityAddress);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}
/**
* Parse the DOM Element into Opensaml objects.
*/
private void parseElement(Element element) throws WSSecurityException {
XMLObject xmlObject = OpenSAMLUtil.fromDom(element);
if (xmlObject instanceof org.opensaml.saml.saml1.core.Assertion) {
this.samlObject = (SAMLObject)xmlObject;
samlVersion = SAMLVersion.VERSION_11;
} else if (xmlObject instanceof org.opensaml.saml.saml2.core.Assertion) {
this.samlObject = (SAMLObject)xmlObject;
samlVersion = SAMLVersion.VERSION_20;
} else {
LOG.error(
"SamlAssertionWrapper: found unexpected type " + xmlObject.getClass().getName()
);
}
assertionElement = element;
}
/**
* Parse a SAMLCallback object to create a SAML Assertion
*/
private void parseCallback(
SAMLCallback samlCallback
) throws WSSecurityException {
samlVersion = samlCallback.getSamlVersion();
if (samlVersion == null) {
samlVersion = SAMLVersion.VERSION_20;
}
String issuer = samlCallback.getIssuer();
String issuerFormat = samlCallback.getIssuerFormat();
String issuerQualifier = samlCallback.getIssuerQualifier();
if (samlVersion.equals(SAMLVersion.VERSION_11)) {
// Build a SAML v1.1 assertion
org.opensaml.saml.saml1.core.Assertion saml1 =
SAML1ComponentBuilder.createSamlv1Assertion(issuer);
try {
// Process the SAML authentication statement(s)
List<AuthenticationStatement> authenticationStatements =
SAML1ComponentBuilder.createSamlv1AuthenticationStatement(
samlCallback.getAuthenticationStatementData()
);
saml1.getAuthenticationStatements().addAll(authenticationStatements);
// Process the SAML attribute statement(s)
List<AttributeStatement> attributeStatements =
SAML1ComponentBuilder.createSamlv1AttributeStatement(
samlCallback.getAttributeStatementData()
);
saml1.getAttributeStatements().addAll(attributeStatements);
// Process the SAML authorization decision statement(s)
List<AuthorizationDecisionStatement> authDecisionStatements =
SAML1ComponentBuilder.createSamlv1AuthorizationDecisionStatement(
samlCallback.getAuthDecisionStatementData()
);
saml1.getAuthorizationDecisionStatements().addAll(authDecisionStatements);
// Build the complete assertion
org.opensaml.saml.saml1.core.Conditions conditions =
SAML1ComponentBuilder.createSamlv1Conditions(samlCallback.getConditions());
saml1.setConditions(conditions);
if (samlCallback.getAdvice() != null) {
org.opensaml.saml.saml1.core.Advice advice =
SAML1ComponentBuilder.createAdvice(samlCallback.getAdvice());
saml1.setAdvice(advice);
}
} catch (org.opensaml.security.SecurityException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
new Object[] {"Error generating KeyInfo from signing credential"}
);
}
// Set the OpenSaml2 XMLObject instance
samlObject = saml1;
} else if (samlVersion.equals(SAMLVersion.VERSION_20)) {
// Build a SAML v2.0 assertion
org.opensaml.saml.saml2.core.Assertion saml2 = SAML2ComponentBuilder.createAssertion();
Issuer samlIssuer = SAML2ComponentBuilder.createIssuer(issuer, issuerFormat, issuerQualifier);
// Authn Statement(s)
List<AuthnStatement> authnStatements =
SAML2ComponentBuilder.createAuthnStatement(
samlCallback.getAuthenticationStatementData()
);
saml2.getAuthnStatements().addAll(authnStatements);
// Attribute statement(s)
List<org.opensaml.saml.saml2.core.AttributeStatement> attributeStatements =
SAML2ComponentBuilder.createAttributeStatement(
samlCallback.getAttributeStatementData()
);
saml2.getAttributeStatements().addAll(attributeStatements);
// AuthzDecisionStatement(s)
List<AuthzDecisionStatement> authDecisionStatements =
SAML2ComponentBuilder.createAuthorizationDecisionStatement(
samlCallback.getAuthDecisionStatementData()
);
saml2.getAuthzDecisionStatements().addAll(authDecisionStatements);
// Build the SAML v2.0 assertion
saml2.setIssuer(samlIssuer);
try {
org.opensaml.saml.saml2.core.Subject subject =
SAML2ComponentBuilder.createSaml2Subject(samlCallback.getSubject());
saml2.setSubject(subject);
} catch (org.opensaml.security.SecurityException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
new Object[] {"Error generating KeyInfo from signing credential"}
);
}
org.opensaml.saml.saml2.core.Conditions conditions =
SAML2ComponentBuilder.createConditions(samlCallback.getConditions());
saml2.setConditions(conditions);
if (samlCallback.getAdvice() != null) {
org.opensaml.saml.saml2.core.Advice advice =
SAML2ComponentBuilder.createAdvice(samlCallback.getAdvice());
saml2.setAdvice(advice);
}
// Set the OpenSaml2 XMLObject instance
samlObject = saml2;
}
}
}