/*
* Copyright (C) 2011-2012 Intel Corporation
* All rights reserved.
*/
package test.io;
import com.intel.mtwilson.ApiClient;
import com.intel.mtwilson.My;
import com.intel.mtwilson.api.*;
import com.intel.mtwilson.TrustAssertion;
import com.intel.dcsg.cpg.crypto.SimpleKeystore;
import com.intel.mtwilson.datatypes.xml.HostTrustXmlResponse;
import com.intel.mtwilson.datatypes.xml.HostTrustXmlResponseList;
import com.intel.dcsg.cpg.io.ConfigurationUtil;
import com.intel.mtwilson.model.*;
import com.intel.mtwilson.saml.TrustAssertion.HostTrustAssertion;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import static org.junit.Assert.*;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.Statement;
import org.opensaml.xml.schema.XSAny;
import org.opensaml.xml.schema.XSString;
/**
*
* @author jbuhacoff
*/
public class SamlTest {
private final Logger log = LoggerFactory.getLogger(getClass());
@Test
public void testLoadSettings() throws IOException {
Configuration conf = ConfigurationUtil.fromResource("/localhost-0.5.2.properties");
System.out.println(conf.getString("mtwilson.api.baseurl", "unable to load settings"));
}
@Test
public void testJoinHostnamesWithComma() {
ArrayList<Hostname> hostnames = new ArrayList<Hostname>();
hostnames.add(new Hostname("1.2.3.4"));
hostnames.add(new Hostname("5.6.7.8"));
String hostnamesCSV = StringUtils.join(hostnames, ","); // calls toString() on each hostname
System.out.println(hostnamesCSV);
}
@Test
public void testGetSamlCertificate() throws NoSuchAlgorithmException, KeyManagementException, MalformedURLException, KeyStoreException, IOException, CertificateException, UnrecoverableEntryException, ApiException, SignatureException, ClientException {
Configuration config = ConfigurationUtil.fromResource("/localhost-0.5.2.properties");
ApiClient api = new ApiClient(config);
X509Certificate certificate = api.getSamlCertificate();
log.debug("SAML Certificate Subject: {}", certificate.getSubjectX500Principal().getName());
log.debug("SAML Certificate Issuer: {}", certificate.getIssuerX500Principal().getName());
URL attestationService = new URL(config.getString("mtwilson.api.baseurl"));
SimpleKeystore keystore = new SimpleKeystore(new File(config.getString("mtwilson.api.keystore")), config.getString("mtwilson.api.keystore.password"));
keystore.addTrustedSamlCertificate(certificate, attestationService.getHost());
keystore.save();
log.info("Saved SAML certificate in keystore");
}
@Test
public void testGetSamlForHost() throws IOException, NoSuchAlgorithmException, KeyManagementException, MalformedURLException, KeyStoreException, CertificateException, UnrecoverableEntryException, ApiException, SignatureException, ClientException {
ApiClient api = new ApiClient(ConfigurationUtil.fromResource("/mtwilson-0.5.2.properties"));
String xmloutput = api.getSamlForHost(new Hostname("10.1.71.149"));
log.debug(xmloutput);
TrustAssertion trustAssertion = api.verifyTrustAssertion(xmloutput);
if( trustAssertion.isValid() ) {
Set<String> hostnames = trustAssertion.getHosts();
for(String hostname : hostnames) {
HostTrustAssertion hostTrustAssertion = trustAssertion.getTrustAssertion(hostname);
log.debug("Valid assertion for {}", hostTrustAssertion.getSubject());
for(String attr : hostTrustAssertion.getAttributeNames()) {
log.debug("Signed attribute {}: {}", new String[] { attr, hostTrustAssertion.getStringAttribute(attr) });
}
}
}
else {
log.debug("Invalid assertion", trustAssertion.error());
}
}
@Test
public void testResourceExists() throws IOException {
InputStream in = getClass().getResourceAsStream("/host-149.saml.xml");
assertNotNull(in);
String xml = IOUtils.toString(in);
log.debug("SAML: {}", xml);
}
@Test
public void testSamlVerifierSamlForHost() throws IOException, NoSuchAlgorithmException, KeyManagementException, MalformedURLException, KeyStoreException, CertificateException, UnrecoverableEntryException, ApiException, SignatureException, ParserConfigurationException, SAXException, UnmarshallingException, ClassNotFoundException, InstantiationException, IllegalAccessException, MarshalException, XMLSignatureException, ConfigurationException {
String xml = IOUtils.toString(getClass().getResourceAsStream("/host-149.saml.xml")); // or get via apiclient like in testGetSamlForHost()
// Configuration config = My.configuration().getConfiguration(); //ConfigurationUtil.fromResource("/localhost-0.5.2.properties");
SimpleKeystore keystore = new SimpleKeystore(My.configuration().getKeystoreFile(), My.configuration().getKeystorePassword()); // used to be mtwilson.api.keystore and mtwilson.api.keystore.password properties from configuration
X509Certificate[] trustedCertificates = keystore.getTrustedCertificates(SimpleKeystore.SAML);
TrustAssertion trustAssertion = new TrustAssertion(trustedCertificates, xml);
if( trustAssertion.isValid() ) {
System.out.println("Assertion is valid");
log.info("Assertion is valid");
Set<String> hostnames = trustAssertion.getHosts();
for(String hostname : hostnames) {
HostTrustAssertion hostTrustAssertion = trustAssertion.getTrustAssertion(hostname);
log.debug("Subject: {}", hostTrustAssertion.getSubject());
log.debug("Issuer: {}", hostTrustAssertion.getIssuer());
Set<String> attributes = hostTrustAssertion.getAttributeNames();
for(String attribute : attributes) {
log.debug("Attribute: {} = {}", new String[] { attribute, hostTrustAssertion.getStringAttribute(attribute) });
}
}
}
else {
log.debug("Assertion is NOT valid", trustAssertion.error());
System.out.println("Assertion is NOT valid");
}
}
/**
* bug #1038
* opensaml xml parser is vulnerable to xml external entity injection ... look at the BIOS_Name value in output, which you can get by commentnig out the two lines in the method marked with "bug #1038" :
*
2013-12-03 14:44:14,564 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: Trusted = true
2013-12-03 14:44:14,565 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: Trusted_BIOS = true
2013-12-03 14:44:14,565 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: BIOS_Name = [ProductNames]
ProductName.1033=Microsoft Visual C++ 2008 Redistributable
ProductName.1041=Microsoft Visual C++ 2008 Redistributable
ProductName.1042=Microsoft Visual C++ 2008 Redistributable
ProductName.1028=Microsoft Visual C++ 2008 Redistributable
ProductName.2052=Microsoft Visual C++ 2008 Redistributable
ProductName.1036=Microsoft Visual C++ 2008 Redistributable
ProductName.1040=Microsoft Visual C++ 2008 Redistributable
ProductName.1031=Microsoft Visual C++ 2008 Redistributable
ProductName.3082=Microsoft Visual C++ 2008 Redistributable
2013-12-03 14:44:14,566 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: BIOS_Version = v60
2013-12-03 14:44:14,566 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: BIOS_OEM = EPSD
2013-12-03 14:44:14,566 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: Trusted_VMM = true
2013-12-03 14:44:14,567 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: VMM_Name = Xen
2013-12-03 14:44:14,567 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: VMM_Version = 4.1.0
2013-12-03 14:44:14,567 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: VMM_OSName = SUSE
2013-12-03 14:44:14,568 DEBUG [main] t.i.SamlTest [SamlTest.java:196] Attribute: VMM_OSVersion = 11 P2
*
*
* With the bug #1038 fix, the XXE is prevented and BIOS_Name appears as null:
*
2013-12-03 14:54:50,992 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: Trusted = true
2013-12-03 14:54:50,993 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: Trusted_BIOS = true
2013-12-03 14:54:50,993 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: BIOS_Name = null
2013-12-03 14:54:50,993 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: BIOS_Version = v60
2013-12-03 14:54:50,994 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: BIOS_OEM = EPSD
2013-12-03 14:54:50,994 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: Trusted_VMM = true
2013-12-03 14:54:50,995 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: VMM_Name = Xen
2013-12-03 14:54:50,996 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: VMM_Version = 4.1.0
2013-12-03 14:54:50,996 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: VMM_OSName = SUSE
2013-12-03 14:54:50,996 DEBUG [main] t.i.SamlTest [SamlTest.java:230] Attribute: VMM_OSVersion = 11 P2
*
*
* @throws Exception
*/
@Test
public void testSamlVerifierSamlForHostWithXXE() throws Exception {
String xmlstr = IOUtils.toString(getClass().getResourceAsStream("/host-149.saml_xxe.xml")); // or get via apiclient like in testGetSamlForHost()
// Configuration config = My.configuration().getConfiguration(); //ConfigurationUtil.fromResource("/localhost-0.5.2.properties");
SimpleKeystore keystore = new SimpleKeystore(My.configuration().getKeystoreFile(), My.configuration().getKeystorePassword()); // used to be mtwilson.api.keystore and mtwilson.api.keystore.password properties from configuration
X509Certificate[] trustedCertificates = keystore.getTrustedCertificates(SimpleKeystore.SAML);
TrustAssertion trustAssertion = new TrustAssertion(trustedCertificates, xmlstr);
if( trustAssertion.isValid() ) {
Set<String> hostnames = trustAssertion.getHosts();
for(String hostname : hostnames) {
HostTrustAssertion hostTrustAssertion = trustAssertion.getTrustAssertion(hostname);
System.out.println("Assertion is valid");
log.info("Assertion is valid");
log.debug("Subject: {}", hostTrustAssertion.getSubject());
log.debug("Issuer: {}", hostTrustAssertion.getIssuer());
Set<String> attributes = hostTrustAssertion.getAttributeNames();
for(String attribute : attributes) {
log.debug("Attribute: {} = {}", new String[] { attribute, hostTrustAssertion.getStringAttribute(attribute) });
}
}
}
else {
// above code validates signature but for bug #1038 we want to see if the opensaml xml parser is vulnerable to XXE regardless of the signature - so below we copy code from TrustAssertion to parse the xml anyway and retrieve the attribute values. BIOS_Name is replaced with XXE and you can see sample output in comment above.
log.debug("Assertion is NOT valid", trustAssertion.error());
System.out.println("Assertion is NOT valid");
// simulate here what the TrustAssertion object does -- because it will not make anything available if the signature can't be verified
DefaultBootstrap.bootstrap(); // required to load default configs that ship with opensaml that specify how to build and parse the xml (if you don't do this you will get a null unmarshaller when you try to parse xml)
// simulate readXml
DocumentBuilderFactory factory1 = DocumentBuilderFactory.newInstance ();
factory1.setNamespaceAware (true);
factory1.setExpandEntityReferences(false); // bug #1038 need to prevent XXE
factory1.setXIncludeAware(false); // bug #1038
DocumentBuilder builder = factory1.newDocumentBuilder(); // ParserConfigurationException
ByteArrayInputStream in = new ByteArrayInputStream(xmlstr.getBytes());
Element document = builder.parse(in).getDocumentElement (); // SAXException, IOException
in.close(); // IOExeception
// simulate readAssertion
UnmarshallerFactory factory2 = org.opensaml.xml.Configuration.getUnmarshallerFactory();
Unmarshaller unmarshaller = factory2.getUnmarshaller(document);
XMLObject xml = unmarshaller.unmarshall(document); // UnmarshallingException
Assertion samlAssertion = (Assertion) xml;
for (Statement statement : samlAssertion.getStatements ()) {
if (statement instanceof AttributeStatement) {
for (Attribute attribute :
((AttributeStatement) statement).getAttributes ())
{
String attributeValue = null;
// XXX TODO currently this only grabs the last value if there was more than one value in the attribute... full implementation should handle all possibilities but we do provide a getAssertion() function so the client can navigate the assertion tree directly in case they need something not covered here
for (XMLObject value : attribute.getAttributeValues ()) {
if (value instanceof XSAny) {
attributeValue = (((XSAny) value).getTextContent()); // boolean attributes are the text "true" or "false"
}
if( value instanceof XSString ) {
attributeValue = (((XSString) value).getValue());
}
}
log.debug("Attribute: {} = {}", new String[] { attribute.getName(), attributeValue });
}
}
}
}
}
@Test
public void testAvailableUnmarshallers() throws ParserConfigurationException, IOException, SAXException, UnmarshallingException, ConfigurationException {
DefaultBootstrap.bootstrap(); // required to load default configs that ship with opensaml that specify how to build and parse the xml (if you don't do this you will get a null unmarshaller when you try to parse xml)
String xml = IOUtils.toString(getClass().getResourceAsStream("/host-149.saml.xml")); // or get via apiclient like in testGetSamlForHost()
ByteArrayInputStream in = new ByteArrayInputStream(xml.getBytes());
// parse the xml
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance ();
builderFactory.setNamespaceAware (true);
DocumentBuilder builder = builderFactory.newDocumentBuilder(); // ParserConfigurationException
Element document = builder.parse(in).getDocumentElement (); // SAXException, IOException
in.close(); // IOExeception
assert document != null;
log.debug("Reading assertion from element {}", document.getTagName());
log.debug("Element local name: {}", document.getLocalName());
log.debug("Element namespace: {}", document.getNamespaceURI());
UnmarshallerFactory factory = org.opensaml.xml.Configuration.getUnmarshallerFactory();
Map<QName,Unmarshaller> availableUnmarshallers = factory.getUnmarshallers();
Set<QName> registeredQNames = availableUnmarshallers.keySet();
log.debug("There are {} registered QNames", registeredQNames.size());
for(QName q : availableUnmarshallers.keySet()) {
log.debug("Unmarshaller for QName {} . {} is null? {}", new String[] { q.getNamespaceURI(), q.toString(), availableUnmarshallers.get(q) == null ? "NULL":"not null" });
}
log.debug("Default unmarshaller provider QName namespace {} prefix {} local name {}", new String[] { org.opensaml.xml.Configuration.getDefaultProviderQName().getNamespaceURI(), org.opensaml.xml.Configuration.getDefaultProviderQName().getPrefix(), org.opensaml.xml.Configuration.getDefaultProviderQName().getLocalPart() } );
Unmarshaller unmarshaller = factory.getUnmarshaller(document);
assert unmarshaller != null;
XMLObject xmlObject = unmarshaller.unmarshall(document);
assert xmlObject != null;
Assertion samlAssertion = (Assertion) xmlObject; // UnmarshallingException
log.debug("SAML Subject: {}", samlAssertion.getSubject().getNameID().getValue());
}
/**
* same as the xml function in ApiClient, for testing purposes
*/
private <T> T xml(String document, Class<T> valueType) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance( valueType.getPackage().getName() ); // was just valueType
javax.xml.bind.Unmarshaller u = jc.createUnmarshaller();
// Object o = u.unmarshal( new StreamSource( new StringReader( document ) ) );
JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal( new StreamSource( new StringReader( document ) ) );
return doc.getValue();
}
@Test
public void testCreateBulkHostTrustXmlResponse() throws JAXBException {
JAXBContext jc = JAXBContext.newInstance( "com.intel.mtwilson.datatypes.xml" );
com.intel.mtwilson.datatypes.xml.ObjectFactory factory = new com.intel.mtwilson.datatypes.xml.ObjectFactory();
HostTrustXmlResponseList list = factory.createHostTrustXmlResponseList();
HostTrustXmlResponse item1 = factory.createHostTrustXmlResponse();
item1.setName("1.1.1.1");
item1.setErrorCode("OK");
// item1.setAssertion("<?xml version=\"1.0\"?><Assertion></Assertion>"); // results in a tag with embedded xml escaped like this: <Assertion><?xml version="1.0"?><Assertion></Assertion></Assertion>
// item1.setAssertion("<![CDATA[<?xml version=\"1.0\"?><Assertion></Assertion>]]>"); // also results in a tag with embedded xml escaped like this: <Assertion><![CDATA[<?xml version="1.0"?><Assertion></Assertion>]]></Assertion>
HostTrustXmlResponse item2 = factory.createHostTrustXmlResponse();
item2.setName("1.1.1.2");
item2.setErrorCode("VALIDATION_ERROR");
list.getHost().add(item1);
list.getHost().add(item2);
// serialize
JAXBElement<HostTrustXmlResponseList> listXml = factory.createHosts(list);
Marshaller m = jc.createMarshaller();
m.marshal(listXml, System.out);
// marshall the jaxb ...
}
@Test
public void testBulkHostTrustXmlResponse() throws IOException, JAXBException {
String xmlResponse = IOUtils.toString(getClass().getResourceAsStream("/bulk-hosts-saml-155,205.xml")); // or get via apiclient like in testGetSamlForHost()
// String xmlResponse = IOUtils.toString(getClass().getResourceAsStream("/bulk-hosts-saml-155,205-simple.xml")); // or get via apiclient like in testGetSamlForHost()
log.debug(xmlResponse);
/*
BulkHostTrustXmlResponse bulkHostTrustXmlResponse = xml(xmlResponse, BulkHostTrustXmlResponse.class);
List<HostTrustXmlResponse> hostTrustXmlResponses = bulkHostTrustXmlResponse.getHostTrustXml();
for( HostTrustXmlResponse hostTrustXmlResponse : hostTrustXmlResponses ) {
log.debug("Response for hostname {} status {}", new String[] { hostTrustXmlResponse.getHostname().toString(), hostTrustXmlResponse.getErrorCode().name() });
log.debug(hostTrustXmlResponse.getAssertionXML());
}
*
*/
// for some reason the log.debug statements don't always make it to the log... looks like something is not flushing them at the end of the test. adding a System.out.println() makes it better most of the time...
HostTrustXmlResponseList list = xml(xmlResponse, HostTrustXmlResponseList.class);
for( HostTrustXmlResponse item : list.getHost() ) {
System.out.println("Host name: "+ item.getName());
System.out.println("Host error code: "+ item.getErrorCode());
System.out.println("Host assertion: "+ item.getAssertion());
}
// System.out.println();
}
}