blob: 66a7d71ae74c6aa0886a8d3e2c4b017b10d18368 [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.io.IOException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
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.util.XMLUtils;
import org.opensaml.saml.saml2.core.SubjectConfirmationData;
import org.w3c.dom.Element;
/**
* Utility methods for SAML stuff
*/
public final class SAMLUtil {
/**
* This constant defines the maximum amount of child elements of a Signature KeyInfo, associated with a
* signed SAML assertion. Any other child element will be ignored.
*/
private static final int MAX_KEYINFO_CONTENT_LIST_SIZE = 3;
/**
* This constant defines the maximum amount of child elements of a X509Data KeyInfo, associated with a
* signed SAML assertion. Any other child element will be ignored.
*/
private static final int MAX_X509DATA_SIZE = 5;
private static final String SIG_NS = "http://www.w3.org/2000/09/xmldsig#";
private SAMLUtil() {
// Complete
}
/**
* Parse a SAML Assertion to obtain a SAMLKeyInfo object from
* the Subject of the assertion
*
* @param samlAssertion The SAML Assertion
* @param keyInfoProcessor A pluggable way to parse the KeyInfo
* @return a SAMLKeyInfo object
* @throws WSSecurityException
*/
public static SAMLKeyInfo getCredentialFromSubject(
SamlAssertionWrapper samlAssertion,
SAMLKeyInfoProcessor keyInfoProcessor,
Crypto sigCrypto,
CallbackHandler callbackHandler
) throws WSSecurityException {
if (samlAssertion.getSaml1() != null) {
return getCredentialFromSubject(
samlAssertion.getSaml1(), keyInfoProcessor, sigCrypto, callbackHandler
);
} else if (samlAssertion.getSaml2() != null) {
return getCredentialFromSubject(
samlAssertion.getSaml2(), keyInfoProcessor, sigCrypto, callbackHandler
);
}
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
new Object[] {"Cannot get credentials from an unknown SAML Assertion"});
}
/**
* Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a
* SAML 1.1 assertion
* @param assertion The SAML 1.1 assertion
* @param keyInfoProcessor A pluggable way to parse the KeyInfo
* @param sigCrypto A Crypto instance
* @param callbackHandler A CallbackHandler instance
* @return The SAMLKeyInfo object obtained from the Subject
* @throws WSSecurityException
*/
public static SAMLKeyInfo getCredentialFromSubject(
org.opensaml.saml.saml1.core.Assertion assertion,
SAMLKeyInfoProcessor keyInfoProcessor,
Crypto sigCrypto,
CallbackHandler callbackHandler
) throws WSSecurityException {
for (org.opensaml.saml.saml1.core.Statement stmt : assertion.getStatements()) {
org.opensaml.saml.saml1.core.Subject samlSubject = null;
if (stmt instanceof org.opensaml.saml.saml1.core.AttributeStatement) {
org.opensaml.saml.saml1.core.AttributeStatement attrStmt =
(org.opensaml.saml.saml1.core.AttributeStatement) stmt;
samlSubject = attrStmt.getSubject();
} else if (stmt instanceof org.opensaml.saml.saml1.core.AuthenticationStatement) {
org.opensaml.saml.saml1.core.AuthenticationStatement authStmt =
(org.opensaml.saml.saml1.core.AuthenticationStatement) stmt;
samlSubject = authStmt.getSubject();
} else {
org.opensaml.saml.saml1.core.AuthorizationDecisionStatement authzStmt =
(org.opensaml.saml.saml1.core.AuthorizationDecisionStatement)stmt;
samlSubject = authzStmt.getSubject();
}
if (samlSubject != null && samlSubject.getSubjectConfirmation() != null) {
Element sub = samlSubject.getSubjectConfirmation().getDOM();
Element keyInfoElement =
XMLUtils.getDirectChildElement(sub, "KeyInfo", SIG_NS);
if (keyInfoElement != null) {
return getCredentialFromKeyInfo(
keyInfoElement, keyInfoProcessor, sigCrypto
);
}
}
}
return null;
}
/**
* Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a
* SAML 2 assertion
* @param assertion The SAML 2 assertion
* @param keyInfoProcessor A pluggable way to parse the KeyInfo
* @param sigCrypto A Crypto instance
* @param callbackHandler A CallbackHandler instance
* @return The SAMLKeyInfo object obtained from the Subject
* @throws WSSecurityException
*/
public static SAMLKeyInfo getCredentialFromSubject(
org.opensaml.saml.saml2.core.Assertion assertion,
SAMLKeyInfoProcessor keyInfoProcessor,
Crypto sigCrypto,
CallbackHandler callbackHandler
) throws WSSecurityException {
org.opensaml.saml.saml2.core.Subject samlSubject = assertion.getSubject();
if (samlSubject != null) {
List<org.opensaml.saml.saml2.core.SubjectConfirmation> subjectConfList =
samlSubject.getSubjectConfirmations();
for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConfirmation : subjectConfList) {
SubjectConfirmationData subjConfData =
subjectConfirmation.getSubjectConfirmationData();
if (subjConfData != null) {
Element sub = subjConfData.getDOM();
Element keyInfoElement =
XMLUtils.getDirectChildElement(sub, "KeyInfo", SIG_NS);
if (keyInfoElement != null) {
return getCredentialFromKeyInfo(
keyInfoElement, keyInfoProcessor, sigCrypto
);
}
}
}
}
return null;
}
/**
* This method returns a SAMLKeyInfo corresponding to the credential found in the
* KeyInfo (DOM Element) argument.
* @param keyInfoElement The KeyInfo as a DOM Element
* @param keyInfoProcessor A pluggable way to parse the KeyInfo
* @param sigCrypto A Crypto instance
* @return The credential (as a SAMLKeyInfo object)
* @throws WSSecurityException
*/
public static SAMLKeyInfo getCredentialFromKeyInfo(
Element keyInfoElement,
SAMLKeyInfoProcessor keyInfoProcessor,
Crypto sigCrypto
) throws WSSecurityException {
//
// First try to find an EncryptedKey, BinarySecret or a SecurityTokenReference via DOM
//
if (keyInfoProcessor != null) {
SAMLKeyInfo samlKeyInfo = keyInfoProcessor.processSAMLKeyInfo(keyInfoElement);
if (samlKeyInfo != null) {
return samlKeyInfo;
}
}
//
// Next marshal the KeyInfo DOM element into a javax KeyInfo object and get the
// (public key) credential
//
KeyInfoFactory keyInfoFactory = null;
try {
keyInfoFactory = KeyInfoFactory.getInstance("DOM", "ApacheXMLDSig");
} catch (NoSuchProviderException ex) {
keyInfoFactory = KeyInfoFactory.getInstance("DOM");
}
XMLStructure keyInfoStructure = new DOMStructure(keyInfoElement);
try {
javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo =
keyInfoFactory.unmarshalKeyInfo(keyInfoStructure);
List<?> list = keyInfo.getContent();
X509Certificate[] certs = null;
PublicKey publicKey = null;
for (int i = 0; i < list.size(); i++) {
// Put a hard bound on how many child elements there can be of KeyInfo
if (i >= MAX_KEYINFO_CONTENT_LIST_SIZE) {
break;
}
XMLStructure xmlStructure = (XMLStructure) list.get(i);
if (xmlStructure instanceof KeyValue && publicKey == null) {
publicKey = ((KeyValue)xmlStructure).getPublicKey();
} else if (xmlStructure instanceof X509Data) {
List<?> x509Data = ((X509Data)xmlStructure).getContent();
for (int j = 0; j < x509Data.size(); j++) {
// Put a hard bound on how many child elements there can be of X509Data
if (j >= MAX_X509DATA_SIZE || certs != null) {
break;
}
Object x509obj = x509Data.get(j);
if (x509obj instanceof X509Certificate) {
certs = new X509Certificate[1];
certs[0] = (X509Certificate)x509obj;
} else if (x509obj instanceof X509IssuerSerial) {
if (sigCrypto == null) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "noSigCryptoFile"
);
}
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
cryptoType.setIssuerSerial(
((X509IssuerSerial)x509obj).getIssuerName(),
((X509IssuerSerial)x509obj).getSerialNumber()
);
certs = sigCrypto.getX509Certificates(cryptoType);
if (certs == null || certs.length < 1) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
new Object[] {"cannot get certificate or key"}
);
}
}
}
}
}
if (certs != null || publicKey != null) {
SAMLKeyInfo samlKeyInfo = new SAMLKeyInfo(certs);
samlKeyInfo.setPublicKey(publicKey);
return samlKeyInfo;
}
} catch (Exception ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, ex, "invalidSAMLsecurity",
new Object[] {"cannot get certificate or key"}
);
}
return null;
}
public static void doSAMLCallback(
CallbackHandler callbackHandler, SAMLCallback callback
) {
// Create a new SAMLCallback with all of the information from the properties file.
try {
// Get the SAML source data using the currently configured callback implementation.
callbackHandler.handle(new SAMLCallback[]{callback});
} catch (IOException | UnsupportedCallbackException e) {
throw new IllegalStateException(
"Error while creating SAML assertion wrapper", e
);
}
}
}