/* * Copyright (C) 2011-2012 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.saml; import com.intel.dcsg.cpg.configuration.CommonsConfigurationAdapter; import com.intel.dcsg.cpg.crypto.Sha1Digest; import com.intel.mtwilson.tag.model.x509.*; import com.intel.mtwilson.datatypes.TxtHost; import com.intel.dcsg.cpg.io.Resource; import com.intel.mtwilson.tag.model.X509AttributeCertificate; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.util.*; import javax.xml.crypto.MarshalException; import javax.xml.crypto.dsig.XMLSignatureException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.asn1.ASN1Encodable; import org.joda.time.DateTime; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; import org.opensaml.common.SAMLObjectBuilder; import org.opensaml.common.SAMLVersion; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeStatement; import org.opensaml.saml2.core.AttributeValue; import org.opensaml.saml2.core.Issuer; import org.opensaml.saml2.core.NameID; import org.opensaml.saml2.core.Subject; import org.opensaml.saml2.core.SubjectConfirmation; import org.opensaml.saml2.core.SubjectConfirmationData; import org.opensaml.saml2.core.impl.AssertionMarshaller; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObjectBuilder; import org.opensaml.xml.XMLObjectBuilderFactory; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.schema.XSString; import org.opensaml.xml.schema.XSAny; import org.opensaml.xml.schema.XSBase64Binary; import org.opensaml.xml.util.XMLHelper; import org.w3c.dom.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * When we respond with an assertion, if we want to prevent caching we should include these headers: * Cache-Control: no-cache, no-store, must-revalidate, private * Pragma: no-cache * But there is no harm in the client caching the attestation results for as long as THEY feel comfortable with it. * * @author jbuhacoff */ public class SamlGenerator { private Logger log = LoggerFactory.getLogger(getClass()); private static XMLObjectBuilderFactory builderFactory; private String issuerName; // for example, http://1.2.3.4/AttestationService private String issuerServiceName; // for example "AttestationService" private Integer validitySeconds; // for example 3600 for one hour private SAMLSignature signatureGenerator = null; private SamlAssertion samlAssertion = null; // private Resource keystoreResource = null; /** * Compatibility constructor * @param keystoreResource ignored * @param configuration commons-configuration object * @throws ConfigurationException */ public SamlGenerator(Resource keystoreResource, org.apache.commons.configuration.Configuration configuration) throws ConfigurationException { this(new CommonsConfigurationAdapter(configuration)); } /** * Configuration keys: * saml.issuer=http://1.2.3.4/AttestationService # used in SAML * saml.validity.seconds=3600 # 3600 seconds = 1 hour * saml.keystore.file=C:/Intel/CloudSecurity/SAML.jks # path for keystore file (absolute or relative to the intel/cloudsecurity folder) * saml.keystore.password=password # password for keystore file * saml.key.alias=forSigning # alias of the signing key in the keystore file * saml.key.password=password # password of the signing key * jsr105Provider=org.jcp.xml.dsig.internal.dom.XMLDSigRI # SAML XML signature provider * keystore.path=. # disk path to SAML keystore (currently ignored) * * * @param configuration with the keys described * @throws ConfigurationException */ public SamlGenerator(com.intel.dcsg.cpg.configuration.Configuration configuration /*Resource keystoreResource, org.apache.commons.configuration.Configuration configuration*/) throws ConfigurationException { builderFactory = getSAMLBuilder(); try { signatureGenerator = new SAMLSignature(/*keystoreResource,*/ configuration); } catch (ClassNotFoundException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | IllegalAccessException | InstantiationException | IOException | CertificateException ex) { log.error("Cannot load SAML signature generator: "+ex.getMessage(), ex); } setIssuer(configuration.getString("saml.issuer", "AttestationService")); setValiditySeconds(configuration.getInteger("saml.validity.seconds", 3600)); } public final void setIssuer(String issuer) { this.issuerName = issuer; this.issuerServiceName = "AttestationService-0.5.4"; } /** * * @param seconds number of seconds or null to not set any expiration */ public final void setValiditySeconds(Integer seconds) { this.validitySeconds = seconds; } /* public void setKeystoreResource(Resource keystoreResource) { this.keystoreResource = keystoreResource; }*/ /** * Input is a Host record with all the attributes to assert * Output is XML containing the SAML assertions * * From /hosts/trust we get BIOS:1,VMM:1 * From /hosts/location we get location * From /pollhosts we get trust level "unknown/untrusted/trusted" and timestamp * From /hosts/reports/trust we get host name, mle info string, created on, overall trust status, and verified on * From /hosts/reports/manifest we get PCR values, trust status, and verified on for each PCR * * @return @SamlAssertion * @throws MarshallingException */ public SamlAssertion generateHostAssertion(TxtHost host, X509AttributeCertificate tagCertificate) throws MarshallingException, ConfigurationException, UnknownHostException, GeneralSecurityException, XMLSignatureException, MarshalException { samlAssertion = new SamlAssertion(); Assertion assertion = createAssertion(host, tagCertificate); AssertionMarshaller marshaller = new AssertionMarshaller(); Element plaintextElement = marshaller.marshall(assertion); String originalAssertionString = XMLHelper.nodeToString(plaintextElement); System.out.println("Assertion String: " + originalAssertionString); // add signatures and/or encryption signAssertion(plaintextElement); samlAssertion.assertion = XMLHelper.nodeToString(plaintextElement); System.out.println("Signed Assertion String: " + samlAssertion.assertion ); return samlAssertion; } /** * Generates a multi-host SAML assertion which contains an AttributeStatement * for each host containing a Host_Address attribute with the host IP address * or hostname and the trust attributes as for a single-host assertion. * The Subject of the multi-host SAML assertion should not be used because * it is simply the collection hosts in the assertion and no statements * are made about the collection as a whole. * * @param hosts * @return * @throws SamlException */ public SamlAssertion generateHostAssertions(Collection<TxtHostWithAssetTag> hosts) throws SamlException { try { samlAssertion = new SamlAssertion(); Assertion assertion = createAssertion(hosts); AssertionMarshaller marshaller = new AssertionMarshaller(); Element plaintextElement = marshaller.marshall(assertion); String originalAssertionString = XMLHelper.nodeToString(plaintextElement); System.out.println("Assertion String: " + originalAssertionString); // add signatures and/or encryption signAssertion(plaintextElement); samlAssertion.assertion = XMLHelper.nodeToString(plaintextElement); System.out.println("Signed Assertion String: " + samlAssertion.assertion ); return samlAssertion; } catch(Exception e) { throw new SamlException(e); } } public static XMLObjectBuilderFactory getSAMLBuilder() throws ConfigurationException{ if(builderFactory == null){ // OpenSAML 2.3 DefaultBootstrap.bootstrap(); builderFactory = Configuration.getBuilderFactory(); } return builderFactory; } // create the issuer private Issuer createIssuer() { // Create Issuer SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME); Issuer issuer = (Issuer) issuerBuilder.buildObject(); issuer.setValue(this.issuerName); return issuer; } // create the Subject Name private NameID createNameID(String hostName) { // Create the NameIdentifier SAMLObjectBuilder nameIdBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME); NameID nameId = (NameID) nameIdBuilder.buildObject(); nameId.setValue(hostName); // nameId.setNameQualifier(input.getStrNameQualifier()); optional: nameId.setFormat(NameID.UNSPECIFIED); // !!! CAN ALSO USE X509 SUBJECT FROM HOST CERTIFICATE instead of host name in database return nameId; } private NameID createNameID(TxtHost host) { return createNameID(host.getHostName().toString()); } // create the Subject and Subject Confirmation private SubjectConfirmation createSubjectConfirmation(TxtHost host) throws ConfigurationException, UnknownHostException { SAMLObjectBuilder subjectConfirmationBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME); SubjectConfirmation subjectConfirmation = (SubjectConfirmation) subjectConfirmationBuilder.buildObject(); subjectConfirmation.setMethod(SubjectConfirmation.METHOD_SENDER_VOUCHES); subjectConfirmation.setSubjectConfirmationData(createSubjectConfirmationData(host)); // Create the NameIdentifier SAMLObjectBuilder nameIdBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME); NameID nameId = (NameID) nameIdBuilder.buildObject(); nameId.setValue(issuerServiceName); // nameId.setNameQualifier(input.getStrNameQualifier()); optional: nameId.setFormat(NameID.UNSPECIFIED); // !!! CAN ALSO USE X509 SUBJECT FROM HOST CERTIFICATE instead of host name in database subjectConfirmation.setNameID(nameId); return subjectConfirmation; } /** * * The SubjectConfirmationData element may be extended with custom information that we want to include, both as attributes or as child elements. * * See also section 2.4.1.2 Element <SubjectConfirmationData> of http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf * * @param host * @return * @throws ConfigurationException * @throws UnknownHostException */ private SubjectConfirmationData createSubjectConfirmationData(TxtHost host) throws ConfigurationException, UnknownHostException { SAMLObjectBuilder confirmationMethodBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); SubjectConfirmationData confirmationMethod = (SubjectConfirmationData) confirmationMethodBuilder.buildObject(); DateTime now = new DateTime(); // Required to add to cache samlAssertion.created_ts = now.toDate(); confirmationMethod.setNotBefore(now); if( validitySeconds != null ) { confirmationMethod.setNotOnOrAfter(now.plusSeconds(validitySeconds)); // Required to add to cache samlAssertion.expiry_ts = confirmationMethod.getNotOnOrAfter().toDate(); } InetAddress localhost = InetAddress.getLocalHost(); confirmationMethod.setAddress(localhost.getHostAddress()); // NOTE: This is the ATTESTATION SERVICE IP ADDRESS, **NOT** THE HOST ADDRESS return confirmationMethod; } private Subject createSubject(TxtHost host) throws ConfigurationException, UnknownHostException { // Create the Subject SAMLObjectBuilder subjectBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Subject.DEFAULT_ELEMENT_NAME); Subject subject = (Subject) subjectBuilder.buildObject(); subject.setNameID(createNameID(host)); subject.getSubjectConfirmations().add(createSubjectConfirmation(host)); return subject; } // create the host attributes /** * An attribute can be multi-valued, but this method builds a single-valued * String attribute such as FirstName=John or IPAddress=1.2.3.4 * @param name * @param value * @return * @throws ConfigurationException */ private Attribute createStringAttribute(String name, String value) throws ConfigurationException { SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME); Attribute attr = (Attribute) attrBuilder.buildObject(); attr.setName(name); XMLObjectBuilder xmlBuilder = builderFactory.getBuilder(XSString.TYPE_NAME); XSString attrValue = (XSString) xmlBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); attrValue.setValue(value); attr.getAttributeValues().add(attrValue); return attr; } /** * This method builds a single-valued boolean attribute such as isTrusted=true * @param name * @param value * @return * @throws ConfigurationException */ private Attribute createBooleanAttribute(String name, boolean value) throws ConfigurationException { SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME); Attribute attr = (Attribute) attrBuilder.buildObject(); attr.setName(name); XMLObjectBuilder xmlBuilder = builderFactory.getBuilder(XSAny.TYPE_NAME); XSAny attrValue = (XSAny) xmlBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); attrValue.setTextContent( value ? "true" : "false" ); attr.getAttributeValues().add(attrValue); return attr; } /** * Creates a base64-encoded attribute * @param name * @param value * @return * @throws ConfigurationException */ private Attribute createBase64BinaryAttribute(String name, byte[] value) throws ConfigurationException { SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME); Attribute attr = (Attribute) attrBuilder.buildObject(); attr.setName(name); XMLObjectBuilder xmlBuilder = builderFactory.getBuilder(XSBase64Binary.TYPE_NAME); XSBase64Binary attrValue = (XSBase64Binary) xmlBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBase64Binary.TYPE_NAME); attrValue.setValue(Base64.encodeBase64String(value)); attr.getAttributeValues().add(attrValue); return attr; } /* works but not needed private List<Attribute> createStringAttributes(Map<String,String> attributes) throws ConfigurationException { ArrayList<Attribute> list = new ArrayList<Attribute>(); for(Map.Entry<String,String> e : attributes.entrySet()) { Attribute attr = createStringAttribute(e.getKey(), e.getValue()); list.add(attr); } return list; } * */ // currently unused but probably works /* private Attribute createComplexAttribute(String name, String xmlValue) throws ConfigurationException { SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME); Attribute attr = (Attribute) attrBuilder.buildObject(); attr.setName(name); XMLObjectBuilder stringBuilder = builderFactory.getBuilder(XSString.TYPE_NAME); XSAny attrValue = (XSAny) stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); attrValue.setTextContent(xmlValue); attr.getAttributeValues().add(attrValue); return attr; } */ // private final String DEFAULT_OID = "2.5.4.789.1"; private AttributeStatement createHostAttributes(TxtHost host, X509AttributeCertificate tagCertificate) throws ConfigurationException { // Builder Attributes SAMLObjectBuilder attrStatementBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME); AttributeStatement attrStatement = (AttributeStatement) attrStatementBuilder.buildObject(); // add host attributes (both for single host and multi-host assertions) attrStatement.getAttributes().add(createStringAttribute("Host_Name", host.getHostName().toString())); attrStatement.getAttributes().add(createStringAttribute("Host_Address", host.getIPAddress())); // attrStatement.getAttributes().add(createStringAttribute("Host_UUID", host.getUuid())); // attrStatement.getAttributes().add(createStringAttribute("Host_AIK_SHA1", host.getUuid())); // Create the attribute statements that are trusted attrStatement.getAttributes().add(createBooleanAttribute("Trusted", host.isBiosTrusted() && host.isVmmTrusted())); attrStatement.getAttributes().add(createBooleanAttribute("Trusted_BIOS", host.isBiosTrusted())); if( host.isBiosTrusted() ) { attrStatement.getAttributes().add(createStringAttribute("BIOS_Name", host.getBios().getName())); attrStatement.getAttributes().add(createStringAttribute("BIOS_Version", host.getBios().getVersion())); attrStatement.getAttributes().add(createStringAttribute("BIOS_OEM", host.getBios().getOem())); } attrStatement.getAttributes().add(createBooleanAttribute("Trusted_VMM", host.isVmmTrusted())); if( host.isVmmTrusted() ) { attrStatement.getAttributes().add(createStringAttribute("VMM_Name", host.getVmm().getName())); attrStatement.getAttributes().add(createStringAttribute("VMM_Version", host.getVmm().getVersion())); attrStatement.getAttributes().add(createStringAttribute("VMM_OSName", host.getVmm().getOsName())); attrStatement.getAttributes().add(createStringAttribute("VMM_OSVersion", host.getVmm().getOsVersion())); } //attrStatement.getAttributes().add(createBooleanAttribute("Trusted_Location", host.isLocationTrusted())); //if( host.isLocationTrusted() ) { // attrStatement.getAttributes().add(createStringAttribute("Location", host.getLocation())); //} if (tagCertificate != null) { // add the asset tag attestation status and if the status is trusted, then add all the attributes. In order to uniquely // identify all the asset tags on the client side, we will just append the text ATAG for all of them. attrStatement.getAttributes().add(createBooleanAttribute("Asset_Tag", host.isAssetTagTrusted())); attrStatement.getAttributes().add(createStringAttribute("Asset_Tag_Certificate_Sha1", Sha1Digest.digestOf(tagCertificate.getEncoded()).toString())); if( host.isAssetTagTrusted()) { // get all microformat attributes List<UTF8NameValueMicroformat> microformatAttributes = tagCertificate.getAttributes(UTF8NameValueMicroformat.class); for(UTF8NameValueMicroformat microformatAttribute : microformatAttributes) { log.debug("microformat attribute OID {} name {} value {}", UTF8NameValueMicroformat.OID, microformatAttribute.getName(), microformatAttribute.getValue()); attrStatement.getAttributes().add(createStringAttribute(String.format("TAG[" + microformatAttribute.getName() + "]"),microformatAttribute.getValue())); } // get all name-valuesequence attributes List<UTF8NameValueSequence> nameValueSequenceAttributes = tagCertificate.getAttributes(UTF8NameValueSequence.class); for(UTF8NameValueSequence nameValueSequenceAttribute : nameValueSequenceAttributes) { log.debug("namevaluesequence attribute OID {} name {} values {}", UTF8NameValueSequence.OID, nameValueSequenceAttribute.getName(), nameValueSequenceAttribute.getValues()); attrStatement.getAttributes().add(createStringAttribute(String.format("TAG[" + nameValueSequenceAttribute.getName() + "]"), StringUtils.join(nameValueSequenceAttribute.getValues(), ","))); } // all attributes including above and any other custom attributes will be available directly via the certificate attrStatement.getAttributes().add(createBase64BinaryAttribute("TagCertificate", tagCertificate.getEncoded())); } else { log.debug("Since Asset tag is not verified, no attributes would be added"); } } else { log.debug("Since asset tag is not provisioned, asset tag attribute will not be added to the assertion."); } if( host.getAikCertificate() != null ) { attrStatement.getAttributes().add(createStringAttribute("AIK_Certificate", host.getAikCertificate())); attrStatement.getAttributes().add(createStringAttribute("AIK_SHA1", host.getAikSha1())); } else if( host.getAikPublicKey() != null ) { attrStatement.getAttributes().add(createStringAttribute("AIK_PublicKey", host.getAikPublicKey())); attrStatement.getAttributes().add(createStringAttribute("AIK_SHA1", host.getAikSha1())); } return attrStatement; } /* private AttributeStatement createHostAttributes(TxtHost host, ManifestType pcrManifest) throws ConfigurationException { AttributeStatement attrStatement = createHostAttributes(host); attrStatement.getAttributes().add(createComplexAttribute("Manifest", pcrManifest); return attrStatement; } */ /** * Creates an assertion with attributes of the host * * ID attribute: see section 5.4.2 "References" of http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf * * @param host * @return */ private Assertion createAssertion(TxtHost host, X509AttributeCertificate tagCertificate) throws ConfigurationException, UnknownHostException { // Create the assertion SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Assertion.DEFAULT_ELEMENT_NAME); Assertion assertion = (Assertion) assertionBuilder.buildObject(); assertion.setID("HostTrustAssertion"); // ID is arbitrary, only needs to be unique WITHIN THE DOCUMENT, and is required so that the Signature element can refer to it, for example #HostTrustAssertion assertion.setIssuer(createIssuer()); DateTime now = new DateTime(); assertion.setIssueInstant(now); assertion.setVersion(SAMLVersion.VERSION_20); assertion.setSubject(createSubject(host)); assertion.getAttributeStatements().add(createHostAttributes(host, tagCertificate)); return assertion; } /** * Differences from createAssertion: * - the assertion ID is "MultipleHostTrustAssertion" instead of "HostTrustAssertion" * - there is no overall Subject for the assertion because it's for multiple host * - each host is identified with host attributes within its own attribute statement * * @param hosts * @return * @throws ConfigurationException * @throws UnknownHostException */ private Assertion createAssertion(Collection<TxtHostWithAssetTag> hosts) throws ConfigurationException, UnknownHostException { // Create the assertion SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Assertion.DEFAULT_ELEMENT_NAME); Assertion assertion = (Assertion) assertionBuilder.buildObject(); assertion.setID("MultipleHostTrustAssertion"); // ID is arbitrary, only needs to be unique WITHIN THE DOCUMENT, and is required so that the Signature element can refer to it, for example #HostTrustAssertion assertion.setIssuer(createIssuer()); DateTime now = new DateTime(); assertion.setIssueInstant(now); assertion.setVersion(SAMLVersion.VERSION_20); // assertion.setSubject(createSubject(host)); for(TxtHostWithAssetTag host : hosts) { assertion.getAttributeStatements().add(createHostAttributes(host.getHost(), host.getTagCertificate())); } return assertion; } private void signAssertion(Element assertion) throws GeneralSecurityException, XMLSignatureException, MarshalException { // Signature // SignedInfo // CanonicalizationMethod Algorithm=http://www.w3.org/2001/10/xml-exc-c14n# // SignatureMethod Algorithm=http://www.w3.org/2000/09/xmldsig#rsa-sha1 // Reference URI="#HostTrustAssertion" // Transforms // Transform Algorithm=http://www.w3.org/2000/09/xmldsig#enveloped-signature // Transform Algorithm=http://www.w3.org/2001/10/xml-exc-c14n# // DigestMethod Algorithm=http://www.w3.org/2000/09/xmldsig#rsa-sha1 // DigestValue (the digest value as text content) // SignatureValue (the signature as text content) // KeyInfo // X509Data // X509Certificate (the certificate as text content) // KeyInfo: can include Certificate (to make it easy to find in a public keystore or verify its CA) if( signatureGenerator != null ) { signatureGenerator.signSAMLObject(assertion); } } }