/* * Copyright (C) 2011-2012 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.saml; import com.intel.dcsg.cpg.crypto.RsaUtil; import com.intel.dcsg.cpg.crypto.SamlUtil; import com.intel.dcsg.cpg.x509.X509Util; import com.intel.dcsg.cpg.crypto.CryptographyException; import com.intel.mtwilson.xml.ClasspathResourceResolver; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.KeyStoreException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.crypto.MarshalException; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.opensaml.DefaultBootstrap; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeStatement; import org.opensaml.saml2.core.Statement; import org.opensaml.xml.Configuration; 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.opensaml.xml.schema.XSAny; import org.opensaml.xml.schema.XSString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.xml.sax.SAXException; /** * This class extracts trust information from a SAML assertion. * * Before using the assertions contained within a TrustAssertion object, you * must call isValid() to find out if the provided assertion is valid. If it * is, you can call getSubject(), getIssuer(), getAttributeNames(), and * getStringAttribute(), etc. * If isValid() returns false, you can call error() to get the Exception object * that describes the validation error. * * See also http://ws.apache.org/wss4j/config.html * * @author jbuhacoff */ public class TrustAssertion { private final Logger log = LoggerFactory.getLogger(getClass()); private Assertion assertion; private HashMap<String,HostTrustAssertion> hostAssertionMap; // host -> Map of assertions about the host // private HashMap<String,String> assertionMap; private boolean isValid; private Exception error; /** * Trusted SAML-signing certificates in the keystore must be marked for * this trusted purpose with the tag "(saml)" or "(SAML)" at the end of * their alias. * * @param trustedSigners keystore with at least one trusted certificate with the "(saml)" tag in its alias * @param xml returned from attestation service * @throws ParserConfigurationException * @throws SAXException * @throws IOException * @throws UnmarshallingException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws MarshalException * @throws XMLSignatureException * @throws KeyStoreException */ public TrustAssertion(X509Certificate[] trustedSigners, String xml) { try { // is the xml signed by a trusted signer? Element document = readXml(xml); SamlUtil verifier = new SamlUtil(); // ClassNotFoundException, InstantiationException, IllegalAccessException boolean isVerified = verifier.verifySAMLSignature(document, trustedSigners); if( isVerified ) { log.info("Validated signature in xml document"); // populate assertions map 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) assertion = readAssertion(document); // ParserConfigurationException, SAXException, IOException, UnmarshallingException // assertionMap = new HashMap<String,String>(); hostAssertionMap = new HashMap<String,HostTrustAssertion>(); populateAssertionMap(); isValid = true; error = null; } else { throw new IllegalArgumentException("Cannot verify XML signature"); } } catch(Exception e) { isValid = false; error = e; assertion = null; // assertionMap = null; hostAssertionMap = null; } } public boolean isValid() { return isValid; } /** * * @return null if assertion is valid, otherwise an exception object describing the error */ public Exception error() { return error; } /** * * @return the OpenSAML Assertion object, or null if there was an error */ public Assertion getAssertion() { return assertion; } /** * @return the assertion's issue instant * @since 0.5.3 */ public Date getDate() { return assertion.getIssueInstant().toDate(); } public Set<String> getHosts() { return hostAssertionMap.keySet(); } public HostTrustAssertion getTrustAssertion(String hostname) { return hostAssertionMap.get(hostname); } public static class HostTrustAssertion { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HostTrustAssertion.class); private Assertion assertion; private Map<String,String> assertionMap; // attributes for a single host public HostTrustAssertion(Assertion assertion, Map<String,String> assertionMap) { this.assertion = assertion; this.assertionMap = assertionMap; } /** * @return the assertion's issue instant * @since 0.5.3 */ public Date getDate() { return assertion.getIssueInstant().toDate(); } /** * * @return the assertion subject's AIK public key * @throws NullPointerException if isValid() == false */ public String getSubject() { // return assertion.getSubject().getNameID().getValue(); return assertionMap.get("Host_Name"); } /** * * @return the assertion subject's format * @throws NullPointerException if isValid() == false */ public String getSubjectFormat() { // return assertion.getSubject().getNameID().getFormat(); return "hostname"; } /** * * @return the assertion issuer * @throws NullPointerException if isValid() == false */ public String getIssuer() { return assertion.getIssuer().getValue(); } /** * * @return a set of the available attribute names in the assertion * @throws NullPointerException if isValid() == false */ public Set<String> getAttributeNames() { HashSet<String> names = new HashSet<String>(); names.addAll(assertionMap.keySet()); return names; } /** * * @param name * @return the value of the named attribute * @throws NullPointerException if isValid() == false */ public String getStringAttribute(String name) { return assertionMap.get(name); } /* public Boolean getBooleanAttribute(String name) { String value = assertionMap.get(name); if( value == null ) { return null; } return Boolean.valueOf(value); }*/ public X509Certificate getAikCertificate() throws CertificateException { String pem = assertionMap.get("AIK_Certificate"); if( pem == null || pem.isEmpty() ) { return null; } X509Certificate cert = X509Util.decodePemCertificate(pem); return cert; } public PublicKey getAikPublicKey() throws CryptographyException { // if there's an aik certificate, get the public key from there try { X509Certificate cert = getAikCertificate(); if( cert != null ) { return cert.getPublicKey(); } } catch(Exception e) { log.debug("Error while getting aik certificate: "+e.toString(), e); // but we keep going to try the aik public key field } // otherwise, look for a public key field String pem = assertionMap.get("AIK_PublicKey"); if( pem == null || pem.isEmpty() ) { return null; } PublicKey publicKey = RsaUtil.decodePemPublicKey(pem); return publicKey; } public boolean isHostTrusted() { String trusted = assertionMap.get("Trusted"); return trusted != null && trusted.equalsIgnoreCase("true"); } public boolean isHostBiosTrusted() { String trusted = assertionMap.get("Trusted_BIOS"); return trusted != null && trusted.equalsIgnoreCase("true"); } public boolean isHostVmmTrusted() { String trusted = assertionMap.get("Trusted_VMM"); return trusted != null && trusted.equalsIgnoreCase("true"); } public boolean isHostLocationTrusted() { String trusted = assertionMap.get("Trusted_Location"); return trusted != null && trusted.equalsIgnoreCase("true"); } } private Element readXml(String xml) throws ParserConfigurationException, SAXException, IOException { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schemaFactory.setResourceResolver(new ClasspathResourceResolver()); try(InputStream samlSchemaProtocolStream = getClass().getResourceAsStream("/saml-schema-protocol-2.0.xsd"); InputStream samlSchemaAssertionStream = getClass().getResourceAsStream("/saml-schema-assertion-2.0.xsd"); InputStream xencSchemaStream = getClass().getResourceAsStream("/xenc-schema.xsd"); InputStream xmldigCoreSchemaStream = getClass().getResourceAsStream("/xmldsig-core-schema.xsd")) { Source samlSchemaProtocol = new StreamSource(samlSchemaProtocolStream); // http://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd Source samlSchemaAssertion = new StreamSource(samlSchemaAssertionStream); // http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd Source xencSchema = new StreamSource(xencSchemaStream); // http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/xenc-schema.xsd Source xmldsigCoreSchema = new StreamSource(xmldigCoreSchemaStream); // http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd Schema schema = schemaFactory.newSchema(new Source[] { samlSchemaProtocol, samlSchemaAssertion, xencSchema, xmldsigCoreSchema }); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware (true); factory.setExpandEntityReferences(false); // bug #1038 prevent XXE factory.setXIncludeAware(false); // bug #1038 prevent XXE factory.setSchema(schema); // fix for javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID HostTrustAssertion DocumentBuilder builder = factory.newDocumentBuilder(); // ParserConfigurationException try(ByteArrayInputStream in = new ByteArrayInputStream(xml.getBytes())) { Element document = builder.parse(in).getDocumentElement (); // SAXException, IOException return document; } } } private Assertion readAssertion(Element document) throws UnmarshallingException { log.debug("Reading assertion from element {}", document.getTagName()); UnmarshallerFactory factory = Configuration.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(document); XMLObject xml = unmarshaller.unmarshall(document); // UnmarshallingException Assertion samlAssertion = (Assertion) xml; return samlAssertion; } /** * Sample assertion statements that may appear in the XML: * Trusted (boolean) * Trusted_BIOS (boolean) * Trusted_VMM (boolean) * BIOS_Name (string) * BIOS_Version (string) * BIOS_OEM (string) * VMM_Name (string) * VMM_Version (string) * VMM_OSName (string) * VMM_OSVersion (string) * The BIOS_* entries will only appear if Trusted_BIOS is true * The VMM_* entries will only appear if Trusted_VMM is true */ private void populateAssertionMap() { for (Statement statement : assertion.getStatements ()) { if (statement instanceof AttributeStatement) { HashMap<String,String> assertionMap = new HashMap<String,String>(); HostTrustAssertion hostTrustAssertion = new HostTrustAssertion(assertion, assertionMap); for (Attribute attribute : ((AttributeStatement) statement).getAttributes ()) { String attributeValue = null; 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()); } } assertionMap.put(attribute.getName(), attributeValue); } hostAssertionMap.put(assertionMap.get("Host_Name"), hostTrustAssertion); } } } }